diff --git a/CHANGELOG.md b/CHANGELOG.md index f73b8139..5920eaf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog +## 2.0.1-nullsafety.4 - 2021-03-17 + +### Added +* Added test cases. + +## 2.0.0-nullsafety.3 - 2021-03-09 + +### Added +* Added nullsafety. + + ## 1.2.4 - 2020-11-12 ### Fixed diff --git a/example/pubspec.lock b/example/pubspec.lock index 21566b89..f70398a1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,42 +7,42 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" convert: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -92,28 +92,35 @@ packages: path: ".." relative: true source: path - version: "1.2.4" + version: "2.0.1-nullsafety.4" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" path_drawing: dependency: transitive description: @@ -134,28 +141,14 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "3.0.4" - platform_detect: - dependency: transitive - description: - name: platform_detect - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" + version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.4" + version: "1.0.3" sky_engine: dependency: transitive description: flutter @@ -167,98 +160,105 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" url_launcher: dependency: "direct main" description: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.5.3" + version: "5.7.10" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+1" + version: "0.0.1+4" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+7" + version: "0.0.1+9" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.8" + version: "1.0.9" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.3+2" + version: "0.1.5+3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+3" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "4.5.1" sdks: - dart: ">=2.10.0-110 <2.11.0" - flutter: ">=1.18.0-6.0.pre <2.0.0" + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.22.0" diff --git a/lib/components/accordian/gf_accordian.dart b/lib/components/accordion/gf_accordion.dart similarity index 92% rename from lib/components/accordian/gf_accordian.dart rename to lib/components/accordion/gf_accordion.dart index 7b331522..9b30f88a 100644 --- a/lib/components/accordian/gf_accordian.dart +++ b/lib/components/accordion/gf_accordion.dart @@ -4,19 +4,19 @@ import 'package:getwidget/getwidget.dart'; class GFAccordion extends StatefulWidget { /// An accordion is used to show (and hide) content. Use [showAccordion] to hide & show the accordion content. const GFAccordion( - {Key key, - this.titleChild, + {Key? key, + this.title, this.content, + this.titleChild, + this.contentChild, this.collapsedTitleBackgroundColor = GFColors.WHITE, this.expandedTitleBackgroundColor = const Color(0xFFE0E0E0), this.collapsedIcon = const Icon(Icons.keyboard_arrow_down), this.expandedIcon = const Icon(Icons.keyboard_arrow_up), - this.title, this.textStyle = const TextStyle(color: Colors.black, fontSize: 16), this.titlePadding = const EdgeInsets.all(10), this.contentBackgroundColor, this.contentPadding = const EdgeInsets.all(10), - this.contentChild, this.titleBorder = const Border(), this.contentBorder = const Border(), this.margin, @@ -30,13 +30,13 @@ class GFAccordion extends StatefulWidget { final bool showAccordion; /// child of type [Widget]is alternative to title key. title will get priority over titleChild - final Widget titleChild; + final Widget? titleChild; /// content of type[String] which shows the messages after the [GFAccordion] is expanded - final String content; + final String? content; /// contentChild of type [Widget]is alternative to content key. content will get priority over contentChild - final Widget contentChild; + final Widget? contentChild; /// type of [Color] or [GFColors] which is used to change the background color of the [GFAccordion] title when it is collapsed final Color collapsedTitleBackgroundColor; @@ -51,7 +51,7 @@ class GFAccordion extends StatefulWidget { final Widget expandedIcon; /// text of type [String] is alternative to child. text will get priority over titleChild - final String title; + final String? title; /// textStyle of type [textStyle] will be applicable to text only and not for the child final TextStyle textStyle; @@ -63,10 +63,10 @@ class GFAccordion extends StatefulWidget { final EdgeInsets contentPadding; /// type of [Color] or [GFColors] which is used to change the background color of the [GFAccordion] description - final Color contentBackgroundColor; + final Color? contentBackgroundColor; /// margin of type [EdgeInsets] which is used to set the margin of the [GFAccordion] - final EdgeInsets margin; + final EdgeInsets? margin; /// titleBorderColor of type [Color] or [GFColors] which is used to change the border color of title final Border titleBorder; @@ -81,7 +81,7 @@ class GFAccordion extends StatefulWidget { final BorderRadius contentBorderRadius; /// function called when the content body collapsed - final Function(bool) onToggleCollapsed; + final Function(bool)? onToggleCollapsed; @override _GFAccordionState createState() => _GFAccordionState(); @@ -89,10 +89,10 @@ class GFAccordion extends StatefulWidget { class _GFAccordionState extends State with TickerProviderStateMixin { - AnimationController animationController; - AnimationController controller; - Animation offset; - bool showAccordion; + late AnimationController animationController; + late AnimationController controller; + late Animation offset; + late bool showAccordion; @override void initState() { @@ -142,7 +142,7 @@ class _GFAccordionState extends State children: [ Expanded( child: widget.title != null - ? Text(widget.title, style: widget.textStyle) + ? Text(widget.title!, style: widget.textStyle) : (widget.titleChild ?? Container()), ), showAccordion ? widget.expandedIcon : widget.collapsedIcon @@ -162,7 +162,7 @@ class _GFAccordionState extends State child: SlideTransition( position: offset, child: widget.content != null - ? Text(widget.content) + ? Text(widget.content!) : (widget.contentChild ?? Container()), )) : Container() @@ -183,7 +183,7 @@ class _GFAccordionState extends State } showAccordion = !showAccordion; if (widget.onToggleCollapsed != null) { - widget.onToggleCollapsed(showAccordion); + widget.onToggleCollapsed!(showAccordion); } }); } diff --git a/lib/components/alert/gf_alert.dart b/lib/components/alert/gf_alert.dart index da62957c..9970410c 100644 --- a/lib/components/alert/gf_alert.dart +++ b/lib/components/alert/gf_alert.dart @@ -5,15 +5,15 @@ import 'package:getwidget/getwidget.dart'; class GFAlert extends StatefulWidget { /// Alert has to be wrap inside the body like [GFFloatingWidget]. See [GFFloatingWidget] const GFAlert( - {Key key, + {Key? key, + this.content, + this.title, this.child, this.backgroundColor, - this.content, this.width, this.type = GFAlertType.basic, this.alignment, this.contentChild, - this.title, this.bottombar, this.contentTextStyle = const TextStyle(color: Colors.black87), this.titleTextStyle = const TextStyle( @@ -24,45 +24,45 @@ class GFAlert extends StatefulWidget { : super(key: key); /// child of type [Widget]is alternative to title key. title will get priority over child - final Widget child; + final Widget? child; /// title of type [String] used to describe the title of the [GFAlert] - final String title; + final String? title; /// child of type [Widget]is alternative to title key. title will get priority over contentchild - final Widget contentChild; + final Widget? contentChild; /// title of type [String] used to describe the content of the [GFAlert] - final String content; + final String? content; ///type of [TextStyle] to change the style of the title not for the child final TextStyle titleTextStyle; ///pass color of type [Color] or [GFColors] for background of [GFAlert] - final Color backgroundColor; + final Color? backgroundColor; ///type of [TextStyle] to change the style of the content not for the contentchild final TextStyle contentTextStyle; /// width of type [double] used to control the width of the [GFAlert] - final double width; + final double? width; ///type of [GFAlertType] which takes the type ie, basic, rounded and fullWidth for the [GFAlert] final GFAlertType type; /// type of [Alignment] used to align the text inside the [GFAlert] - final Alignment alignment; + final Alignment? alignment; ///type of [Widget] used for the buttons ie, OK, Cancel for the action in [GFAlert] - final Widget bottombar; + final Widget? bottombar; @override _GFAlertState createState() => _GFAlertState(); } class _GFAlertState extends State with TickerProviderStateMixin { - AnimationController animationController; - Animation animation; + late AnimationController animationController; + late Animation animation; @override void initState() { @@ -115,7 +115,7 @@ class _GFAlertState extends State with TickerProviderStateMixin { Align( alignment: widget.alignment ?? Alignment.topLeft, child: widget.title != null - ? Text(widget.title, style: widget.titleTextStyle) + ? Text(widget.title!, style: widget.titleTextStyle) : (widget.child ?? Container()), ), const SizedBox( @@ -124,7 +124,7 @@ class _GFAlertState extends State with TickerProviderStateMixin { Align( alignment: widget.alignment ?? Alignment.topLeft, child: widget.content != null - ? Text(widget.content, style: widget.contentTextStyle) + ? Text(widget.content!, style: widget.contentTextStyle) : (widget.contentChild ?? Container()), ), const SizedBox( diff --git a/lib/components/animation/gf_animation.dart b/lib/components/animation/gf_animation.dart index 9c6c07ab..edd907da 100644 --- a/lib/components/animation/gf_animation.dart +++ b/lib/components/animation/gf_animation.dart @@ -4,10 +4,10 @@ import 'package:getwidget/types/gf_animation_type.dart'; class GFAnimation extends StatefulWidget { /// Wrap any widget with [GFAnimation] to animate your widget. see [GFAnimationType] for types of animations. const GFAnimation({ - Key key, + Key? key, + required this.child, this.duration, this.alignment, - this.child, this.curve, this.type, this.width, @@ -36,89 +36,89 @@ class GFAnimation extends StatefulWidget { }) : super(key: key); /// The duration for animation to perform - final Duration duration; + final Duration? duration; /// The duration for reverse animation to perform - final Duration reverseDuration; + final Duration? reverseDuration; /// Defines how the animated widget is aligned during Animation. - final Alignment alignment; + final Alignment? alignment; /// Defines how the animated widget is aligned(after the onTap) during Animation. - final Alignment activeAlignment; + final Alignment? activeAlignment; /// The child of type [Widget] to display animation effect. final Widget child; /// Determines the animation curve. Defaults to [Curves.linear]. - final Curve curve; + final Curve? curve; ///type of [GFAnimation] which takes the type ie, align, size, container, /// rotateTransition, scaleTransition, slideTransition, and textStyle for the [GFAnimation] - final GFAnimationType type; + final GFAnimationType? type; /// defines [AnimatedContainer] initial width - final double width; + final double? width; /// defines the width of [AnimatedContainer] upto which it can expand during animation - final double changedWidth; + final double? changedWidth; /// defines [AnimatedContainer] initial height - final double height; + final double? height; /// defines the height of [AnimatedContainer] upto which it can expand during animation - final double changedHeight; + final double? changedHeight; /// defines the color of [AnimatedContainer] when onTap triggers - final Color activeColor; + final Color? activeColor; /// defines the color of [AnimatedContainer] - final Color color; + final Color? color; /// defines [child]'s or [AnimatedContainer] padding - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; /// defines [child]'s or [AnimatedContainer] margin - final EdgeInsetsGeometry margin; + final EdgeInsetsGeometry? margin; /// Called when the user taps the [child] - final Function onTap; + final void Function()? onTap; /// For GFAnimationType.rotateTransition, customized turns animation can be added to [RotationTransition] widget - final Animation turnsAnimation; + final Animation? turnsAnimation; /// For GFAnimationType.scaleTransition, customized scale animation can be added to [ScaleTransition] widget - final Animation scaleAnimation; + final Animation? scaleAnimation; /// Type of [AnimationController], its a controller of an animation. - final AnimationController controller; + final AnimationController? controller; /// Defines direction of the [AnimatedDefaultTextStyle] TextDirection i.e [ltr,rtl] - final TextDirection textDirection; + final TextDirection? textDirection; /// For GFAnimationType.slideTransition, which animates the position of a widget. - final Animation slidePosition; + final Animation? slidePosition; /// Defines the [TextStyle] of [AnimatedDefaultTextStyle] - final TextStyle style; + final TextStyle? style; /// Defines the [TextAlign] of [AnimatedDefaultTextStyle] - final TextAlign textAlign; + final TextAlign? textAlign; /// Defines the [TextOverflow] of [AnimatedDefaultTextStyle] - final TextOverflow textOverflow; + final TextOverflow? textOverflow; /// Defines the [maxLines] of [AnimatedDefaultTextStyle] - final int maxLines; + final int? maxLines; /// Defines the [TextWidthBasis] of [AnimatedDefaultTextStyle] - final TextWidthBasis textWidthBasis; + final TextWidthBasis? textWidthBasis; /// Defines the [fontSize] of [AnimatedDefaultTextStyle] - final double fontSize; + final double? fontSize; /// Defines the [fontWeight] of [AnimatedDefaultTextStyle] - final FontWeight fontWeight; + final FontWeight? fontWeight; @override _GFAnimationState createState() => _GFAnimationState(); @@ -127,9 +127,9 @@ class GFAnimation extends StatefulWidget { class _GFAnimationState extends State with SingleTickerProviderStateMixin { bool selected = false; - AnimationController controller; - Animation animation; - Animation offsetAnimation; + AnimationController? controller; + Animation? animation; + Animation? offsetAnimation; @override void initState() { @@ -139,9 +139,9 @@ class _GFAnimationState extends State duration: widget.duration ?? const Duration(seconds: 2), vsync: this); animation = widget.turnsAnimation ?? - Tween(begin: 0, end: 20).animate(controller); + Tween(begin: 0, end: 20).animate(controller!); if (widget.turnsAnimation == null) { - controller.forward(); + controller?.forward(); } } else if (widget.type == GFAnimationType.scaleTransition) { controller = widget.controller ?? @@ -150,8 +150,8 @@ class _GFAnimationState extends State vsync: this); animation = widget.scaleAnimation ?? CurvedAnimation( - parent: controller, curve: widget.curve ?? Curves.ease); - controller.forward(); + parent: controller!, curve: widget.curve ?? Curves.ease); + controller?.forward(); } else if (widget.type == GFAnimationType.slideTransition) { controller = AnimationController( duration: widget.duration ?? const Duration(seconds: 2), vsync: this) @@ -160,18 +160,18 @@ class _GFAnimationState extends State begin: Offset.zero, end: const Offset(1.5, 0), ).animate(CurvedAnimation( - parent: controller, + parent: controller!, curve: Curves.linear, )); } super.initState(); } - @override - void dispose() { - controller.dispose(); - super.dispose(); - } + // @override + // void dispose() { + // controller.dispose(); + // super.dispose(); + // } @override Widget build(BuildContext context) => getAnimatedTypeWidget(); @@ -212,7 +212,7 @@ class _GFAnimationState extends State }); } } else { - widget.onTap(); + widget.onTap!(); } }, child: Container( @@ -238,7 +238,7 @@ class _GFAnimationState extends State }); } } else { - widget.onTap(); + widget.onTap!(); } }, child: AnimatedSize( @@ -258,22 +258,35 @@ class _GFAnimationState extends State ), ); - Widget buildRotationTransitionWidget() => RotationTransition( - turns: animation, + Widget buildRotationTransitionWidget() { + if (animation != null && widget.alignment != null) { + return RotationTransition( + turns: animation!, child: widget.child, - alignment: widget.alignment, + alignment: widget.alignment!, ); + } + + throw Exception('neither animation nor alignment cannot be null'); + } - Widget buildScaleTransitionWidget() => ScaleTransition( + Widget buildScaleTransitionWidget() { + if (animation != null) { + return ScaleTransition( child: widget.child, - scale: animation, + scale: animation!, alignment: widget.alignment ?? Alignment.center, ); + } + + throw Exception('animation cannot be null'); + } + // TODO(krishna): Handle null here Widget buildSlideTransitionWidget() => SlideTransition( child: widget.child, textDirection: widget.textDirection ?? TextDirection.ltr, - position: widget.slidePosition ?? offsetAnimation, + position: widget.slidePosition ?? offsetAnimation!, ); Widget buildAnimatedDefaultTextStyleWidget() => GestureDetector( @@ -297,25 +310,25 @@ class _GFAnimationState extends State switch (widget.type) { case GFAnimationType.align: return buildAnimatedAlignWidget(); - break; + case GFAnimationType.container: return buildAnimatedContainerWidget(); - break; + case GFAnimationType.size: return buildAnimatedSizeWidget(); - break; + case GFAnimationType.rotateTransition: return buildRotationTransitionWidget(); - break; + case GFAnimationType.scaleTransition: return buildScaleTransitionWidget(); - break; + case GFAnimationType.slideTransition: return buildSlideTransitionWidget(); - break; + case GFAnimationType.textStyle: return buildAnimatedDefaultTextStyleWidget(); - break; + default: return buildAnimatedContainerWidget(); } diff --git a/lib/components/appbar/gf_appbar.dart b/lib/components/appbar/gf_appbar.dart index a471efba..0e7d62f2 100644 --- a/lib/components/appbar/gf_appbar.dart +++ b/lib/components/appbar/gf_appbar.dart @@ -23,7 +23,7 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// /// Typically used in the [Scaffold.appBar] property. GFAppBar({ - Key key, + Key? key, this.leading, this.automaticallyImplyLeading = true, this.title, @@ -56,19 +56,14 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { this.onTap, this.onChanged, this.onSubmitted, - }) : assert(automaticallyImplyLeading != null), - assert(elevation == null || elevation >= 0.0), - assert(primary != null), - assert(titleSpacing != null), - assert(toolbarOpacity != null), - assert(bottomOpacity != null), + }) : assert(elevation == null || elevation >= 0.0), preferredSize = Size.fromHeight( - kToolbarHeight + (bottom?.preferredSize?.height ?? 0), + kToolbarHeight + (bottom?.preferredSize.height ?? 0), ), super(key: key); /// A widget to display before the [title]. - final Widget leading; + final Widget? leading; /// Controls whether we should try to imply the leading widget if null. /// @@ -81,14 +76,14 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// /// Typically a [Text] widget containing a description of the current contents /// of the app. - final Widget title; + final Widget? title; /// Widgets to display after the [title] widget. /// /// Typically these widgets are [IconButton]s representing common operations. /// For less common operations, consider using a [PopupMenuButton] as the /// last action. - final List actions; + final List? actions; /// This widget is stacked behind the toolbar and the tab bar. It's height will /// be the same as the app bar's overall height. @@ -98,7 +93,7 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// changes the [GFAppBar]'s height when scrolled. /// /// Typically a [FlexibleSpaceBar]. See [FlexibleSpaceBar] for details. - final Widget flexibleSpace; + final Widget? flexibleSpace; /// This widget appears across the bottom of the app bar. /// @@ -108,7 +103,7 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// See also: /// /// * [PreferredSize], which can be used to give an arbitrary widget a preferred size. - final PreferredSizeWidget bottom; + final PreferredSizeWidget? bottom; /// The z-coordinate at which to place this app bar relative to its parent. /// @@ -119,34 +114,34 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// If this property is null, then [ThemeData.appBarTheme.elevation] is used, /// if that is also null, the default value is 4, the appropriate elevation /// for app bars. - final double elevation; + final double? elevation; /// The material's shape as well its shadow. /// /// A shadow is only displayed if the [elevation] is greater than /// zero. - final ShapeBorder shape; + final ShapeBorder? shape; /// The color to use for the app bar's material. Typically this should be set /// along with [brightness], [iconTheme], [textTheme]. /// /// If this property is null, then [ThemeData.appBarTheme.color] is used, /// if that is also null, then [ThemeData.primaryColor] is used. - final Color backgroundColor; + final Color? backgroundColor; /// The brightness of the app bar's material. Typically this is set along /// with [backgroundColor], [iconTheme], [textTheme]. /// /// If this property is null, then [ThemeData.appBarTheme.brightness] is used, /// if that is also null, then [ThemeData.primaryColorBrightness] is used. - final Brightness brightness; + final Brightness? brightness; /// The color, opacity, and size to use for app bar icons. Typically this /// is set along with [backgroundColor], [brightness], [textTheme]. /// /// If this property is null, then [ThemeData.appBarTheme.iconTheme] is used, /// if that is also null, then [ThemeData.primaryIconTheme] is used. - final IconThemeData iconTheme; + final IconThemeData? iconTheme; /// The color, opacity, and size to use for the icons that appear in the app /// bar's [actions]. This should only be used when the [actions] should be @@ -155,14 +150,14 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// /// If this property is null, then [ThemeData.appBarTheme.actionsIconTheme] is /// used, if that is also null, then this falls back to [iconTheme]. - final IconThemeData actionsIconTheme; + final IconThemeData? actionsIconTheme; /// The typographic styles to use for text in the app bar. Typically this is /// set along with [brightness] [backgroundColor], [iconTheme]. /// /// If this property is null, then [ThemeData.appBarTheme.textTheme] is used, /// if that is also null, then [ThemeData.primaryTextTheme] is used. - final TextTheme textTheme; + final TextTheme? textTheme; /// Whether this app bar is being displayed at the top of the screen. /// @@ -174,7 +169,7 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// Whether the title should be centered. /// /// Defaults to being adapted to the current [TargetPlatform]. - final bool centerTitle; + final bool? centerTitle; /// The spacing around [title] content on the horizontal axis. This spacing is /// applied even if there is no [leading] content or [actions]. If you want @@ -231,7 +226,7 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// runs and can validate and change ("format") the input value. /// * onEditingComplete, [onSubmitted], [onSelectionChanged]: /// which are more specialized input change notifications. - final ValueChanged onChanged; + final ValueChanged? onChanged; /// {@macro flutter.widgets.editableText.onSubmitted} /// @@ -240,12 +235,12 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// * [EditableText.onSubmitted] for an example of how to handle moving to /// the next/previous field when using [TextInputAction.next] and /// [TextInputAction.previous] for [textInputAction]. - final ValueChanged onSubmitted; + final ValueChanged? onSubmitted; /// Controls the text being edited. /// /// If null, this widget will create its own [TextEditingController]. - final TextEditingController searchController; + final TextEditingController? searchController; /// {@template flutter.material.textfield.onTap} /// Called for each distinct tap except for every second tap of a double tap. @@ -267,19 +262,18 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { /// To listen to arbitrary pointer events without competing with the /// text field's internal gesture detector, use a [Listener]. /// {@endtemplate} - final GestureTapCallback onTap; + final GestureTapCallback? onTap; bool _getEffectiveCenterTitle(ThemeData theme) { if (centerTitle != null) { - return centerTitle; + return centerTitle!; } - assert(theme.platform != null); switch (theme.platform) { case TargetPlatform.android: case TargetPlatform.fuchsia: return false; case TargetPlatform.iOS: - return actions == null || actions.length < 2; + return actions == null || actions!.length < 2; default: return false; } @@ -291,7 +285,7 @@ class GFAppBar extends StatefulWidget implements PreferredSizeWidget { class _GFAppBarState extends State { static const double _defaultElevation = 4; - Widget searchBar; + Widget? searchBar; bool showSearchBar = false; final TextEditingController _searchController = TextEditingController(); @@ -310,11 +304,9 @@ class _GFAppBarState extends State { final ThemeData theme = Theme.of(context); final AppBarTheme appBarTheme = AppBarTheme.of(context); final ScaffoldState scaffold = Scaffold.of(context); - // final ScaffoldState scaffold =Scaffold.of(context, nullOk: true); - // final ScaffoldState scaffold = Scaffold.maybeOf(context); - final ModalRoute parentRoute = ModalRoute.of(context); - final bool hasDrawer = scaffold?.hasDrawer ?? false; - final bool hasEndDrawer = scaffold?.hasEndDrawer ?? false; + final ModalRoute? parentRoute = ModalRoute.of(context); + final bool hasDrawer = scaffold.hasDrawer; + final bool hasEndDrawer = scaffold.hasEndDrawer; final bool canPop = parentRoute?.canPop ?? false; final bool useCloseButton = parentRoute is PageRoute && parentRoute.fullscreenDialog; @@ -324,10 +316,10 @@ class _GFAppBarState extends State { IconThemeData actionsIconTheme = widget.actionsIconTheme ?? appBarTheme.actionsIconTheme ?? overallIconTheme; - TextStyle centerStyle = widget.textTheme?.headline5 ?? + TextStyle? centerStyle = widget.textTheme?.headline5 ?? appBarTheme.textTheme?.headline5 ?? theme.primaryTextTheme.headline5; - TextStyle sideStyle = widget.textTheme?.bodyText1 ?? + TextStyle? sideStyle = widget.textTheme?.bodyText1 ?? appBarTheme.textTheme?.bodyText1 ?? theme.primaryTextTheme.bodyText1; @@ -338,13 +330,13 @@ class _GFAppBarState extends State { curve: Curves.fastOutSlowIn, ).transform(widget.toolbarOpacity); if (centerStyle?.color != null) { - centerStyle = centerStyle.copyWith( - color: centerStyle.color.withOpacity(opacity), + centerStyle = centerStyle!.copyWith( + color: centerStyle.color!.withOpacity(opacity), ); } if (sideStyle?.color != null) { - sideStyle = sideStyle.copyWith( - color: sideStyle.color.withOpacity(opacity), + sideStyle = sideStyle!.copyWith( + color: sideStyle.color!.withOpacity(opacity), ); } overallIconTheme = overallIconTheme.copyWith( @@ -355,7 +347,7 @@ class _GFAppBarState extends State { ); } - Widget leading = widget.leading; + Widget? leading = widget.leading; if (leading == null && widget.automaticallyImplyLeading) { if (hasDrawer) { leading = IconButton( @@ -376,9 +368,9 @@ class _GFAppBarState extends State { ); } - Widget title = widget.title; - if (title != null) { - bool namesRoute; + Widget? title = widget.title; + if (title != null && centerStyle != null) { + bool? namesRoute; switch (theme.platform) { case TargetPlatform.android: case TargetPlatform.fuchsia: @@ -401,12 +393,12 @@ class _GFAppBarState extends State { ); } - Widget actions; - if (widget.actions != null && widget.actions.isNotEmpty) { + Widget? actions; + if (widget.actions != null && widget.actions!.isNotEmpty) { actions = Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, - children: widget.actions, + children: widget.actions!, ); } else if (hasEndDrawer) { actions = IconButton( @@ -458,7 +450,7 @@ class _GFAppBarState extends State { ), onTap: widget.onTap, onChanged: widget.onChanged, - controller: _searchController ?? widget.searchController, + controller: widget.searchController ?? _searchController, onSubmitted: widget.onSubmitted, ); } @@ -493,13 +485,14 @@ class _GFAppBarState extends State { // If the toolbar is allocated less than kToolbarHeight make it // appear to scroll upwards within its shrinking container. + // TODO(krishna): Handle null Widget appBar = ClipRect( child: CustomSingleChildLayout( delegate: const _ToolbarContainerLayout(), child: IconTheme.merge( data: overallIconTheme, child: DefaultTextStyle( - style: sideStyle, + style: sideStyle!, child: toolbar, ), ), @@ -544,7 +537,7 @@ class _GFAppBarState extends State { appBar = Stack( fit: StackFit.passthrough, children: [ - widget.flexibleSpace, + widget.flexibleSpace!, appBar, ], ); @@ -606,9 +599,8 @@ class _ToolbarContainerLayout extends SingleChildLayoutDelegate { // center it within its (NavigationToolbar) parent, and allow the // parent to constrain the title's actual height. class GFAppBarTitleBar extends SingleChildRenderObjectWidget { - const GFAppBarTitleBar({Key key, @required Widget child}) - : assert(child != null), - super(key: key, child: child); + const GFAppBarTitleBar({Key? key, required Widget child}) + : super(key: key, child: child); @override RenderGFAppBarTitleBar createRenderObject(BuildContext context) => @@ -625,8 +617,8 @@ class GFAppBarTitleBar extends SingleChildRenderObjectWidget { class RenderGFAppBarTitleBar extends RenderAligningShiftedBox { RenderGFAppBarTitleBar({ - RenderBox child, - TextDirection textDirection, + RenderBox? child, + TextDirection? textDirection, }) : super( child: child, alignment: Alignment.center, @@ -637,11 +629,13 @@ class RenderGFAppBarTitleBar extends RenderAligningShiftedBox { void performLayout() { final BoxConstraints innerConstraints = constraints.copyWith(maxHeight: double.infinity); - child.layout( + child?.layout( innerConstraints, parentUsesSize: true, ); - size = constraints.constrain(child.size); + if (child != null) { + size = constraints.constrain(child!.size); + } alignChild(); } } diff --git a/lib/components/avatar/gf_avatar.dart b/lib/components/avatar/gf_avatar.dart index 320c263c..341e762d 100644 --- a/lib/components/avatar/gf_avatar.dart +++ b/lib/components/avatar/gf_avatar.dart @@ -5,7 +5,7 @@ import 'package:getwidget/getwidget.dart'; class GFAvatar extends StatelessWidget { /// Create Avatar of all types i,e, square, circle, standard with different sizes. const GFAvatar( - {Key key, + {Key? key, this.child, this.backgroundColor, this.backgroundImage, @@ -20,25 +20,25 @@ class GFAvatar extends StatelessWidget { super(key: key); /// Typically a [Text] widget. If the [CircleAvatar] is to have an image, use [backgroundImage] instead. - final Widget child; + final Widget? child; /// use [Color] or [GFColors]. The color with which to fill the circle. - final Color backgroundColor; + final Color? backgroundColor; /// use [Color] or [GFColors]. The default text color for text in the circle. - final Color foregroundColor; + final Color? foregroundColor; /// The background image of the circle. - final ImageProvider backgroundImage; + final ImageProvider? backgroundImage; /// The size of the avatar, expressed as the radius (half the diameter). - final double radius; + final double? radius; /// The minimum size of the avatar, expressed as the radius (half the diameter). - final double minRadius; + final double? minRadius; /// The maximum size of the avatar, expressed as the radius (half the diameter). - final double maxRadius; + final double? maxRadius; /// size of avatar. use [GFSize] or [double] i.e, 1.2, small, medium, large etc. final double size; @@ -48,7 +48,7 @@ class GFAvatar extends StatelessWidget { /// border radius to give extra radius for avatar square or standard shape /// Not applicable to circleshape - final BorderRadius borderRadius; + final BorderRadius? borderRadius; // /// The default max if only the min is specified. // static const double _defaultMaxRadius = double.infinity; @@ -83,17 +83,17 @@ class GFAvatar extends StatelessWidget { @override Widget build(BuildContext context) { - final Color backgroundColor = this.backgroundColor; - final Color foregroundColor = this.foregroundColor; + final Color? backgroundColor = this.backgroundColor; + final Color? foregroundColor = this.foregroundColor; assert(debugCheckHasMediaQuery(context)); final ThemeData theme = Theme.of(context); - TextStyle textStyle = theme.primaryTextTheme.subtitle1.copyWith( + TextStyle? textStyle = theme.primaryTextTheme.subtitle1?.copyWith( color: foregroundColor, ); - Color effectiveBackgroundColor = backgroundColor; + Color? effectiveBackgroundColor = backgroundColor; - if (effectiveBackgroundColor == null) { - switch (ThemeData.estimateBrightnessForColor(textStyle.color)) { + if (effectiveBackgroundColor == null && textStyle?.color != null) { + switch (ThemeData.estimateBrightnessForColor(textStyle!.color!)) { case Brightness.dark: effectiveBackgroundColor = theme.primaryColorLight; break; @@ -101,8 +101,8 @@ class GFAvatar extends StatelessWidget { effectiveBackgroundColor = theme.primaryColorDark; break; } - } else if (foregroundColor == null) { - switch (ThemeData.estimateBrightnessForColor(backgroundColor)) { + } else if (foregroundColor == null && textStyle != null) { + switch (ThemeData.estimateBrightnessForColor(backgroundColor!)) { case Brightness.dark: textStyle = textStyle.copyWith(color: theme.primaryColorLight); break; @@ -125,7 +125,7 @@ class GFAvatar extends StatelessWidget { color: effectiveBackgroundColor, image: backgroundImage != null ? DecorationImage( - image: backgroundImage, + image: backgroundImage!, fit: BoxFit.cover, ) : null, @@ -134,16 +134,16 @@ class GFAvatar extends StatelessWidget { ? BorderRadius.circular(10) : borderRadius, ), - child: child == null + child: child == null && textStyle != null ? null : Center( child: MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: 1), child: IconTheme( - data: theme.iconTheme.copyWith(color: textStyle.color), + data: theme.iconTheme.copyWith(color: textStyle?.color), child: DefaultTextStyle( - style: textStyle, - child: child, + style: textStyle!, + child: child!, ), ), ), diff --git a/lib/components/badge/gf_badge.dart b/lib/components/badge/gf_badge.dart index f55cc0a4..7c0ce823 100644 --- a/lib/components/badge/gf_badge.dart +++ b/lib/components/badge/gf_badge.dart @@ -4,7 +4,7 @@ import 'package:getwidget/getwidget.dart'; class GFBadge extends StatefulWidget { /// Create badges of all types, check out [GFButtonBadge] for button badges and [GFIconBadge] for icon type badges const GFBadge({ - Key key, + Key? key, this.textStyle, this.borderShape, this.shape = GFBadgeShape.standard, @@ -14,14 +14,13 @@ class GFBadge extends StatefulWidget { this.border, this.text, this.child, - }) : assert(shape != null, 'Counter shape can not be null'), - super(key: key); + }) : super(key: key); /// The border side for the badge's [Material]. - final BorderSide border; + final BorderSide? border; /// Typically the counter badge's shape. - final ShapeBorder borderShape; + final ShapeBorder? borderShape; /// Counter type of [GFBadgeShape] i.e, standard, pills, square, circle final GFBadgeShape shape; @@ -33,13 +32,13 @@ class GFBadge extends StatefulWidget { final double size; /// child of type [Widget] is alternative to child. text will get priority over child - final Widget child; + final Widget? child; /// text of type [String] is alternative to child. text will get priority over child - final String text; + final String? text; /// text style of counter text. - final TextStyle textStyle; + final TextStyle? textStyle; /// Pass [GFColors] or [Color] final Color textColor; @@ -49,14 +48,14 @@ class GFBadge extends StatefulWidget { } class _GFBadgeState extends State { - Color color; - Color textColor; - Widget child; - GFBadgeShape counterShape; - double size; - double height; - double width; - double fontSize; + late Color color; + late Color textColor; + Widget? child; + GFBadgeShape? counterShape; + late double size; + double? height; + double? width; + double? fontSize; @override void initState() { @@ -81,7 +80,7 @@ class _GFBadgeState extends State { @override Widget build(BuildContext context) { final BorderSide shapeBorder = widget.border != null - ? widget.border + ? widget.border! : BorderSide( color: color, width: 0, @@ -138,12 +137,10 @@ class _GFBadgeState extends State { height: height, width: counterShape == GFBadgeShape.circle ? height : width, child: Material( - textStyle: textColor != null - ? TextStyle( - color: textColor, - fontSize: fontSize, - ) - : widget.textStyle, + textStyle: TextStyle( + color: textColor, + fontSize: fontSize, + ), shape: widget.borderShape ?? shape, color: color, type: MaterialType.button, diff --git a/lib/components/badge/gf_button_badge.dart b/lib/components/badge/gf_button_badge.dart index ee3f08bc..ad321571 100644 --- a/lib/components/badge/gf_button_badge.dart +++ b/lib/components/badge/gf_button_badge.dart @@ -7,8 +7,8 @@ import 'package:getwidget/getwidget.dart'; class GFButtonBadge extends StatefulWidget { /// Create badges badges of all types. check out [GFIconButton] for icon badges, and [GFBadge] for badges const GFButtonBadge({ - Key key, - @required this.onPressed, + Key? key, + required this.onPressed, this.onHighlightChanged, this.textStyle, this.boxShadow, @@ -29,7 +29,7 @@ class GFButtonBadge extends StatefulWidget { this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, - MaterialTapTargetSize materialTapTargetSize, + MaterialTapTargetSize? materialTapTargetSize, this.type = GFButtonType.solid, this.shape = GFButtonShape.standard, this.color = GFColors.PRIMARY, @@ -48,46 +48,41 @@ class GFButtonBadge extends StatefulWidget { this.icon, }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded, - assert(shape != null, 'Button shape can not be null'), // assert(elevation != null && elevation >= 0.0), - assert(focusElevation != null && focusElevation >= 0.0), - assert(hoverElevation != null && hoverElevation >= 0.0), - assert(highlightElevation != null && highlightElevation >= 0.0), - assert(disabledElevation != null && disabledElevation >= 0.0), - assert(padding != null), - assert(animationDuration != null), - assert(clipBehavior != null), - assert(autofocus != null), + assert(focusElevation >= 0.0), + assert(hoverElevation >= 0.0), + assert(highlightElevation >= 0.0), + assert(disabledElevation >= 0.0), super( key: key, ); /// Called when the button is tapped or otherwise activated. - final VoidCallback onPressed; + final VoidCallback? onPressed; /// Called by the underlying [InkWell] widget's InkWell.onHighlightChanged callback. - final ValueChanged onHighlightChanged; + final ValueChanged? onHighlightChanged; /// Defines the default text style, with [Material.textStyle], for the button's child. - final TextStyle textStyle; + final TextStyle? textStyle; /// The border side for the badge's [Material]. - final BorderSide borderSide; + final BorderSide? borderSide; /// The box shadow for the badge's [Material]. - final BoxShadow boxShadow; + final BoxShadow? boxShadow; /// Pass [GFColors] or [Color]. The color for the badge's [Material] when it has the input focus. - final Color focusColor; + final Color? focusColor; /// Pass [GFColors] or [Color]. The color for the badge's [Material] when a pointer is hovering over it. - final Color hoverColor; + final Color? hoverColor; /// Pass [GFColors] or [Color]. The highlight color for the badge's [InkWell]. - final Color highlightColor; + final Color? highlightColor; /// Pass [GFColors] or [Color]. The splash color for the badge's [InkWell]. - final Color splashColor; + final Color? splashColor; /// The elevation for the badge's [Material] when the badge is [enabled] but not pressed. final double elevation; @@ -108,10 +103,10 @@ class GFButtonBadge extends StatefulWidget { final EdgeInsetsGeometry padding; /// Defines the badge's size. - final BoxConstraints constraints; + final BoxConstraints? constraints; /// The shape of the badge's [Material]. - final ShapeBorder borderShape; + final ShapeBorder? borderShape; /// Defines the duration of animated changes for [shape] and [elevation]. final Duration animationDuration; @@ -123,7 +118,7 @@ class GFButtonBadge extends StatefulWidget { final MaterialTapTargetSize materialTapTargetSize; /// {@macro flutter.widgets.Focus.focusNode} - final FocusNode focusNode; + final FocusNode? focusNode; /// {@macro flutter.widgets.Focus.autofocus} final bool autofocus; @@ -148,10 +143,10 @@ class GFButtonBadge extends StatefulWidget { /// See also: /// /// * [color] - the fill color of the badge when the badge is [enabled]. - final Color disabledColor; + final Color? disabledColor; /// Pass [GFColors] or [Color] - final Color textColor; + final Color? textColor; /// The color to use for this badge's text when the badge is disabled. /// @@ -167,25 +162,25 @@ class GFButtonBadge extends StatefulWidget { /// See also: /// /// * [textColor] - The color to use for this badge's text when the badge is [enabled]. - final Color disabledTextColor; + final Color? disabledTextColor; /// size of [double] or [GFSize] i.e, 1.2, small, medium, large etc. final double size; /// text of type [String] is alternative to child. text will get priority over child - final String text; + final String? text; /// icon type of [GFPosition] i.e, start, end final GFPosition position; /// on true state blockButton gives block size badge - final bool blockButton; + final bool? blockButton; /// on true state full width Button gives full width badge - final bool fullWidthButton; + final bool? fullWidthButton; /// on true state default box shadow appears around badge, if GFButtonType is solid - final bool badgeBoxShadow; + final bool? badgeBoxShadow; /// A set of thirteen colors that can be used to derive the badge theme's /// colors. @@ -197,7 +192,7 @@ class GFButtonBadge extends StatefulWidget { /// The colors for new badge classes can be defined exclusively in terms /// of [colorScheme]. When it's possible, the existing badges will /// (continue to) gradually migrate to it. - final ColorScheme colorScheme; + final ColorScheme? colorScheme; /// Whether detected gestures should provide acoustic and/or haptic feedback. /// @@ -207,7 +202,7 @@ class GFButtonBadge extends StatefulWidget { /// See also: /// /// * [Feedback] for providing platform-specific feedback to certain actions. - final bool enableFeedback; + final bool? enableFeedback; /// Called when the badge is long-pressed. /// @@ -216,10 +211,10 @@ class GFButtonBadge extends StatefulWidget { /// See also: /// /// * [enabled], which is true if the badge is enabled. - final VoidCallback onLongPress; + final VoidCallback? onLongPress; /// Can be used to display GFBadge, [Icons] inside badge design - final Widget icon; + final Widget? icon; @override _GFButtonBadgeState createState() => _GFButtonBadgeState(); diff --git a/lib/components/badge/gf_icon_badge.dart b/lib/components/badge/gf_icon_badge.dart index 0045b2dc..65e30daf 100644 --- a/lib/components/badge/gf_icon_badge.dart +++ b/lib/components/badge/gf_icon_badge.dart @@ -4,12 +4,11 @@ import 'package:getwidget/getwidget.dart'; class GFIconBadge extends StatefulWidget { /// Create badges of all types, check out [GFBadge] for button badges and [GFIconBadge] for icon badges. const GFIconBadge({ - Key key, + Key? key, this.padding = const EdgeInsets.symmetric(horizontal: 8), - @required this.child, - @required this.counterChild, - }) : assert(padding != null), - super(key: key); + required this.child, + required this.counterChild, + }) : super(key: key); /// child of type [Widget] is used to show icon. /// Use [GFIconButton] widget for compatibility. @@ -32,9 +31,9 @@ class _GFIconBadgeState extends State { padding: widget.padding, child: Stack( children: [ - widget.child ?? Container(), + widget.child, Positioned( - child: widget.counterChild ?? Container(), + child: widget.counterChild, ), ], ), diff --git a/lib/components/border/gf_border.dart b/lib/components/border/gf_border.dart index 4057d5ed..d630d6b2 100644 --- a/lib/components/border/gf_border.dart +++ b/lib/components/border/gf_border.dart @@ -7,7 +7,7 @@ import 'package:getwidget/types/gf_border_type.dart'; class GFBorder extends StatelessWidget { /// Create different types of borders around given child widget GFBorder({ - @required this.child, + required this.child, this.color = Colors.black, this.strokeWidth = 1, this.type = GFBorderType.rect, @@ -15,8 +15,7 @@ class GFBorder extends StatelessWidget { this.padding = const EdgeInsets.all(10), this.radius = const Radius.circular(0), // this.customPath, - }) : assert(child != null), - assert(_isValidDashedLine(dashedLine), 'Invalid dash pattern'); + }) : assert(_isValidDashedLine(dashedLine), 'Invalid dash pattern'); /// child of type [Widget] which can be any component or text, etc final Widget child; @@ -67,9 +66,7 @@ class GFBorder extends StatelessWidget { /// the value of dashedLine cannot be 0 or null, it should have some definite and proper value bool _isValidDashedLine(List dash) { final Set _dashSet = dash.toSet(); - if (_dashSet == null) { - return false; - } + if (_dashSet.length == 1 && _dashSet.elementAt(0) == 0.0) { return false; } diff --git a/lib/components/border/gf_dashed_border.dart b/lib/components/border/gf_dashed_border.dart index ae321ead..e4741a68 100644 --- a/lib/components/border/gf_dashed_border.dart +++ b/lib/components/border/gf_dashed_border.dart @@ -30,7 +30,7 @@ class DashedType extends CustomPainter { final Radius radius; /// customPath of type [PathBuilder] used for the drawing the paths - final PathBuilder customPath; + final PathBuilder? customPath; @override void paint(Canvas canvas, Size size) { @@ -39,21 +39,22 @@ class DashedType extends CustomPainter { ..color = color ..style = PaintingStyle.stroke; - Path _path; + Path? _path; if (customPath != null) { _path = dashPath( - customPath(size), + customPath!(size), dashedarray: CircularIntervalList(dashedLine), ); } else { _path = _getPath(size); } - - canvas.drawPath(_path, paint); + if (_path != null) { + canvas.drawPath(_path, paint); + } } /// Returns a [Path] based on the the borderType parameter - Path _getPath(Size size) { + Path? _getPath(Size size) { Path path; switch (type) { case GFBorderType.circle: @@ -148,10 +149,9 @@ class CircularIntervalList { } } -Path dashPath(Path source, - {@required CircularIntervalList dashedarray, +Path? dashPath(Path? source, + {required CircularIntervalList dashedarray, DashOffset dashOffset = const DashOffset.absolute(0)}) { - assert(dashedarray != null); if (source == null) { return null; } @@ -177,12 +177,12 @@ enum _DashOffsetType { absolute, percentage } class DashOffset { ///gives offset of the dashed path that will be measured as a percentage which ranges from 0.0 to 1.0 DashOffset.percentage(double percentage) - : _value = percentage.clamp(0.0, 1.0) ?? 0.0, + : _value = percentage.clamp(0.0, 1.0), _dashOffsetType = _DashOffsetType.percentage; ///gives offset of the dashed path that will be measured as a absolute value const DashOffset.absolute(double start) - : _value = start ?? 0.0, + : _value = start, _dashOffsetType = _DashOffsetType.absolute; final double _value; diff --git a/lib/components/bottom_sheet/gf_bottom_sheet.dart b/lib/components/bottom_sheet/gf_bottom_sheet.dart index 0772f64d..5a680592 100644 --- a/lib/components/bottom_sheet/gf_bottom_sheet.dart +++ b/lib/components/bottom_sheet/gf_bottom_sheet.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; class GFBottomSheet extends StatefulWidget { /// Opens sheet from bottom as a popup on current sheets it has different customization options. GFBottomSheet({ - Key key, - @required this.contentBody, + Key? key, + required this.contentBody, this.stickyHeader, this.stickyFooter, - this.controller, + required this.controller, this.minContentHeight = 0, this.maxContentHeight = 300, this.elevation = 0.0, @@ -34,7 +34,7 @@ class GFBottomSheet extends StatefulWidget { /// [stickyHeader] is the header of GFBottomSheet. /// User can interact by swiping or tapping the [stickyHeader] - final Widget stickyHeader; + final Widget? stickyHeader; /// [contentBody] is the body of GFBottomSheet. /// User can interact by swiping or tapping the [contentBody] @@ -42,7 +42,7 @@ class GFBottomSheet extends StatefulWidget { /// [stickyFooter] is the footer of GFBottomSheet. /// User can interact by swiping or tapping the [stickyFooter] - final Widget stickyFooter; + final Widget? stickyFooter; /// [stickyFooterHeight] defines the height of GFBottomSheet's [stickyFooter]. final double stickyFooterHeight; @@ -71,33 +71,36 @@ class GFBottomSheet extends StatefulWidget { class _GFBottomSheetState extends State with TickerProviderStateMixin { - final StreamController _streamController = + final StreamController _streamController = StreamController.broadcast(); - bool isDragDirectionUp; + bool? isDragDirectionUp; bool showBottomSheet = false; - Function _controllerListener; - double position; + late void Function() _controllerListener; + late double position; bool showContent = false; void _onVerticalDragUpdate(data) { _setAnimationDuration(); - if (((widget.controller.height - data.delta.dy) > + if (widget.controller.height != null && + ((widget.controller.height! - data.delta.dy) > widget.minContentHeight) && - ((widget.controller.height - data.delta.dy) < + ((widget.controller.height! - data.delta.dy) < widget.maxContentHeight)) { isDragDirectionUp = data.delta.dy <= 0; - widget.controller.height -= data.delta.dy; + widget.controller.height = widget.controller.height! - data.delta.dy; } } void _onVerticalDragEnd(data) { _setAnimationDuration(); - if (isDragDirectionUp && widget.controller.value) { - _showBottomSheet(); - } else if (!isDragDirectionUp && !widget.controller.value) { - _hideBottomSheet(); - } else { - widget.controller.value = isDragDirectionUp; + if (isDragDirectionUp != null) { + if (isDragDirectionUp! && widget.controller.value) { + _showBottomSheet(); + } else if (!isDragDirectionUp! && !widget.controller.value) { + _hideBottomSheet(); + } else { + widget.controller.value = isDragDirectionUp!; + } } } @@ -123,7 +126,7 @@ class _GFBottomSheetState extends State final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme; final double elevation = - widget.elevation ?? bottomSheetTheme.elevation ?? 0; + widget.elevation == 0.0 ? bottomSheetTheme.elevation ?? 0.0 : 0.0; final Widget bottomSheet = Column( mainAxisSize: MainAxisSize.min, children: [ @@ -141,7 +144,7 @@ class _GFBottomSheetState extends State !widget.enableExpandableContent ? AnimatedBuilder( animation: widget.controller, - builder: (_, Widget child) => AnimatedContainer( + builder: (_, Widget? child) => AnimatedContainer( curve: Curves.easeOut, duration: Duration( milliseconds: widget.controller.animationDuration), @@ -163,20 +166,23 @@ class _GFBottomSheetState extends State curve: Curves.easeOut, duration: Duration( milliseconds: widget.controller.animationDuration), - height: snapshot.hasData == null + height: !snapshot.hasData ? widget.minContentHeight : snapshot.data, child: GestureDetector( onVerticalDragUpdate: (DragUpdateDetails details) { - if (((widget.controller.height - details.delta.dy) > + if (widget.controller.height != null && + ((widget.controller.height! - details.delta.dy) > widget.minContentHeight) && - ((widget.controller.height - details.delta.dy) < + ((widget.controller.height! - details.delta.dy) < (MediaQuery.of(context).size.height * 0.8 - widget.stickyFooterHeight - widget.stickyHeaderHeight))) { isDragDirectionUp = details.delta.dy <= 0; - widget.controller.height -= details.delta.dy; + widget.controller.height = + widget.controller.height! - details.delta.dy; } + _streamController.add(widget.controller.height); }, onVerticalDragEnd: _onVerticalDragEnd, @@ -190,7 +196,7 @@ class _GFBottomSheetState extends State ? Container() : AnimatedBuilder( animation: widget.controller, - builder: (_, Widget child) => AnimatedContainer( + builder: (_, Widget? child) => AnimatedContainer( curve: Curves.easeOut, duration: Duration( milliseconds: widget.controller.animationDuration), @@ -242,17 +248,17 @@ class GFBottomSheetController extends ValueNotifier { GFBottomSheetController() : super(false); /// Defines the height of the GFBottomSheet's contentBody - double _height; + double? _height; /// Defines the drag animation duration of the GFBottomSheet - int animationDuration; + late int animationDuration; // ignore: unnecessary_getters_setters - set height(double value) => _height = value; + set height(double? value) => _height = value; // ignore: unnecessary_getters_setters - double get height => _height; - bool get isBottomSheetOpened => value; + double? get height => _height; + bool? get isBottomSheetOpened => value; void hideBottomSheet() => value = false; void showBottomSheet() => value = true; } diff --git a/lib/components/button/gf_button.dart b/lib/components/button/gf_button.dart index df810c6d..e080fc64 100644 --- a/lib/components/button/gf_button.dart +++ b/lib/components/button/gf_button.dart @@ -9,8 +9,8 @@ import 'package:getwidget/getwidget.dart'; class GFButton extends StatefulWidget { /// Create buttons of all types. check out [GFIconButton] for icon buttons, and [GFBadge] for badges const GFButton({ - Key key, - @required this.onPressed, + Key? key, + required this.onPressed, this.onHighlightChanged, this.textStyle, this.boxShadow, @@ -31,7 +31,7 @@ class GFButton extends StatefulWidget { this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, - MaterialTapTargetSize materialTapTargetSize, + MaterialTapTargetSize? materialTapTargetSize, this.child, this.type = GFButtonType.solid, this.shape = GFButtonShape.standard, @@ -51,44 +51,39 @@ class GFButton extends StatefulWidget { this.disabledTextColor, }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded, - assert(shape != null, 'Button shape can not be null'), // assert(elevation != null && elevation >= 0.0), - assert(focusElevation != null && focusElevation >= 0.0), - assert(hoverElevation != null && hoverElevation >= 0.0), - assert(highlightElevation != null && highlightElevation >= 0.0), - assert(disabledElevation != null && disabledElevation >= 0.0), - assert(padding != null), - assert(animationDuration != null), - assert(clipBehavior != null), - assert(autofocus != null), + assert(focusElevation >= 0.0), + assert(hoverElevation >= 0.0), + assert(highlightElevation >= 0.0), + assert(disabledElevation >= 0.0), super(key: key); /// Called when the button is tapped or otherwise activated. - final VoidCallback onPressed; + final VoidCallback? onPressed; /// Called by the underlying [InkWell] widget's InkWell.onHighlightChanged callback. - final ValueChanged onHighlightChanged; + final ValueChanged? onHighlightChanged; /// Defines the default text style, with [Material.textStyle], for the button's [child]. - final TextStyle textStyle; + final TextStyle? textStyle; /// The border side for the button's [Material]. - final BorderSide borderSide; + final BorderSide? borderSide; /// The box shadow for the button's [Material], if GFButtonType is solid - final BoxShadow boxShadow; + final BoxShadow? boxShadow; /// Pass [GFColors] or [Color]. The color for the button's [Material] when it has the input focus. - final Color focusColor; + final Color? focusColor; /// Pass [GFColors] or [Color]. The color for the button's [Material] when a pointer is hovering over it. - final Color hoverColor; + final Color? hoverColor; /// Pass [GFColors] or [Color]. The highlight color for the button's [InkWell]. - final Color highlightColor; + final Color? highlightColor; /// Pass [GFColors] or [Color]. The splash color for the button's [InkWell]. - final Color splashColor; + final Color? splashColor; /// The elevation for the button's [Material] when the button is [enabled] but not pressed. final double elevation; @@ -109,16 +104,16 @@ class GFButton extends StatefulWidget { final EdgeInsetsGeometry padding; /// Defines the button's size. - final BoxConstraints constraints; + final BoxConstraints? constraints; /// The shape of the button's [Material]. - final ShapeBorder borderShape; + final ShapeBorder? borderShape; /// Defines the duration of animated changes for [shape] and [elevation]. final Duration animationDuration; /// Typically the button's label. - final Widget child; + final Widget? child; /// Whether the button is enabled or disabled. bool get enabled => onPressed != null; @@ -127,7 +122,7 @@ class GFButton extends StatefulWidget { final MaterialTapTargetSize materialTapTargetSize; /// {@macro flutter.widgets.Focus.focusNode} - final FocusNode focusNode; + final FocusNode? focusNode; /// {@macro flutter.widgets.Focus.autofocus} final bool autofocus; @@ -152,10 +147,10 @@ class GFButton extends StatefulWidget { /// See also: /// /// * [color] - the fill color of the button when the button is [enabled]. - final Color disabledColor; + final Color? disabledColor; /// Pass [GFColors] or [Color] - final Color textColor; + final Color? textColor; /// The color to use for this button's text when the button is disabled. /// @@ -171,28 +166,28 @@ class GFButton extends StatefulWidget { /// See also: /// /// * [textColor] - The color to use for this button's text when the button is [enabled]. - final Color disabledTextColor; + final Color? disabledTextColor; /// size of [double] or [GFSize] i.e, 1.2, small, medium, large etc. final double size; /// text of type [String] is alternative to child. text will get priority over child - final String text; + final String? text; /// icon of type [Widget] - final Widget icon; + final Widget? icon; /// icon type of [GFPosition] i.e, start, end final GFPosition position; /// on true state blockButton gives block size button - final bool blockButton; + final bool? blockButton; /// on true state full width Button gives full width button - final bool fullWidthButton; + final bool? fullWidthButton; /// on true state default box shadow appears around button, if GFButtonType is solid - final bool buttonBoxShadow; + final bool? buttonBoxShadow; /// A set of thirteen colors that can be used to derive the button theme's /// colors. @@ -204,7 +199,7 @@ class GFButton extends StatefulWidget { /// The colors for new button classes can be defined exclusively in terms /// of [colorScheme]. When it's possible, the existing buttons will /// (continue to) gradually migrate to it. - final ColorScheme colorScheme; + final ColorScheme? colorScheme; /// Whether detected gestures should provide acoustic and/or haptic feedback. /// @@ -214,7 +209,7 @@ class GFButton extends StatefulWidget { /// See also: /// /// * [Feedback] for providing platform-specific feedback to certain actions. - final bool enableFeedback; + final bool? enableFeedback; /// Called when the button is long-pressed. /// @@ -223,25 +218,25 @@ class GFButton extends StatefulWidget { /// See also: /// /// * [enabled], which is true if the button is enabled. - final VoidCallback onLongPress; + final VoidCallback? onLongPress; @override _GFButtonState createState() => _GFButtonState(); } class _GFButtonState extends State { - Color color; - Color disabledColor; - Color textColor; - Color disabledTextColor; - Widget child; - Widget icon; - Function onPressed; - GFButtonType type; - GFButtonShape shape; - double size; - GFPosition position; - BoxShadow boxShadow; + late Color color; + Color? textColor; + Color? disabledColor; + Color? disabledTextColor; + Widget? child; + Widget? icon; + Function? onPressed; + late GFButtonType type; + late GFButtonShape shape; + late double size; + late GFPosition position; + late BoxShadow boxShadow; final Set _states = {}; @@ -249,7 +244,7 @@ class _GFButtonState extends State { void initState() { color = widget.color; textColor = widget.textColor; - child = widget.text != null ? Text(widget.text) : widget.child; + child = widget.text != null ? Text(widget.text!) : widget.child; icon = widget.icon; onPressed = widget.onPressed; type = widget.type; @@ -270,8 +265,8 @@ class _GFButtonState extends State { bool get _pressed => _states.contains(MaterialState.pressed); bool get _disabled => _states.contains(MaterialState.disabled); - double buttonWidth() { - double buttonWidth = 0; + double? buttonWidth() { + double? buttonWidth = 0; if (widget.blockButton == true) { buttonWidth = MediaQuery.of(context).size.width * 0.88; } else if (widget.fullWidthButton == true) { @@ -291,7 +286,7 @@ class _GFButtonState extends State { setState(() { _updateState(MaterialState.pressed, value); if (widget.onHighlightChanged != null) { - widget.onHighlightChanged(value); + widget.onHighlightChanged!(value); } }); } @@ -325,7 +320,7 @@ class _GFButtonState extends State { } color = widget.color; textColor = widget.textColor; - child = widget.text != null ? Text(widget.text) : widget.child; + child = widget.text != null ? Text(widget.text!) : widget.child; icon = widget.icon; onPressed = widget.onPressed; type = widget.type; @@ -365,18 +360,15 @@ class _GFButtonState extends State { Color getBorderColor() { if (widget.enabled) { - final Color fillColor = color ?? GFColors.PRIMARY; - if (fillColor != null) { - return fillColor; - } + final Color fillColor = color; + return fillColor; } else { if (disabledColor != null) { - return disabledColor; + return disabledColor!; } else { return color.withOpacity(0.48); } } - return color ?? GFColors.PRIMARY; } Color getDisabledFillColor() { @@ -386,7 +378,7 @@ class _GFButtonState extends State { return Colors.transparent; } if (disabledColor != null) { - return disabledColor; + return disabledColor!; } else { return color.withOpacity(0.48); } @@ -398,13 +390,13 @@ class _GFButtonState extends State { widget.type == GFButtonType.outline2x) { return Colors.transparent; } - final Color fillColor = color ?? GFColors.PRIMARY; + final Color fillColor = color; return fillColor; } Color getDisabledTextColor() { if (disabledTextColor != null) { - return disabledTextColor; + return disabledTextColor!; } else if (widget.type == GFButtonType.outline || widget.type == GFButtonType.outline2x || widget.type == GFButtonType.transparent) { @@ -423,7 +415,7 @@ class _GFButtonState extends State { ? color == GFColors.TRANSPARENT ? GFColors.DARK : color - : textColor + : textColor! : getDisabledTextColor(); } if (textColor == null) { @@ -433,25 +425,23 @@ class _GFButtonState extends State { return GFColors.WHITE; } } else { - return textColor; + return textColor!; } } - final Color effectiveTextColor = MaterialStateProperty.resolveAs( + final Color? effectiveTextColor = MaterialStateProperty.resolveAs( widget.textStyle?.color, _states); final Color themeColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.12); final BorderSide outlineBorder = BorderSide( - color: color == null - ? themeColor - : widget.borderSide == null - ? getBorderColor() - : widget.borderSide.color, - width: widget.borderSide?.width == null + color: widget.borderSide == null + ? getBorderColor() + : widget.borderSide!.color, + width: (widget.borderSide?.width == null ? widget.type == GFButtonType.outline2x ? 2.0 : 1.0 - : widget.borderSide?.width, + : widget.borderSide?.width)!, ); Size minSize; @@ -472,7 +462,7 @@ class _GFButtonState extends State { ? outlineBorder : widget.borderSide ?? BorderSide( - color: color == null ? themeColor : getBorderColor(), + color: getBorderColor(), width: 0, ); @@ -500,7 +490,7 @@ class _GFButtonState extends State { ); } - BoxDecoration getBoxShadow() { + BoxDecoration? getBoxShadow() { if (widget.type != GFButtonType.transparent) { if (widget.boxShadow == null && widget.buttonBoxShadow != true) { return null; @@ -525,7 +515,7 @@ class _GFButtonState extends State { offset: Offset.zero, ) : widget.boxShadow != null - ? widget.boxShadow + ? widget.boxShadow! : BoxShadow( color: Theme.of(context).canvasColor, blurRadius: 0, @@ -577,7 +567,7 @@ class _GFButtonState extends State { ? null : widget.borderShape ?? shapeBorderType, color: widget.enabled ? getColor() : getDisabledFillColor(), - type: color == null ? MaterialType.transparency : MaterialType.button, + type: MaterialType.button, animationDuration: widget.animationDuration, clipBehavior: widget.clipBehavior, child: InkWell( @@ -607,22 +597,25 @@ class _GFButtonState extends State { widthFactor: 1, heightFactor: 1, child: icon != null && - (position == GFPosition.start || position == null) + child != null && + (position == GFPosition.start) ? Row( mainAxisSize: MainAxisSize.min, children: [ - icon, + icon!, const SizedBox(width: 8), - child + child! ], ) - : icon != null && (position == GFPosition.end) + : icon != null && + child != null && + (position == GFPosition.end) ? Row( mainAxisSize: MainAxisSize.min, children: [ - child, + child!, const SizedBox(width: 8), - icon + icon! ], ) : child, @@ -657,15 +650,15 @@ class _GFButtonState extends State { /// "tap target", but not its material or its ink splashes. class _InputPadding extends SingleChildRenderObjectWidget { const _InputPadding({ - Key key, - Widget child, + Key? key, + Widget? child, this.minSize, }) : super( key: key, child: child, ); - final Size minSize; + final Size? minSize; @override RenderObject createRenderObject(BuildContext context) => @@ -679,78 +672,86 @@ class _InputPadding extends SingleChildRenderObjectWidget { } class _RenderInputPadding extends RenderShiftedBox { - _RenderInputPadding(this._minSize, [RenderBox child]) : super(child); + _RenderInputPadding(this._minSize, [RenderBox? child]) : super(child); - Size get minSize => _minSize; - Size _minSize; + Size? get minSize => _minSize; + Size? _minSize; - set minSize(Size value) { + set minSize(Size? value) { _minSize = value; markNeedsLayout(); } @override double computeMinIntrinsicWidth(double height) { - if (child != null) { - return math.max(child.getMinIntrinsicWidth(height), minSize.width); + if (child != null && minSize != null) { + return math.max(child!.getMinIntrinsicWidth(height), minSize!.width); } return 0; } @override double computeMinIntrinsicHeight(double width) { - if (child != null) { - return math.max(child.getMinIntrinsicHeight(width), minSize.height); + if (child != null && minSize != null) { + return math.max(child!.getMinIntrinsicHeight(width), minSize!.height); } return 0; } @override double computeMaxIntrinsicWidth(double height) { - if (child != null) { - return math.max(child.getMaxIntrinsicWidth(height), minSize.width); + if (child != null && minSize != null) { + return math.max(child!.getMaxIntrinsicWidth(height), minSize!.width); } return 0; } @override double computeMaxIntrinsicHeight(double width) { - if (child != null) { - return math.max(child.getMaxIntrinsicHeight(width), minSize.height); + if (child != null && minSize != null) { + return math.max(child!.getMaxIntrinsicHeight(width), minSize!.height); } return 0; } @override void performLayout() { - if (child != null) { - child.layout(constraints, parentUsesSize: true); - final BoxParentData childParentData = child.parentData; - final double height = math.max(child.size.width, minSize.width); - final double width = math.max(child.size.height, minSize.height); + if (child != null && minSize != null) { + child!.layout(constraints, parentUsesSize: true); + // ignore: avoid_as + final BoxParentData childParentData = child!.parentData as BoxParentData; + final double height = math.max(child!.size.width, minSize!.width); + final double width = math.max(child!.size.height, minSize!.height); size = constraints.constrain(Size(height, width)); - childParentData.offset = Alignment.center.alongOffset(size - child.size); + childParentData.offset = + // ignore: avoid_as + Alignment.center.alongOffset(size - child!.size as Offset); } else { size = Size.zero; } } @override - bool hitTest(BoxHitTestResult result, {Offset position}) { + bool hitTest(BoxHitTestResult result, {required Offset position}) { if (super.hitTest(result, position: position)) { return true; } - final Offset center = child.size.center(Offset.zero); - return result.addWithRawTransform( - transform: MatrixUtils.forceToPoint(center), - position: center, - hitTest: (BoxHitTestResult result, Offset position) { - assert(position == center); - return child.hitTest( - result, - position: center, - ); - }, - ); + + if (child != null) { + final Offset center = child!.size.center(Offset.zero); + return result.addWithRawTransform( + transform: MatrixUtils.forceToPoint(center), + position: center, + hitTest: (BoxHitTestResult result, Offset position) { + assert(position == center); + return child!.hitTest( + result, + position: center, + ); + }, + ); + } + + throw Exception('child property cannot be null'); } } diff --git a/lib/components/button/gf_button_bar.dart b/lib/components/button/gf_button_bar.dart index 3b579d8f..364bb828 100644 --- a/lib/components/button/gf_button_bar.dart +++ b/lib/components/button/gf_button_bar.dart @@ -6,7 +6,7 @@ class GFButtonBar extends StatelessWidget { /// Create buttons bar for all types of buttons. /// check [GFButton] and [GFIconButton] const GFButtonBar({ - Key key, + Key? key, this.direction = Axis.horizontal, this.alignment = WrapAlignment.center, this.spacing = 8.0, @@ -15,7 +15,7 @@ class GFButtonBar extends StatelessWidget { this.crossAxisAlignment = WrapCrossAlignment.start, this.textDirection, this.verticalDirection = VerticalDirection.down, - this.children = const [], + required this.children, this.padding = const EdgeInsets.all(0), }) : super(key: key); @@ -85,6 +85,10 @@ class GFButtonBar extends StatelessWidget { /// Defaults to 0.0. final double runSpacing; + /// The buttons to arrange horizontally. + /// Typically [GFButton] or [GFIconButton] widgets. + final List children; + /// How the children within a run should be aligned relative to each other in /// the cross axis. /// @@ -128,7 +132,7 @@ class GFButtonBar extends StatelessWidget { /// [crossAxisAlignment] is either [WrapCrossAlignment.start] or /// [WrapCrossAlignment.end], or there's more than one child, then the /// [textDirection] (or the ambient [Directionality]) must not be null. - final TextDirection textDirection; + final TextDirection? textDirection; /// Determines the order to lay children out vertically and how to interpret /// `start` and `end` in the vertical direction. @@ -154,10 +158,6 @@ class GFButtonBar extends StatelessWidget { /// [verticalDirection] must not be null. final VerticalDirection verticalDirection; - /// The buttons to arrange horizontally. - /// Typically [RaisedButton] or [GFButton] or [GFIconButton] widgets. - final List children; - @override Widget build(BuildContext context) => Padding( padding: padding, diff --git a/lib/components/button/gf_icon_button.dart b/lib/components/button/gf_icon_button.dart index a228c390..561dda0f 100644 --- a/lib/components/button/gf_icon_button.dart +++ b/lib/components/button/gf_icon_button.dart @@ -7,17 +7,17 @@ import 'package:getwidget/getwidget.dart'; class GFIconButton extends StatefulWidget { /// Create icon buttons of all types. check out [GFButton] for buttons const GFIconButton({ - Key key, + Key? key, this.iconSize = 0.0, this.padding = const EdgeInsets.all(8), this.alignment = Alignment.center, - @required this.icon, + required this.icon, this.focusColor, this.hoverColor, this.highlightColor, this.splashColor, this.disabledColor, - @required this.onPressed, + required this.onPressed, this.focusNode, this.autofocus = false, this.tooltip, @@ -29,12 +29,7 @@ class GFIconButton extends StatefulWidget { this.size = GFSize.MEDIUM, this.buttonBoxShadow, this.borderSide, - }) : assert(iconSize != null), - assert(padding != null), - assert(alignment != null), - assert(autofocus != null), - assert(icon != null), - super(key: key); + }) : super(key: key); /// The size of the icon inside the button. final double iconSize; @@ -49,10 +44,10 @@ class GFIconButton extends StatefulWidget { final Widget icon; /// The color for the button's icon when it has the input focus. - final Color focusColor; + final Color? focusColor; /// The color for the button's icon when a pointer is hovering over it. - final Color hoverColor; + final Color? hoverColor; /// Button type of [GFButtonType] i.e, solid, outline, outline2x transparent final GFButtonType type; @@ -64,53 +59,53 @@ class GFIconButton extends StatefulWidget { final Color color; /// Pass [GFColors] or [Color]. The primary color of the button when the button is in the down (pressed) state. - final Color splashColor; + final Color? splashColor; /// Pass [GFColors] or [Color]. The secondary color of the button when the button is in the down (pressed) state. - final Color highlightColor; + final Color? highlightColor; /// Pass [GFColors] or [Color]. The color to use for the icon inside the button, if the icon is disabled. - final Color disabledColor; + final Color? disabledColor; /// The callback that is called when the button is tapped or otherwise activated. - final VoidCallback onPressed; + final VoidCallback? onPressed; /// {@macro flutter.widgets.Focus.focusNode} - final FocusNode focusNode; + final FocusNode? focusNode; /// {@macro flutter.widgets.Focus.autofocus} final bool autofocus; /// Text that describes the action that will occur when the button is pressed. - final String tooltip; + final String? tooltip; /// The box shadow for the button's [Material], if GFButtonType is solid - final BoxShadow boxShadow; + final BoxShadow? boxShadow; /// The shape and border for the button's [Material]. - final ShapeBorder borderShape; + final ShapeBorder? borderShape; /// size of [double] or [GFSize] i.e, 1.2, small, medium, large etc. final double size; /// on true state default box shadow appears around button, if GFButtonType is solid - final bool buttonBoxShadow; + final bool? buttonBoxShadow; /// The border side for the button's [Material]. - final BorderSide borderSide; + final BorderSide? borderSide; @override _GFIconButtonState createState() => _GFIconButtonState(); } class _GFIconButtonState extends State { - Color color; - Function onPressed; - GFButtonType type; - GFIconButtonShape shape; - BoxShadow boxShadow; - double height; - double width; + late Color color; + Function? onPressed; + late GFButtonType type; + late GFIconButtonShape shape; + BoxShadow? boxShadow; + double? height; + double? width; double iconPixel = 18; @override @@ -127,14 +122,14 @@ class _GFIconButtonState extends State { return color; } else { if (widget.disabledColor != null) { - return widget.disabledColor; + return widget.disabledColor!; } else { return color.withOpacity(0.48); } } } - Color getDisabledFillColor() { + Color? getDisabledFillColor() { if (widget.type == GFButtonType.transparent || widget.type == GFButtonType.outline || widget.type == GFButtonType.outline2x) { @@ -147,7 +142,7 @@ class _GFIconButtonState extends State { } } - Color getColor() { + Color? getColor() { if (widget.type == GFButtonType.transparent || widget.type == GFButtonType.outline || widget.type == GFButtonType.outline2x) { @@ -157,7 +152,7 @@ class _GFIconButtonState extends State { } } - Color getIconColor() { + Color? getIconColor() { if (widget.type == GFButtonType.transparent || widget.type == GFButtonType.outline || widget.type == GFButtonType.outline2x) { @@ -182,12 +177,12 @@ class _GFIconButtonState extends State { final BorderSide outlineBorder = BorderSide( color: widget.borderSide == null ? getBorderColor() - : widget.borderSide.color, - width: widget.borderSide?.width == null + : widget.borderSide!.color, + width: (widget.borderSide?.width == null ? widget.type == GFButtonType.outline2x ? 2.0 : 1.0 - : widget.borderSide?.width, + : widget.borderSide?.width)!, ); final BorderSide shapeBorder = widget.type == GFButtonType.outline || @@ -199,7 +194,7 @@ class _GFIconButtonState extends State { width: 0, ); - ShapeBorder shapeBorderType; + ShapeBorder? shapeBorderType; if (shape == GFIconButtonShape.pills) { shapeBorderType = RoundedRectangleBorder( @@ -263,12 +258,12 @@ class _GFIconButtonState extends State { if (widget.tooltip != null) { result = Tooltip( - message: widget.tooltip, + message: widget.tooltip!, child: result, ); } - BoxDecoration getBoxShadow() { + BoxDecoration? getBoxShadow() { if (widget.type != GFButtonType.transparent) { if (widget.boxShadow == null && widget.buttonBoxShadow != true) { return null; diff --git a/lib/components/button/gf_social_button.dart b/lib/components/button/gf_social_button.dart index 54249394..f4c0382f 100644 --- a/lib/components/button/gf_social_button.dart +++ b/lib/components/button/gf_social_button.dart @@ -7,8 +7,8 @@ import 'package:getwidget/getwidget.dart'; class GFSocialButton extends GFButton { /// Create buttons of all types. check out [GFIconButton] for icon buttons, and [GFBadge] for badges const GFSocialButton({ - Key key, - @required this.onPressed, + Key? key, + required this.onPressed, this.onHighlightChanged, this.textStyle, this.boxShadow, @@ -29,7 +29,7 @@ class GFSocialButton extends GFButton { this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, - MaterialTapTargetSize materialTapTargetSize, + MaterialTapTargetSize? materialTapTargetSize, this.child, this.type = GFButtonType.solid, this.shape = GFButtonShape.standard, @@ -49,16 +49,11 @@ class GFSocialButton extends GFButton { this.disabledTextColor, }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded, - assert(shape != null, 'Button shape can not be null'), // assert(elevation != null && elevation >= 0.0), - assert(focusElevation != null && focusElevation >= 0.0), - assert(hoverElevation != null && hoverElevation >= 0.0), - assert(highlightElevation != null && highlightElevation >= 0.0), - assert(disabledElevation != null && disabledElevation >= 0.0), - assert(padding != null), - assert(animationDuration != null), - assert(clipBehavior != null), - assert(autofocus != null), + assert(focusElevation >= 0.0), + assert(hoverElevation >= 0.0), + assert(highlightElevation >= 0.0), + assert(disabledElevation >= 0.0), super( key: key, onPressed: onPressed, @@ -66,39 +61,39 @@ class GFSocialButton extends GFButton { /// Called when the button is tapped or otherwise activated. @override - final VoidCallback onPressed; + final VoidCallback? onPressed; /// Called by the underlying [InkWell] widget's InkWell.onHighlightChanged callback. @override - final ValueChanged onHighlightChanged; + final ValueChanged? onHighlightChanged; /// Defines the default text style, with [Material.textStyle], for the button's [child]. @override - final TextStyle textStyle; + final TextStyle? textStyle; /// The border side for the button's [Material]. @override - final BorderSide borderSide; + final BorderSide? borderSide; /// The box shadow for the button's [Material], if GFButtonType is solid @override - final BoxShadow boxShadow; + final BoxShadow? boxShadow; /// Pass [GFColors] or [Color]. The color for the button's [Material] when it has the input focus. @override - final Color focusColor; + final Color? focusColor; /// Pass [GFColors] or [Color]. The color for the button's [Material] when a pointer is hovering over it. @override - final Color hoverColor; + final Color? hoverColor; /// Pass [GFColors] or [Color]. The highlight color for the button's [InkWell]. @override - final Color highlightColor; + final Color? highlightColor; /// Pass [GFColors] or [Color]. The splash color for the button's [InkWell]. @override - final Color splashColor; + final Color? splashColor; /// The elevation for the button's [Material] when the button is [enabled] but not pressed. @override @@ -126,11 +121,11 @@ class GFSocialButton extends GFButton { /// Defines the button's size. @override - final BoxConstraints constraints; + final BoxConstraints? constraints; /// The shape of the button's [Material]. @override - final ShapeBorder borderShape; + final ShapeBorder? borderShape; /// Defines the duration of animated changes for [shape] and [elevation]. @override @@ -138,7 +133,7 @@ class GFSocialButton extends GFButton { /// Typically the button's label. @override - final Widget child; + final Widget? child; /// Whether the button is enabled or disabled. @override @@ -150,7 +145,7 @@ class GFSocialButton extends GFButton { /// {@macro flutter.widgets.Focus.focusNode} @override - final FocusNode focusNode; + final FocusNode? focusNode; /// {@macro flutter.widgets.Focus.autofocus} @override @@ -182,11 +177,11 @@ class GFSocialButton extends GFButton { /// * [color] - the fill color of the button when the button is [enabled]. @override - final Color disabledColor; + final Color? disabledColor; /// Pass [GFColors] or [Color] @override - final Color textColor; + final Color? textColor; /// The color to use for this button's text when the button is disabled. /// @@ -203,7 +198,7 @@ class GFSocialButton extends GFButton { /// /// * [textColor] - The color to use for this button's text when the button is [enabled]. @override - final Color disabledTextColor; + final Color? disabledTextColor; /// size of [double] or [GFSize] i.e, 1.2, small, medium, large etc. @override @@ -211,11 +206,11 @@ class GFSocialButton extends GFButton { /// text of type [String] is alternative to child. text will get priority over child @override - final String text; + final String? text; /// icon of type [Widget] @override - final Widget icon; + final Widget? icon; /// icon type of [GFPosition] i.e, start, end @override @@ -223,15 +218,15 @@ class GFSocialButton extends GFButton { /// on true state blockButton gives block size button @override - final bool blockButton; + final bool? blockButton; /// on true state full width Button gives full width button @override - final bool fullWidthButton; + final bool? fullWidthButton; /// on true state default box shadow appears around button, if GFButtonType is solid @override - final bool buttonBoxShadow; + final bool? buttonBoxShadow; /// A set of thirteen colors that can be used to derive the button theme's /// colors. @@ -244,7 +239,7 @@ class GFSocialButton extends GFButton { /// of [colorScheme]. When it's possible, the existing buttons will /// (continue to) gradually migrate to it. @override - final ColorScheme colorScheme; + final ColorScheme? colorScheme; /// Whether detected gestures should provide acoustic and/or haptic feedback. /// @@ -255,7 +250,7 @@ class GFSocialButton extends GFButton { /// /// * [Feedback] for providing platform-specific feedback to certain actions. @override - final bool enableFeedback; + final bool? enableFeedback; /// Called when the button is long-pressed. /// @@ -265,7 +260,7 @@ class GFSocialButton extends GFButton { /// /// * [enabled], which is true if the button is enabled. @override - final VoidCallback onLongPress; + final VoidCallback? onLongPress; Widget build(BuildContext context) => GFButton( onPressed: onPressed, diff --git a/lib/components/card/gf_card.dart b/lib/components/card/gf_card.dart index dfadce1c..5390e152 100644 --- a/lib/components/card/gf_card.dart +++ b/lib/components/card/gf_card.dart @@ -13,7 +13,7 @@ class GFCard extends StatelessWidget { /// The [elevation] must be null or non-negative. The [borderOnForeground] /// must not be null. const GFCard( - {Key key, + {Key? key, this.color, this.elevation, this.shape, @@ -25,6 +25,8 @@ class GFCard extends StatelessWidget { this.title, this.content, this.image, + this.showImage = false, + this.showOverlayImage = false, this.buttonBar, this.imageOverlay, this.titlePosition, @@ -35,7 +37,6 @@ class GFCard extends StatelessWidget { this.height, this.gradient}) : assert(elevation == null || elevation >= 0.0), - assert(borderOnForeground != null), assert( color == null || gradient == null, 'Cannot provide both a color and a decoration\n' @@ -43,69 +44,73 @@ class GFCard extends StatelessWidget { super(key: key); /// defines the card's height - final double height; + final double? height; /// [GFPosition] titlePosition helps to set titlebar at top of card - final GFPosition titlePosition; + final GFPosition? titlePosition; /// The card's background color. - final Color color; + final Color? color; /// The z-coordinate at which to place this card. This controls the size of the shadow below the card. - final double elevation; + final double? elevation; /// The shape of the card's [Material]. - final ShapeBorder shape; + final ShapeBorder? shape; /// Whether to paint the [shape] border in front of the child. final bool borderOnForeground; /// If this property is null then [ThemeData.cardTheme.clipBehavior] is used. - final Clip clipBehavior; + final Clip? clipBehavior; /// The empty space that surrounds the card. Defines the card's outer [Container.margin]. - final EdgeInsetsGeometry margin; + final EdgeInsetsGeometry? margin; /// The empty space that surrounds the card. Defines the card's outer [Container.padding].. final EdgeInsetsGeometry padding; /// Whether this widget represents a single semantic container, or if false /// a collection of individual semantic nodes. - final bool semanticContainer; + final bool? semanticContainer; /// The title to display inside the GFTitleBar. see GFTitleBar - final GFListTile title; + final GFListTile? title; /// widget can be used to define content - final Widget content; + final Widget? content; + + final bool showImage; + + final bool showOverlayImage; /// image widget can be used - final Image image; + final Image? image; /// overlay image [GFImageOverlay] widget can be used /// to display image with shaded overlay - final ImageProvider imageOverlay; + final ImageProvider? imageOverlay; /// widget can be used to define buttons bar, see [GFButtonBar] - final GFButtonBar buttonBar; + final GFButtonBar? buttonBar; /// How the image should be inscribed into the box. /// The default is [BoxFit.scaleDown] if centerSlice is null, and /// [BoxFit.fill] if centerSlice is not null. /// [boxFit] for only [GFImageOverlay] - final BoxFit boxFit; + final BoxFit? boxFit; /// A color filter to apply to the image before painting it. - final ColorFilter colorFilter; + final ColorFilter? colorFilter; /// The corners of this [GFCard] are rounded by this [BorderRadius]. - final BorderRadiusGeometry borderRadius; + final BorderRadiusGeometry? borderRadius; /// A border to draw above the [GFCard]. - final Border border; + final Border? border; /// defines the gradient background - final LinearGradient gradient; + final LinearGradient? gradient; static const double _defaultElevation = 1; static const Clip _defaultClipBehavior = Clip.none; @@ -120,15 +125,18 @@ class GFCard extends StatelessWidget { children: [ titlePosition == GFPosition.start ? title ?? Container() - : image != null + : showImage != false ? ClipRRect( - borderRadius: borderRadius ?? + // ignore: avoid_as + borderRadius: borderRadius as BorderRadius? ?? const BorderRadius.vertical(top: Radius.circular(4)), child: image, ) : Container(), titlePosition == GFPosition.start - ? image ?? Container() + ? showImage != false + ? Container(child: image) + : Container() : title ?? Container(), Padding( padding: padding, @@ -143,7 +151,7 @@ class GFCard extends StatelessWidget { width: MediaQuery.of(context).size.width, child: cardChild, color: color ?? cardTheme.color ?? Theme.of(context).cardColor, - image: imageOverlay, + image: imageOverlay!, boxFit: boxFit, colorFilter: colorFilter, border: border, @@ -174,9 +182,9 @@ class GFCard extends StatelessWidget { clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? _defaultClipBehavior, - child: imageOverlay == null ? cardChild : overlayImage, + child: showOverlayImage == false ? cardChild : overlayImage, ) - : imageOverlay == null + : showOverlayImage == false ? cardChild : overlayImage, ); diff --git a/lib/components/carousel/gf_carousel.dart b/lib/components/carousel/gf_carousel.dart index fcf8173a..f11b8af5 100644 --- a/lib/components/carousel/gf_carousel.dart +++ b/lib/components/carousel/gf_carousel.dart @@ -4,7 +4,8 @@ import 'package:flutter/material.dart'; class GFCarousel extends StatefulWidget { /// Creates slide show of Images and [Widget] with animation for sliding. const GFCarousel({ - @required this.items, + Key? key, + required this.items, this.pagerSize, this.passiveIndicator, this.activeIndicator, @@ -24,25 +25,25 @@ class GFCarousel extends StatefulWidget { this.onPageChanged, this.scrollPhysics, this.scrollDirection = Axis.horizontal, - }); + }) : super(key: key); /// The pagination dots size can be defined using [double]. - final double pagerSize; + final double? pagerSize; /// The slider pagination's active color. - final Color activeIndicator; + final Color? activeIndicator; /// The slider pagination's passive color. - final Color passiveIndicator; + final Color? passiveIndicator; /// The [GFCarousel] shows pagination on state true. - final bool pagination; + final bool? pagination; /// The widgets to be shown as sliders. final List items; /// Set slide widget height and overrides any existing [aspectRatio]. - final double height; + final double? height; /// Aspect ratio is used if no height have been declared. Defaults to 16:9 aspect ratio. final double aspectRatio; @@ -72,7 +73,7 @@ class GFCarousel extends StatefulWidget { final Curve autoPlayCurve; /// Sets a timer on touch detected that pause the auto play with the given [Duration]. Touch Detection is only active if [autoPlay] is true. - final Duration pauseAutoPlayOnTouch; + final Duration? pauseAutoPlayOnTouch; /// Determines if current page should be larger then the side images, /// creating a feeling of depth in the carousel. Defaults to false. @@ -83,7 +84,7 @@ class GFCarousel extends StatefulWidget { final Axis scrollDirection; /// Called whenever the page in the center of the viewport changes. - final Function(int index) onPageChanged; + final Function(int index)? onPageChanged; /// How the carousel should respond to user input. /// @@ -94,10 +95,10 @@ class GFCarousel extends StatefulWidget { /// [PageScrollPhysics] prior to being used. /// /// Defaults to matching platform conventions. - final ScrollPhysics scrollPhysics; + final ScrollPhysics? scrollPhysics; - List map(List list, Function handler) { - List result; + List map(List list, Function handler) { + List result; result = []; for (var i = 0; i < list.length; i++) { result.add(handler(i, list[i])); @@ -110,7 +111,7 @@ class GFCarousel extends StatefulWidget { } class _GFCarouselState extends State with TickerProviderStateMixin { - Timer timer; + Timer? timer; /// Size of cell double size = 0; @@ -120,7 +121,7 @@ class _GFCarouselState extends State with TickerProviderStateMixin { /// [pageController] is created using the properties passed to the constructor /// and can be used to control the [PageView] it is passed to. - PageController pageController; + late PageController pageController; /// The actual index of the [PageView]. int realPage = 10000; @@ -129,13 +130,18 @@ class _GFCarouselState extends State with TickerProviderStateMixin { void initState() { super.initState(); realPage = widget.enableInfiniteScroll - ? realPage + widget.initialPage - : widget.initialPage; + // ignore: avoid_as + ? realPage + (widget.initialPage as int) + // ignore: avoid_as + : widget.initialPage as int; pageController = PageController( - viewportFraction: widget.viewportFraction, + // ignore: avoid_as + viewportFraction: widget.viewportFraction as double, initialPage: widget.enableInfiniteScroll - ? realPage + widget.initialPage - : widget.initialPage, + // ignore: avoid_as + ? realPage + (widget.initialPage as int) + // ignore: avoid_as + : widget.initialPage as int, ); timer = getPlayTimer(); } @@ -149,8 +155,8 @@ class _GFCarouselState extends State with TickerProviderStateMixin { }); void pauseOnTouch() { - timer.cancel(); - timer = Timer(widget.pauseAutoPlayOnTouch, () { + timer?.cancel(); + timer = Timer(widget.pauseAutoPlayOnTouch!, () { timer = getPlayTimer(); }); } @@ -183,7 +189,7 @@ class _GFCarouselState extends State with TickerProviderStateMixin { setState(() => currentSlide = index); } - int currentSlide; + int? currentSlide; @override Widget build(BuildContext context) => Stack( @@ -200,10 +206,11 @@ class _GFCarouselState extends State with TickerProviderStateMixin { : widget.items.length, onPageChanged: (int index) { int currentPage; - currentPage = _getRealIndex( - index + widget.initialPage, realPage, widget.items.length); + // ignore: avoid_as + currentPage = _getRealIndex(index + (widget.initialPage as int), + realPage, widget.items.length); if (widget.onPageChanged != null) { - widget.onPageChanged(currentPage); + widget.onPageChanged!(currentPage); } if (widget.pagination == true && widget.onPageChanged == null) { onPageSlide(currentPage); @@ -211,7 +218,8 @@ class _GFCarouselState extends State with TickerProviderStateMixin { }, itemBuilder: (BuildContext context, int i) { final int index = _getRealIndex( - i + widget.initialPage, + // ignore: avoid_as + i + (widget.initialPage as int), realPage, widget.items.length, ); @@ -223,12 +231,12 @@ class _GFCarouselState extends State with TickerProviderStateMixin { builder: (BuildContext context, child) { double value; try { - value = pageController.page - i; + value = pageController.page! - i; // ignore: avoid_catches_without_on_clauses } catch (e) { final BuildContext storageContext = pageController.position.context.storageContext; - final double previousSavedPosition = + final double? previousSavedPosition = PageStorage.of(storageContext) ?.readState(storageContext); if (previousSavedPosition != null) { @@ -288,13 +296,14 @@ class _GFCarouselState extends State with TickerProviderStateMixin { color: currentSlide == pagerIndex ? widget.activeIndicator == null ? const Color.fromRGBO(0, 0, 0, 0.9) - : widget.activeIndicator + : widget.activeIndicator! : widget.passiveIndicator == null ? const Color.fromRGBO(0, 0, 0, 0.4) - : widget.passiveIndicator, + : widget.passiveIndicator!, ), ), - ), + // ignore: avoid_as + ) as List, ), ), ) diff --git a/lib/components/carousel/gf_items_carousel.dart b/lib/components/carousel/gf_items_carousel.dart index f65efce4..c9a9e919 100644 --- a/lib/components/carousel/gf_items_carousel.dart +++ b/lib/components/carousel/gf_items_carousel.dart @@ -26,9 +26,9 @@ class GFItemsCarousel extends StatefulWidget { /// Creates slide show of Images and [Widget] with animation for sliding. /// Shows multiple items on one slide, items number depends on rowCount. const GFItemsCarousel({ - Key key, - this.rowCount, - this.children, + Key? key, + required this.rowCount, + required this.children, this.onSlideStart, this.onSlide, this.onSlideEnd, @@ -42,15 +42,15 @@ class GFItemsCarousel extends StatefulWidget { final List children; /// When a pointer has contacted the screen and has begun to move. - final GFItemsCarouselSlideStartCallback onSlideStart; + final GFItemsCarouselSlideStartCallback? onSlideStart; /// When a pointer that is in contact with the screen and moving /// has moved again. - final GFItemsCarouselSlideCallback onSlide; + final GFItemsCarouselSlideCallback? onSlide; /// When a pointer that was previously in contact with the screen /// and moving is no longer in contact with the screen. - final GFItemsCarouselSlideEndCallback onSlideEnd; + final GFItemsCarouselSlideEndCallback? onSlideEnd; /// defines the height of items final double itemHeight; @@ -73,10 +73,10 @@ class _GFItemsCarouselState extends State /// Width of cells container double width = 0; - AnimationController animationController; + late AnimationController animationController; /// Shift of cells container - double offset; + late double offset; @override void initState() { @@ -110,7 +110,7 @@ class _GFItemsCarouselState extends State void onSlideStart(DragStartDetails details) { animationController.stop(); if (widget.onSlideStart != null) { - widget.onSlideStart(details); + widget.onSlideStart!(details); } } @@ -119,7 +119,7 @@ class _GFItemsCarouselState extends State offset = calculateOffset(3 * details.delta.dx); }); if (widget.onSlide != null) { - widget.onSlide(details); + widget.onSlide!(details); } } @@ -154,7 +154,7 @@ class _GFItemsCarouselState extends State animationController.forward(); if (widget.onSlideEnd != null) { - widget.onSlideEnd(details); + widget.onSlideEnd!(details); } } diff --git a/lib/components/checkbox/gf_checkbox.dart b/lib/components/checkbox/gf_checkbox.dart index e1188619..2470f3a8 100644 --- a/lib/components/checkbox/gf_checkbox.dart +++ b/lib/components/checkbox/gf_checkbox.dart @@ -4,15 +4,15 @@ import 'package:getwidget/getwidget.dart'; class GFCheckbox extends StatefulWidget { /// [GFCheckbox] is a small box (as in a checklist) in which to place a check mark to make a selection with various customization options. const GFCheckbox( - {Key key, + {Key? key, this.size = GFSize.MEDIUM, this.type = GFCheckboxType.basic, this.activeBgColor = GFColors.PRIMARY, this.inactiveBgColor = GFColors.WHITE, this.activeBorderColor = GFColors.WHITE, this.inactiveBorderColor = GFColors.DARK, - this.onChanged, - this.value, + required this.onChanged, + required this.value, this.activeIcon = const Icon( Icons.check, size: 20, @@ -22,8 +22,7 @@ class GFCheckbox extends StatefulWidget { this.customBgColor = GFColors.SUCCESS, this.autofocus = false, this.focusNode}) - : assert(autofocus != null), - super(key: key); + : super(key: key); /// type of [GFCheckboxType] which is of four type is basic, square, circular and custom final GFCheckboxType type; @@ -44,7 +43,7 @@ class GFCheckbox extends StatefulWidget { final Color inactiveBorderColor; /// Called when the user checks or unchecks the checkbox. - final ValueChanged onChanged; + final ValueChanged? onChanged; /// Used to set the current state of the checkbox final bool value; @@ -53,7 +52,7 @@ class GFCheckbox extends StatefulWidget { final Widget activeIcon; /// type of [Widget] used to change the checkbox's inactive icon - final Widget inactiveIcon; + final Widget? inactiveIcon; /// type of [Color] used to change the background color of the custom active checkbox only final Color customBgColor; @@ -63,7 +62,7 @@ class GFCheckbox extends StatefulWidget { final bool autofocus; /// an optional focus node to use as the focus node for this widget. - final FocusNode focusNode; + final FocusNode? focusNode; @override _GFCheckboxState createState() => _GFCheckboxState(); @@ -86,7 +85,7 @@ class _GFCheckboxState extends State { canRequestFocus: enabled, onTap: widget.onChanged != null ? () { - widget.onChanged(!widget.value); + widget.onChanged!(!widget.value); } : null, child: Container( diff --git a/lib/components/checkbox_list_tile/gf_checkbox_list_tile.dart b/lib/components/checkbox_list_tile/gf_checkbox_list_tile.dart index ba31a401..2d9924e3 100644 --- a/lib/components/checkbox_list_tile/gf_checkbox_list_tile.dart +++ b/lib/components/checkbox_list_tile/gf_checkbox_list_tile.dart @@ -5,15 +5,15 @@ class GFCheckboxListTile extends StatelessWidget { /// [GFCheckboxListTile] is a tile with small box at right (as in a checklist) /// in which to place a check mark to make a selection with various customization options. const GFCheckboxListTile({ - Key key, - @required this.value, - @required this.onChanged, + Key? key, + required this.value, + required this.onChanged, this.titleText, - this.subtitleText, + this.subTitleText, this.color, this.avatar, this.title, - this.subtitle, + this.subTitle, this.icon, this.description, this.padding = const EdgeInsets.all(8), @@ -34,32 +34,29 @@ class GFCheckboxListTile extends StatelessWidget { this.position = GFPosition.end, this.selected = false, this.autofocus = false, - }) : assert(value != null), - assert(selected != null), - assert(autofocus != null), - super(key: key); + }) : super(key: key); ///type of [String] used to pass text, alternative to title property and gets higher priority than title - final String titleText; + final String? titleText; - ///type of [String] used to pass text, alternative to subtitle property and gets higher priority than subtitle - final String subtitleText; + ///type of [String] used to pass text, alternative to subTitle property and gets higher priority than subTitle + final String? subTitleText; /// The GFListTile's background color. Can be given [Color] or [GFColors] - final Color color; + final Color? color; /// If position is start Checkbox will come instead of avatar, /// type of [Widget] or [GFAvatar] used to create rounded user profile - final Widget avatar; + final Widget? avatar; /// The title to display inside the [GFListTile]. see [Text] - final Widget title; + final Widget? title; - /// The subtitle to display inside the [GFListTile]. see [Text] - final Widget subtitle; + /// The subTitle to display inside the [GFListTile]. see [Text] + final Widget? subTitle; /// The description to display inside the [GFListTile]. see [Text] - final Widget description; + final Widget? description; /// defines the margin of GFListTile final EdgeInsets margin; @@ -86,7 +83,7 @@ class GFCheckboxListTile extends StatelessWidget { final Color inactiveBorderColor; /// Called when the user checks or unchecks the checkbox. - final ValueChanged onChanged; + final ValueChanged? onChanged; /// Used to set the current state of the checkbox final bool value; @@ -98,7 +95,7 @@ class GFCheckboxListTile extends StatelessWidget { final Widget activeIcon; /// type of [Widget] used to change the checkbox's inactive icon - final Widget inactiveIcon; + final Widget? inactiveIcon; /// type of [Color] used to change the background color of the custom active checkbox only final Color customBgColor; @@ -112,7 +109,7 @@ class GFCheckboxListTile extends StatelessWidget { final bool autofocus; /// If position is end Checkbox will come instead of icon, The icon to display inside the [GFListTile]. see [Icon] - final Widget icon; + final Widget? icon; @override Widget build(BuildContext context) { @@ -136,14 +133,14 @@ class GFCheckboxListTile extends StatelessWidget { enabled: onChanged != null, onTap: onChanged != null ? () { - onChanged(!value); + onChanged!(!value); } : null, selected: selected, avatar: position == GFPosition.start ? checkbox : avatar, titleText: titleText, - subtitle: subtitle, - subtitleText: subtitleText, + subTitle: subTitle, + subTitleText: subTitleText, description: description, color: color, padding: padding, diff --git a/lib/components/drawer/gf_drawer.dart b/lib/components/drawer/gf_drawer.dart index 76226e3f..1b385462 100644 --- a/lib/components/drawer/gf_drawer.dart +++ b/lib/components/drawer/gf_drawer.dart @@ -56,7 +56,7 @@ class GFDrawer extends StatelessWidget { /// Typically used in the [Scaffold.drawer] property. /// The [elevation] must be non-negative. const GFDrawer({ - Key key, + Key? key, this.elevation = 16.0, this.child, this.semanticLabel, @@ -64,7 +64,7 @@ class GFDrawer extends StatelessWidget { this.colorFilter, this.gradient, this.color, - }) : assert(elevation != null && elevation >= 0.0), + }) : assert(elevation >= 0.0), super(key: key); /// The z-coordinate at which to place this drawer relative to its parent. @@ -80,7 +80,7 @@ class GFDrawer extends StatelessWidget { /// Typically a [SliverList]. /// /// {@macro flutter.widgets.child} - final Widget child; + final Widget? child; /// The semantic label of the dialog used by accessibility frameworks to /// announce screen transitions when the drawer is opened and closed. @@ -92,32 +92,32 @@ class GFDrawer extends StatelessWidget { /// /// * SemanticsConfiguration.namesRoute, for a description of how this /// value is used. - final String semanticLabel; + final String? semanticLabel; /// Background image can be added to the [GFDrawer]. - final ImageProvider backgroundImage; + final ImageProvider? backgroundImage; /// A composited layer that applies a color filter to its children. - final ColorFilter colorFilter; + final ColorFilter? colorFilter; /// Gradient can be used for transforming gradient shaders without /// applying the same transform to the entire canvas. - final Gradient gradient; + final Gradient? gradient; /// Defines the background color of the drawer - final Color color; + final Color? color; @override Widget build(BuildContext context) { assert(debugCheckHasMaterialLocalizations(context)); - String label = semanticLabel; + String? label = semanticLabel; switch (Theme.of(context).platform) { case TargetPlatform.iOS: label = semanticLabel; break; case TargetPlatform.android: case TargetPlatform.fuchsia: - label = semanticLabel ?? MaterialLocalizations.of(context)?.drawerLabel; + label = semanticLabel ?? MaterialLocalizations.of(context).drawerLabel; break; default: break; @@ -137,7 +137,7 @@ class GFDrawer extends StatelessWidget { gradient: gradient, image: backgroundImage != null ? DecorationImage( - image: backgroundImage, + image: backgroundImage!, fit: BoxFit.cover, colorFilter: colorFilter, ) diff --git a/lib/components/drawer/gf_drawer_header.dart b/lib/components/drawer/gf_drawer_header.dart index 3f8f3d39..3869f739 100644 --- a/lib/components/drawer/gf_drawer_header.dart +++ b/lib/components/drawer/gf_drawer_header.dart @@ -5,7 +5,7 @@ import 'package:getwidget/getwidget.dart'; class GFDrawerHeaderPictures extends StatelessWidget { const GFDrawerHeaderPictures({ - Key key, + Key? key, this.currentAccountPicture, this.otherAccountsPictures, this.closeButton, @@ -13,15 +13,15 @@ class GFDrawerHeaderPictures extends StatelessWidget { /// A widget placed in the upper-left corner that represents the current /// user's account. Normally a [CircleAvatar]. - final Widget currentAccountPicture; + final Widget? currentAccountPicture; /// A list of widgets that represent the current user's other accounts. /// Up to three of these widgets will be arranged in a row in the header's /// upper-right corner. Normally a list of [CircleAvatar] widgets. - final List otherAccountsPictures; + final List? otherAccountsPictures; /// widget onTap drawer get closed - final Widget closeButton; + final Widget? closeButton; @override Widget build(BuildContext context) => Stack( @@ -37,7 +37,7 @@ class GFDrawerHeaderPictures extends StatelessWidget { }, type: GFButtonType.transparent, ) - : closeButton, + : closeButton!, PositionedDirectional( top: 0, end: 0, @@ -81,7 +81,7 @@ class GFDrawerHeader extends StatefulWidget { /// /// Requires one of its ancestors to be a [Material] widget. const GFDrawerHeader({ - Key key, + Key? key, this.decoration, this.margin = const EdgeInsets.only(bottom: 8), this.currentAccountPicture, @@ -94,19 +94,19 @@ class GFDrawerHeader extends StatefulWidget { /// The header's background. If decoration is null then a [BoxDecoration] /// with its background color set to the current theme's primaryColor is used. - final Decoration decoration; + final Decoration? decoration; /// The margin around the drawer header. final EdgeInsetsGeometry margin; /// A widget placed in the upper-left corner that represents the current /// user's account. Normally a [CircleAvatar]. - final Widget currentAccountPicture; + final Widget? currentAccountPicture; /// A list of widgets that represent the current user's other accounts. /// Up to three of these widgets will be arranged in a row in the header's /// upper-right corner. Normally a list of [CircleAvatar] widgets. - final List otherAccountsPictures; + final List? otherAccountsPictures; /// A widget to be placed inside the drawer header, inset by the padding. /// @@ -114,7 +114,7 @@ class GFDrawerHeader extends StatefulWidget { /// precisely, consider using an [Align] or [Center] widget. /// /// {@macro flutter.widgets.child} - final Widget child; + final Widget? child; /// The duration for animations of the [decoration]. final Duration duration; @@ -123,7 +123,7 @@ class GFDrawerHeader extends StatefulWidget { final Curve curve; /// widget onTap drawer get closed - final Widget closeButton; + final Widget? closeButton; @override _GFDrawerHeaderState createState() => _GFDrawerHeaderState(); diff --git a/lib/components/dropdown/gf_dropdown.dart b/lib/components/dropdown/gf_dropdown.dart index 8d8caa90..adfa4843 100644 --- a/lib/components/dropdown/gf_dropdown.dart +++ b/lib/components/dropdown/gf_dropdown.dart @@ -5,14 +5,14 @@ class GFDropdown extends StatefulWidget { /// GF Dropdown let user to select from the number of items and display selected /// item in the button. It displays list of items in the overlay dropdown fashion. const GFDropdown( - {Key key, - @required this.items, + {Key? key, + required this.items, this.icon, this.selectedItemBuilder, this.value, this.hint, this.disabledHint, - @required this.onChanged, + required this.onChanged, this.onTap, this.elevation = 8, this.style, @@ -34,18 +34,18 @@ class GFDropdown extends StatefulWidget { this.dropdownButtonColor = GFColors.WHITE}) : super(key: key); - final DropdownButtonBuilder selectedItemBuilder; - final List> items; + final DropdownButtonBuilder? selectedItemBuilder; + final List>? items; /// The widget to use for the drop-down button's icon. /// Defaults to an [Icon] with the [Icons.arrow_drop_down] glyph. - final Widget icon; + final Widget? icon; /// The z-coordinate at which to place the menu when open. final int elevation; /// The value of the currently selected [DropdownMenuItem]. - final T value; + final T? value; /// Defines the border of dropdown button final BorderSide border; @@ -54,33 +54,33 @@ class GFDropdown extends StatefulWidget { final EdgeInsets padding; /// A placeholder widget that is displayed by the dropdown button. - final Widget hint; + final Widget? hint; /// A message to show when the dropdown is disabled. - final Widget disabledHint; + final Widget? disabledHint; /// Called when the user selects an item. /// If the [onChanged] callback is null or the list of [DropdownButton.items] /// is null then the dropdown button will be disabled, - final ValueChanged onChanged; + final ValueChanged? onChanged; /// Called when the dropdown button is tapped. - final VoidCallback onTap; + final VoidCallback? onTap; /// Defaults to the [TextTheme.subtitle1] value of the current /// [ThemeData.textTheme] of the current [Theme]. - final TextStyle style; + final TextStyle? style; /// The widget to use for drawing the drop-down button's underline. - final Widget underline; + final Widget? underline; /// The color of any [Icon] descendant of [icon] if this button is disabled, /// i.e. if [onChanged] is null. - final Color iconDisabledColor; + final Color? iconDisabledColor; /// The color of any [Icon] descendant of [icon] if this button is enabled, /// i.e. if [onChanged] is defined. - final Color iconEnabledColor; + final Color? iconEnabledColor; /// The size to use for the drop-down button's down arrow icon button. final double iconSize; @@ -92,20 +92,20 @@ class GFDropdown extends StatefulWidget { final bool isExpanded; /// Defines the height of the menu items - final double itemHeight; + final double? itemHeight; /// The color for the button's [Material] when it has the input focus. - final Color focusColor; + final Color? focusColor; /// Defines the keyboard focus for this widget. - final FocusNode focusNode; + final FocusNode? focusNode; /// On true state it should focus itself if nothing else is already focused. /// Defaults to false final bool autofocus; /// Defines the background color of the dropdown. - final Color dropdownColor; + final Color? dropdownColor; /// Defines the border radius of the dropdown. final BorderRadius borderRadius; diff --git a/lib/components/dropdown/gf_multiselect.dart b/lib/components/dropdown/gf_multiselect.dart index be98ec28..8463caed 100644 --- a/lib/components/dropdown/gf_multiselect.dart +++ b/lib/components/dropdown/gf_multiselect.dart @@ -6,8 +6,8 @@ class GFMultiSelect extends StatefulWidget { /// Checkbox ListTile items and display selected items in the TitleTile box. /// It displays list of items in the overlay dropdown fashion. const GFMultiSelect({ - @required this.items, - @required this.onSelect, + required this.items, + required this.onSelect, this.dropdownTitleTileText = 'Select : ', this.dropdownTitleTileTextStyle = const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), @@ -55,9 +55,8 @@ class GFMultiSelect extends StatefulWidget { this.dropdownTitleTileHintTextStyle = const TextStyle(fontSize: 12, fontWeight: FontWeight.w400), this.dropdownButton, - Key key, - }) : assert(selected != null), - super(key: key); + Key? key, + }) : super(key: key); /// defines the list of items the user can select final List items; @@ -73,7 +72,7 @@ class GFMultiSelect extends StatefulWidget { final TextStyle dropdownTitleTileTextStyle; /// type of [String] to define the dropdownTitleTile hint text - final String dropdownTitleTileHintText; + final String? dropdownTitleTileHintText; /// type of [TextStyle] to define the textStyle of [dropdownTitleTileHintTextStyle] final TextStyle dropdownTitleTileHintTextStyle; @@ -82,7 +81,7 @@ class GFMultiSelect extends StatefulWidget { final BorderRadius dropdownTitleTileBorderRadius; /// defines the border of the dropdownTitleTile. - final Border dropdownTitleTileBorder; + final Border? dropdownTitleTileBorder; /// defines the background color of dropdownButton final dynamic dropdownTitleTileColor; @@ -107,16 +106,16 @@ class GFMultiSelect extends StatefulWidget { final Widget collapsedIcon; /// defines the submit button in the dropdown - final Widget submitButton; + final Widget? submitButton; /// defines the cancel button in the dropdown - final Widget cancelButton; + final Widget? cancelButton; /// defines dropdown checkbox ListTile's background color. Can be given [Color] or [GFColors] final dynamic color; /// type of [Widget] or [GFAvatar] used to defines dropdown checkbox ListTile's leading - final Widget avatar; + final Widget? avatar; /// defines the margin of dropdown checkbox ListTile final EdgeInsets margin; @@ -154,7 +153,7 @@ class GFMultiSelect extends StatefulWidget { /// defines dropdown ListTile's checkbox's inactive icon ///type of [Widget] used to change the checkbox's inactive icon - final Widget inactiveIcon; + final Widget? inactiveIcon; /// type of [Color] used to change the background color of the custom active checkbox only final Color customBgColor; @@ -166,7 +165,7 @@ class GFMultiSelect extends StatefulWidget { /// defines the background color of the dropdown. Can be given [Color] or [GFColors] final Color dropdownBgColor; - final Widget dropdownButton; + final Widget? dropdownButton; @override _GFMultiSelectState createState() => _GFMultiSelectState(); @@ -194,9 +193,7 @@ class _GFMultiSelectState extends State> { @override void dispose() { - if (_controller != null) { - _controller.dispose(); - } + _controller.dispose(); super.dispose(); } @@ -279,10 +276,7 @@ class _GFMultiSelectState extends State> { onChanged: (bool selected) { _controller.text; _onItemSelect(selected, index); - if (selected == null) { - widget.onSelect(null); - return; - } + widget.onSelect(_selectedTitlesIndex); }, selected: widget.selected, diff --git a/lib/components/floating_widget/gf_floating_widget.dart b/lib/components/floating_widget/gf_floating_widget.dart index 1be9abc8..b7d36305 100644 --- a/lib/components/floating_widget/gf_floating_widget.dart +++ b/lib/components/floating_widget/gf_floating_widget.dart @@ -5,7 +5,7 @@ class GFFloatingWidget extends StatefulWidget { /// Creates a Floating body can be used to display the quick messages, warning and erros. /// Inside floating widget [GFToast] can be used as body. See [GFToast] const GFFloatingWidget( - {Key key, + {Key? key, this.child, this.horizontalPosition, this.verticalPosition, @@ -15,19 +15,19 @@ class GFFloatingWidget extends StatefulWidget { : super(key: key); ///child of type [Widget] which floats across the body based on horizontal and vertical position - final Widget child; + final Widget? child; ///body of type [Widget] which is same as Scaffold's body - final Widget body; + final Widget? body; /// horizontalPosition of type [double] which aligns the child horizontally across the body - final double horizontalPosition; + final double? horizontalPosition; /// verticalPosition of type [double] which aligns the child vertically across the body - final double verticalPosition; + final double? verticalPosition; ///blurnessColor of tye [Color] or [GFColors] which is used to blur the backgroundColor when ever the [child] is used in [GFFloatingWidget] - final Color blurnessColor; + final Color? blurnessColor; ///type of bool which allows to show or hide the blurness of the backgroundColor whenever the [child] is used in [GFFloatingWidget] final bool showBlurness; @@ -37,7 +37,7 @@ class GFFloatingWidget extends StatefulWidget { } class _GFFloatingWidgetState extends State { - Widget child; + Widget? child; @override void initState() { diff --git a/lib/components/image/gf_image_overlay.dart b/lib/components/image/gf_image_overlay.dart index bfef2145..bb432cea 100644 --- a/lib/components/image/gf_image_overlay.dart +++ b/lib/components/image/gf_image_overlay.dart @@ -6,13 +6,13 @@ import 'package:flutter/material.dart'; class GFImageOverlay extends StatelessWidget { /// Creates a image widget with shaded overlay. const GFImageOverlay({ - Key key, + Key? key, this.height, this.width, this.color, this.padding, this.margin, - this.image, + required this.image, this.child = const Text(''), this.alignment, this.borderRadius, @@ -21,23 +21,22 @@ class GFImageOverlay extends StatelessWidget { this.boxFit = BoxFit.fill, this.border, this.shape = BoxShape.rectangle, - }) : assert(shape != null), - super(key: key); + }) : super(key: key); /// define image's [double] height - final double height; + final double? height; /// define image's [double] width - final double width; + final double? width; /// The image background color. - final Color color; + final Color? color; /// The empty space that surrounds the card. Defines the image's outer [Container.margin]. - final EdgeInsetsGeometry margin; + final EdgeInsetsGeometry? margin; /// The empty space that surrounds the card. Defines the image's outer [Container.padding].. - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; /// The [Image] widget used to display image final ImageProvider image; @@ -46,21 +45,21 @@ class GFImageOverlay extends StatelessWidget { final Widget child; /// Align the [child] within the container. - final AlignmentGeometry alignment; + final AlignmentGeometry? alignment; /// How the image should be inscribed into the box. /// The default is [BoxFit.scaleDown] if centerSlice is null, and /// [BoxFit.fill] if centerSlice is not null. - final BoxFit boxFit; + final BoxFit? boxFit; /// A color filter to apply to the image before painting it. - final ColorFilter colorFilter; + final ColorFilter? colorFilter; /// The corners of this [GFImageOverlay] are rounded by this [BorderRadius]. - final BorderRadiusGeometry borderRadius; + final BorderRadiusGeometry? borderRadius; /// A border to draw above the [GFImageOverlay]. - final Border border; + final Border? border; /// The shape to fill the background [color], gradient, and [image] into and /// to cast as the boxShadow. @@ -92,7 +91,7 @@ class GFImageOverlay extends StatelessWidget { color: color, image: DecorationImage( fit: boxFit, - colorFilter: child != null ? colorFilter : null, + colorFilter: colorFilter, image: image, ), ), diff --git a/lib/components/intro_screen/gf_intro_screen.dart b/lib/components/intro_screen/gf_intro_screen.dart index b80606eb..760ab2f7 100644 --- a/lib/components/intro_screen/gf_intro_screen.dart +++ b/lib/components/intro_screen/gf_intro_screen.dart @@ -7,18 +7,18 @@ class GFIntroScreen extends StatefulWidget { /// It has many features that helps to build custom-made introduction screen sliders. /// Presents informative screens to users with various possibilities in customization. const GFIntroScreen({ - Key key, - @required this.pageController, - @required this.currentIndex, - @required this.pageCount, - @required this.slides, + Key? key, + required this.pageController, + required this.currentIndex, + required this.pageCount, + required this.slides, this.color, this.width, this.height, this.borderRadius, this.border, this.introScreenBottomNavigationBar, - this.showIntroScreenBottomNavigationBar = true, + this.showIntroScreenBottomNavigationBar = false, this.child, this.navigationBarColor = GFColors.SUCCESS, this.navigationBarHeight = 50, @@ -75,24 +75,24 @@ class GFIntroScreen extends StatefulWidget { final PageController pageController; /// defines background color of the [GFIntroScreen] slides - final Color color; + final Color? color; /// defines [GFIntroScreen] slides height - final double height; + final double? height; /// defines [GFIntroScreen] slides width - final double width; + final double? width; /// defines [GFIntroScreen] border radius to defines slides shape - final BorderRadius borderRadius; + final BorderRadius? borderRadius; /// defines [GFIntroScreen] slides border - final Border border; + final Border? border; /// defines [GFIntroScreen]'s bottom navigation bar - final GFIntroScreenBottomNavigationBar introScreenBottomNavigationBar; + final GFIntroScreenBottomNavigationBar? introScreenBottomNavigationBar; - /// on true state, displays [GFIntroScreenBottomNavigationBar], defaults to true + /// on true state, displays [GFIntroScreenBottomNavigationBar], defaults to false final bool showIntroScreenBottomNavigationBar; /// defines the currentIndex of [GFIntroScreen] slides, default value is 0 @@ -102,13 +102,13 @@ class GFIntroScreen extends StatefulWidget { final int pageCount; /// defines [GFIntroScreenBottomNavigationBar]'s child, it takes any widget - final Widget child; + final Widget? child; /// defines [GFIntroScreenBottomNavigationBar] height final double navigationBarHeight; /// defines [GFIntroScreenBottomNavigationBar] width - final double navigationBarWidth; + final double? navigationBarWidth; /// defines [GFIntroScreenBottomNavigationBar] padding final EdgeInsets navigationBarPadding; @@ -120,31 +120,31 @@ class GFIntroScreen extends StatefulWidget { final Color navigationBarColor; /// defines the shape of [GFIntroScreenBottomNavigationBar] - final ShapeBorder navigationBarShape; + final ShapeBorder? navigationBarShape; /// Called when the forward button is tapped - final VoidCallback onForwardButtonTap; + final VoidCallback? onForwardButtonTap; /// Called when the back button is tapped - final VoidCallback onBackButtonTap; + final VoidCallback? onBackButtonTap; /// Called when the done button is tapped - final VoidCallback onDoneTap; + final VoidCallback? onDoneTap; /// Called when the skip button is tapped - final VoidCallback onSkipTap; + final VoidCallback? onSkipTap; /// takes any Widget to define the backButton widget, - final Widget backButton; + final Widget? backButton; /// takes any Widget to define the forwardButton widget - final Widget forwardButton; + final Widget? forwardButton; /// takes any Widget to define the doneButton widget - final Widget doneButton; + final Widget? doneButton; /// takes any Widget to define the skipButton widget - final Widget skipButton; + final Widget? skipButton; /// takes String to define backButton text final String backButtonText; @@ -189,7 +189,7 @@ class GFIntroScreen extends StatefulWidget { final Color dividerColor; /// defines pagination shape - final ShapeBorder dotShape; + final ShapeBorder? dotShape; /// defines pagination inactive color final Color inactiveColor; diff --git a/lib/components/intro_screen/gf_intro_screen_bottom_navigation_bar.dart b/lib/components/intro_screen/gf_intro_screen_bottom_navigation_bar.dart index 4f2a23e2..1626b8d5 100644 --- a/lib/components/intro_screen/gf_intro_screen_bottom_navigation_bar.dart +++ b/lib/components/intro_screen/gf_intro_screen_bottom_navigation_bar.dart @@ -4,10 +4,10 @@ import 'package:getwidget/getwidget.dart'; class GFIntroScreenBottomNavigationBar extends StatefulWidget { const GFIntroScreenBottomNavigationBar({ - Key key, - @required this.pageController, - @required this.currentIndex, - @required this.pageCount, + Key? key, + required this.pageController, + required this.currentIndex, + required this.pageCount, this.child, this.navigationBarColor = GFColors.SUCCESS, this.navigationBarHeight = 50, @@ -67,13 +67,13 @@ class GFIntroScreenBottomNavigationBar extends StatefulWidget { final int pageCount; /// defines [GFIntroScreenBottomNavigationBar], it takes any widget - final Widget child; + final Widget? child; /// defines [GFIntroScreenBottomNavigationBar] height final double navigationBarHeight; /// defines [GFIntroScreenBottomNavigationBar] width - final double navigationBarWidth; + final double? navigationBarWidth; /// defines [GFIntroScreenBottomNavigationBar] padding final EdgeInsets navigationBarPadding; @@ -85,31 +85,31 @@ class GFIntroScreenBottomNavigationBar extends StatefulWidget { final Color navigationBarColor; /// defines the shape of [GFIntroScreenBottomNavigationBar] - final ShapeBorder navigationBarShape; + final ShapeBorder? navigationBarShape; /// Called when the [forwardButtonText] is tapped - final VoidCallback onForwardButtonTap; + final VoidCallback? onForwardButtonTap; /// Called when the [backButtonText] is tapped - final VoidCallback onBackButtonTap; + final VoidCallback? onBackButtonTap; /// Called when the [doneButtonText] is tapped - final VoidCallback onDoneTap; + final VoidCallback? onDoneTap; /// Called when the [skipButtonText] is tapped - final VoidCallback onSkipTap; + final VoidCallback? onSkipTap; /// defines the backButton widget - final Widget backButton; + final Widget? backButton; /// defines the forwardButton widget - final Widget forwardButton; + final Widget? forwardButton; /// defines the doneButton widget - final Widget doneButton; + final Widget? doneButton; /// defines the skipButton widget - final Widget skipButton; + final Widget? skipButton; /// defines the backButton text final String backButtonText; @@ -154,7 +154,7 @@ class GFIntroScreenBottomNavigationBar extends StatefulWidget { final Color dividerColor; /// defines pagination shape - final ShapeBorder dotShape; + final ShapeBorder? dotShape; /// defines pagination inactive color final Color inactiveColor; @@ -178,22 +178,23 @@ class GFIntroScreenBottomNavigationBar extends StatefulWidget { class _GFIntroScreenBottomNavigationBarState extends State { - PageController _pageController; - int currentIndex; - List pages; + late PageController _pageController; + late int currentIndex; + List? pages; @override void initState() { _pageController = widget.pageController; currentIndex = _pageController.initialPage; - if (widget.pageController != null) { - _pageController = widget.pageController; - } + _pageController = widget.pageController; _pageController.addListener(() { if (mounted) { - setState(() { - currentIndex = _pageController.page.round(); - }); + final index = _pageController.page?.round(); + if (index != null) { + setState(() { + currentIndex = index; + }); + } } }); super.initState(); @@ -201,9 +202,7 @@ class _GFIntroScreenBottomNavigationBarState @override void dispose() { - if (_pageController != null) { - _pageController.dispose(); - } + _pageController.dispose(); super.dispose(); } @@ -239,7 +238,7 @@ class _GFIntroScreenBottomNavigationBarState margin: widget.navigationBarMargin, child: widget.child != null ? Row( - children: [widget.child], + children: [widget.child!], ) : Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/components/list_tile/gf_list_tile.dart b/lib/components/list_tile/gf_list_tile.dart index eed7744d..66bcb741 100644 --- a/lib/components/list_tile/gf_list_tile.dart +++ b/lib/components/list_tile/gf_list_tile.dart @@ -5,13 +5,13 @@ import 'package:getwidget/getwidget.dart'; class GFListTile extends StatelessWidget { /// Creates ListTile with leading, title, trailing, image widget for almost every type of ListTile design. const GFListTile({ - Key key, + Key? key, this.titleText, - this.subtitleText, + this.subTitleText, this.color, this.avatar, this.title, - this.subtitle, + this.subTitle, this.description, this.icon, this.padding = const EdgeInsets.all(8), @@ -24,34 +24,31 @@ class GFListTile extends StatelessWidget { this.hoverColor, this.focusNode, this.autofocus = false, - }) : assert(enabled != null), - assert(selected != null), - assert(autofocus != null), - super(key: key); + }) : super(key: key); /// type of [String] used to pass text, alternative to title property and gets higher priority than title - final String titleText; + final String? titleText; - /// type of [String] used to pass text, alternative to subtitle property and gets higher priority than subtitle - final String subtitleText; + /// type of [String] used to pass text, alternative to subTitle property and gets higher priority than subTitle + final String? subTitleText; /// The GFListTile's background color. Can be given [Color] or [GFColors] - final Color color; + final Color? color; /// type of [Widget] or [GFAvatar] used to create rounded user profile - final Widget avatar; + final Widget? avatar; /// The title to display inside the [GFListTile]. see [Text] - final Widget title; + final Widget? title; - /// The subtitle to display inside the [GFListTile]. see [Text] - final Widget subtitle; + /// The subTitle to display inside the [GFListTile]. see [Text] + final Widget? subTitle; /// The description to display inside the [GFListTile]. see [Text] - final Widget description; + final Widget? description; /// The icon to display inside the [GFListTile]. see [Icon] - final Widget icon; + final Widget? icon; /// defines the margin of GFListTile final EdgeInsets margin; @@ -69,12 +66,12 @@ class GFListTile extends StatelessWidget { /// Called when the user taps this list tile. /// /// Inoperative if [enabled] is false. - final GestureTapCallback onTap; + final GestureTapCallback? onTap; /// Called when the user long-presses on this list tile. /// /// Inoperative if [enabled] is false. - final GestureLongPressCallback onLongPress; + final GestureLongPressCallback? onLongPress; /// If this tile is also [enabled] then icons and text are rendered with the same color. /// @@ -83,13 +80,13 @@ class GFListTile extends StatelessWidget { final bool selected; /// The color for the tile's [Material] when it has the input focus. - final Color focusColor; + final Color? focusColor; /// The color for the tile's [Material] when a pointer is hovering over it. - final Color hoverColor; + final Color? hoverColor; /// Defines the keyboard focus for this widget. - final FocusNode focusNode; + final FocusNode? focusNode; /// On true state it should focus itself if nothing else is already focused. /// Defaults to false @@ -127,22 +124,22 @@ class GFListTile extends StatelessWidget { children: [ titleText != null ? Text( - titleText, + titleText!, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w500, color: GFColors.DARK), ) : title ?? Container(), - subtitleText != null + subTitleText != null ? Text( - subtitleText, + subTitleText!, style: const TextStyle( fontSize: 14.5, color: Colors.black54, ), ) - : subtitle ?? Container(), + : subTitle ?? Container(), description ?? Container() ], ), diff --git a/lib/components/loader/gf_loader.dart b/lib/components/loader/gf_loader.dart index c47ba463..5a3314ed 100644 --- a/lib/components/loader/gf_loader.dart +++ b/lib/components/loader/gf_loader.dart @@ -5,7 +5,7 @@ import 'package:getwidget/getwidget.dart'; class GFLoader extends StatefulWidget { /// [GFLoader] shows differnt type of loaders with different customization options. const GFLoader( - {Key key, + {Key? key, this.loaderColorOne = Colors.redAccent, this.loaderColorTwo = Colors.green, this.loaderColorThree = Colors.blueAccent, @@ -21,7 +21,7 @@ class GFLoader extends StatefulWidget { : super(key: key); /// Type of [Widget] used only in custom type and it is prominent over the loaderIconOne, loaderIconTwo, loaderIconThree in custom type - final Widget child; + final Widget? child; /// Type of GFColor or [Color] which defines the color of the first dot in only circle or square type of loader final Color loaderColorOne; @@ -39,16 +39,16 @@ class GFLoader extends StatefulWidget { final GFLoaderType type; /// Type of [Widget] which takes text, icons or images for first dot only in custom type of loader - final Widget loaderIconOne; + final Widget? loaderIconOne; /// Type of [Widget] which takes text, icons or images for second dot only in custom type of loader - final Widget loaderIconTwo; + final Widget? loaderIconTwo; /// Type of [Widget] which takes text, icons or images for third dot only in custom type of loader - final Widget loaderIconThree; + final Widget? loaderIconThree; /// type of Animation used to change the color of the android loader only - final Animation androidLoaderColor; + final Animation? androidLoaderColor; /// type of [double] used to change the stroke width of the android loader only final double loaderstrokeWidth; @@ -63,22 +63,22 @@ class GFLoader extends StatefulWidget { class _GFLoaderState extends State with SingleTickerProviderStateMixin { - Animation loaderanimation1; - Animation loaderanimation2; - Animation loaderanimation3; - AnimationController controller; + late Animation loaderAnimation1; + late Animation loaderAnimation2; + late Animation loaderAnimation3; + late AnimationController controller; Interval interval = const Interval(0.1, 0.81, curve: Curves.linear); @override void initState() { controller = AnimationController(duration: widget.duration, vsync: this); - loaderanimation1 = Tween(begin: 0, end: 1).animate( + loaderAnimation1 = Tween(begin: 0, end: 1).animate( CurvedAnimation(parent: controller, curve: interval), ); - loaderanimation2 = Tween(begin: 0, end: 1).animate( + loaderAnimation2 = Tween(begin: 0, end: 1).animate( CurvedAnimation(parent: controller, curve: interval), ); - loaderanimation3 = Tween(begin: 0, end: 1).animate( + loaderAnimation3 = Tween(begin: 0, end: 1).animate( CurvedAnimation(parent: controller, curve: interval), ); controller.addListener(() { @@ -116,12 +116,12 @@ class _GFLoaderState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ Opacity( - opacity: loaderanimation1.value <= 0.3 - ? 2.5 * loaderanimation1.value - : (loaderanimation1.value > 0.30 && - loaderanimation1.value <= 0.70) + opacity: loaderAnimation1.value <= 0.3 + ? 2.5 * loaderAnimation1.value + : (loaderAnimation1.value > 0.30 && + loaderAnimation1.value <= 0.70) ? 1.0 - : 2.5 - (2.5 * loaderanimation1.value), + : 2.5 - (2.5 * loaderAnimation1.value), child: Padding( padding: const EdgeInsets.only(right: 8), child: Loader( @@ -134,12 +134,12 @@ class _GFLoaderState extends State ), ), Opacity( - opacity: loaderanimation2.value <= 0.3 - ? 2.5 * loaderanimation2.value - : (loaderanimation2.value > 0.30 && - loaderanimation2.value <= 0.70) + opacity: loaderAnimation2.value <= 0.3 + ? 2.5 * loaderAnimation2.value + : (loaderAnimation2.value > 0.30 && + loaderAnimation2.value <= 0.70) ? 1.0 - : 2.5 - (2.5 * loaderanimation2.value), + : 2.5 - (2.5 * loaderAnimation2.value), child: Padding( padding: const EdgeInsets.only(right: 8), child: Loader( @@ -151,12 +151,12 @@ class _GFLoaderState extends State ), ), Opacity( - opacity: loaderanimation3.value <= 0.3 - ? 2.5 * loaderanimation3.value - : (loaderanimation3.value > 0.30 && - loaderanimation3.value <= 0.70) + opacity: loaderAnimation3.value <= 0.3 + ? 2.5 * loaderAnimation3.value + : (loaderAnimation3.value > 0.30 && + loaderAnimation3.value <= 0.70) ? 1.0 - : 2.5 - (2.5 * loaderanimation3.value), + : 2.5 - (2.5 * loaderAnimation3.value), child: Padding( padding: const EdgeInsets.only(right: 8), child: Loader( @@ -173,16 +173,14 @@ class _GFLoaderState extends State @override void dispose() { - if (controller != null) { - controller.dispose(); - } + controller.dispose(); super.dispose(); } } class Loader extends StatelessWidget { const Loader( - {Key key, + {Key? key, this.radius, this.color, this.type, @@ -190,12 +188,12 @@ class Loader extends StatelessWidget { this.size, this.child}) : super(key: key); - final double radius; - final Color color; - final GFLoaderType type; - final Widget icon; - final double size; - final Widget child; + final double? radius; + final Color? color; + final GFLoaderType? type; + final Widget? icon; + final double? size; + final Widget? child; @override Widget build(BuildContext context) => Center( diff --git a/lib/components/progress_bar/gf_progress_bar.dart b/lib/components/progress_bar/gf_progress_bar.dart index 2b4e2456..f1efd4a0 100644 --- a/lib/components/progress_bar/gf_progress_bar.dart +++ b/lib/components/progress_bar/gf_progress_bar.dart @@ -4,7 +4,7 @@ import 'package:getwidget/getwidget.dart'; class GFProgressBar extends StatefulWidget { /// Shows progress as a completed and remaining percentage in bar of given state GFProgressBar({ - Key key, + Key? key, this.percentage = 0.2, this.circleWidth = 5.0, this.circleStartAngle = 0.0, @@ -30,9 +30,8 @@ class GFProgressBar extends StatefulWidget { this.alignment = MainAxisAlignment.start, this.clipLinearGradient = false, }) : super(key: key) { - if (linearGradient != null && progressBarColor != null) { - throw ArgumentError( - ' linearGradient and progressBarColor cannot be given'); + if (linearGradient != null) { + throw ArgumentError(' linearGradient cannot be given'); } assert(circleStartAngle >= 0.0); if (percentage < 0.0 || percentage > 1.0) { @@ -41,7 +40,7 @@ class GFProgressBar extends StatefulWidget { } /// width of the Progress bar - final double width; + final double? width; /// Height of the Progress Bar final double lineHeight; @@ -59,26 +58,26 @@ class GFProgressBar extends StatefulWidget { final int animationDuration; /// type of Widget which comes before the progress bar - final Widget leading; + final Widget? leading; /// type of Widget which comes after the progress bar - final Widget trailing; + final Widget? trailing; /// type of Widget which can be text , icon etc which is generally used to show the percentage of progress complete - final Widget child; + final Widget? child; /// type of MainAxisAlignment used to align the leading, trailing and the progress bar in a fashion final MainAxisAlignment alignment; /// type of EdgeInsets which gives padding to the GFProgressBar - final EdgeInsets padding; + final EdgeInsets? padding; /// set true if you want to animate the progress bar from the last percentage value you set final bool animateFromLastPercentage; /// If present, this will make the progress bar colored by this gradient. /// This will override [progressBarColor]. It is an error to provide both. - final LinearGradient linearGradient; + final LinearGradient? linearGradient; /// set false if you don't want to preserve the state of the widget final bool autoLive; @@ -87,7 +86,7 @@ class GFProgressBar extends StatefulWidget { final bool fromRightToLeft; /// Creates a mask filter that takes the progress shape being drawn and blurs it. - final MaskFilter mask; + final MaskFilter? mask; /// Set true if you want to display only part of [linearGradient] based on progressPercent value /// If no [linearGradient] is specified this option is ignored. @@ -97,7 +96,7 @@ class GFProgressBar extends StatefulWidget { final GFProgressType type; /// type of [GFProgressHeadType] which changes the shape of progress head based on the type ie, circular and square - final GFProgressHeadType progressHeadType; + final GFProgressHeadType? progressHeadType; /// type of double which defines the thickness of the circle's arc in Circular Progress bar final double circleWidth; @@ -109,7 +108,7 @@ class GFProgressBar extends StatefulWidget { final bool reverse; /// type of double used to show the radius of Circular Progress Bar - final double radius; + final double? radius; /// type of double which should be from 0 to 1 to indicate the progress of the ProgressBars final double percentage; @@ -120,8 +119,8 @@ class GFProgressBar extends StatefulWidget { class _GFProgressBarState extends State with TickerProviderStateMixin, AutomaticKeepAliveClientMixin { - AnimationController _animationController, circularAnimationController; - Animation _animation, circularAnimation; + AnimationController? _animationController, circularAnimationController; + Animation? _animation, circularAnimation; double _progressPercent = 0; double _percentage = 0; @@ -133,13 +132,13 @@ class _GFProgressBarState extends State duration: Duration(milliseconds: widget.animationDuration), vsync: this); _animation = Tween(begin: _progressPercent, end: widget.percentage) - .animate(_animationController) + .animate(_animationController!) ..addListener(() { setState(() { - _progressPercent = _animation.value; + _progressPercent = _animation?.value; }); }); - _animationController.forward(); + _animationController?.forward(); } else { _updateprogressPercent(); } @@ -149,13 +148,13 @@ class _GFProgressBarState extends State duration: Duration(milliseconds: widget.animationDuration), vsync: this); circularAnimation = Tween(begin: _percentage, end: widget.percentage) - .animate(circularAnimationController) + .animate(circularAnimationController!) ..addListener(() { setState(() { - _percentage = circularAnimation.value; + _percentage = circularAnimation?.value; }); }); - circularAnimationController.forward(); + circularAnimationController?.forward(); } else { _updateProgress(); } @@ -175,12 +174,10 @@ class _GFProgressBarState extends State @override void dispose() { - if (_animationController != null) { - _animationController.dispose(); - } - if (circularAnimationController != null) { - circularAnimationController.dispose(); - } + _animationController?.dispose(); + + circularAnimationController?.dispose(); + super.dispose(); } @@ -189,15 +186,15 @@ class _GFProgressBarState extends State super.didUpdateWidget(oldWidget); if (oldWidget.percentage != widget.percentage) { if (_animationController != null) { - _animationController.duration = + _animationController?.duration = Duration(milliseconds: widget.animationDuration); _animation = Tween( begin: widget.animateFromLastPercentage ? oldWidget.percentage : 0.0, end: widget.percentage) - .animate(_animationController); - _animationController.forward(from: 0); + .animate(_animationController!); + _animationController?.forward(from: 0); } else { _updateprogressPercent(); } @@ -205,15 +202,15 @@ class _GFProgressBarState extends State if (oldWidget.percentage != widget.percentage || oldWidget.circleStartAngle != widget.circleStartAngle) { if (circularAnimationController != null) { - circularAnimationController.duration = + circularAnimationController?.duration = Duration(milliseconds: widget.animationDuration); circularAnimation = Tween( begin: widget.animateFromLastPercentage ? oldWidget.percentage : 0.0, end: widget.percentage) - .animate(circularAnimationController); - circularAnimationController.forward(from: 0); + .animate(circularAnimationController!); + circularAnimationController?.forward(from: 0); } else { _updateProgress(); } @@ -223,7 +220,7 @@ class _GFProgressBarState extends State @override Widget build(BuildContext context) { super.build(context); - final item = []; + final item = []; if (widget.leading != null) { item.add(widget.leading); } @@ -256,7 +253,7 @@ class _GFProgressBarState extends State progressBarColor: widget.progressBarColor, backgroundColor: widget.backgroundColor, circleStartAngle: widget.circleStartAngle, - radius: (widget.radius / 2) - widget.circleWidth / 2, + radius: (widget.radius! / 2) - widget.circleWidth / 2, circleWidth: widget.circleWidth, reverse: widget.reverse, linearGradient: widget.linearGradient, @@ -284,7 +281,8 @@ class _GFProgressBarState extends State child: Row( mainAxisAlignment: widget.alignment, crossAxisAlignment: CrossAxisAlignment.center, - children: item, + // ignore: avoid_as + children: item as List, )), ) : Material( @@ -294,7 +292,8 @@ class _GFProgressBarState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: item, + // ignore: avoid_as + children: item as List, )), ); } @@ -316,15 +315,15 @@ class LinearPainter extends CustomPainter { this.mask, this.clipLinearGradient, }) { - _paintBackground.color = backgroundColor; + _paintBackground.color = backgroundColor!; _paintBackground.style = PaintingStyle.stroke; - _paintBackground.strokeWidth = circleWidth; + _paintBackground.strokeWidth = circleWidth!; - _paintLine.color = progress.toString() == '0.0' - ? progressBarColor.withOpacity(0) - : progressBarColor; + _paintLine.color = progress.toString() == '0.0' && progressBarColor != null + ? progressBarColor!.withOpacity(0) + : progressBarColor!; _paintLine.style = PaintingStyle.stroke; - _paintLine.strokeWidth = circleWidth; + _paintLine.strokeWidth = circleWidth!; if (progressHeadType == GFProgressHeadType.square) { _paintLine.strokeCap = StrokeCap.butt; @@ -335,16 +334,16 @@ class LinearPainter extends CustomPainter { } final Paint _paintBackground = Paint(); final Paint _paintLine = Paint(); - final double circleWidth; - final double progress; - final Widget child; - final bool fromRightToLeft; - final Color progressBarColor; - final Color backgroundColor; - final LinearGradient linearGradient; - final MaskFilter mask; - final bool clipLinearGradient; - GFProgressHeadType progressHeadType; + final double? circleWidth; + final double? progress; + final Widget? child; + final bool? fromRightToLeft; + final Color? progressBarColor; + final Color? backgroundColor; + final LinearGradient? linearGradient; + final MaskFilter? mask; + final bool? clipLinearGradient; + GFProgressHeadType? progressHeadType; @override void paint(Canvas canvas, Size size) { @@ -354,14 +353,14 @@ class LinearPainter extends CustomPainter { if (mask != null) { _paintLine.maskFilter = mask; } - if (fromRightToLeft) { - final xProgress = size.width - size.width * progress; + if (fromRightToLeft!) { + final xProgress = size.width - size.width * progress!; if (linearGradient != null) { _paintLine.shader = _createGradientShaderRightToLeft(size, xProgress); } canvas.drawLine(end, Offset(xProgress, size.height / 2), _paintLine); } else { - final xProgress = size.width * progress; + final xProgress = size.width * progress!; if (linearGradient != null) { _paintLine.shader = _createGradientShaderLeftToRight(size, xProgress); } @@ -369,10 +368,10 @@ class LinearPainter extends CustomPainter { } } - Shader _createGradientShaderRightToLeft(Size size, double xProgress) { + Shader? _createGradientShaderRightToLeft(Size size, double xProgress) { final Offset shaderEndPoint = - clipLinearGradient ? Offset.zero : Offset(xProgress, size.height); - return linearGradient.createShader( + clipLinearGradient! ? Offset.zero : Offset(xProgress, size.height); + return linearGradient?.createShader( Rect.fromPoints( Offset(size.width, size.height), shaderEndPoint, @@ -380,11 +379,11 @@ class LinearPainter extends CustomPainter { ); } - Shader _createGradientShaderLeftToRight(Size size, double xProgress) { - final Offset shaderEndPoint = clipLinearGradient + Shader? _createGradientShaderLeftToRight(Size size, double xProgress) { + final Offset shaderEndPoint = clipLinearGradient! ? Offset(size.width, size.height) : Offset(xProgress, size.height); - return linearGradient.createShader( + return linearGradient?.createShader( Rect.fromPoints( Offset.zero, shaderEndPoint, @@ -400,7 +399,7 @@ class CirclePainter extends CustomPainter { CirclePainter( {this.circleWidth, this.progress, - @required this.radius, + required this.radius, this.progressHeadType, this.progressBarColor, this.backgroundColor, @@ -409,12 +408,12 @@ class CirclePainter extends CustomPainter { this.reverse, this.arcBackgroundColor, this.mask}) { - _paintBackground.color = backgroundColor; + _paintBackground.color = backgroundColor!; _paintBackground.style = PaintingStyle.stroke; - _paintBackground.strokeWidth = circleWidth; - _paintLine.color = progressBarColor; + _paintBackground.strokeWidth = circleWidth!; + _paintLine.color = progressBarColor!; _paintLine.style = PaintingStyle.stroke; - _paintLine.strokeWidth = circleWidth; + _paintLine.strokeWidth = circleWidth!; if (progressHeadType == GFProgressHeadType.circular) { _paintLine.strokeCap = StrokeCap.round; } else if (progressHeadType == GFProgressHeadType.square) { @@ -423,17 +422,17 @@ class CirclePainter extends CustomPainter { } final Paint _paintBackground = Paint(); final Paint _paintLine = Paint(); - final double circleWidth; - final double progress; + final double? circleWidth; + final double? progress; final double radius; - final Color progressBarColor; - final Color backgroundColor; + final Color? progressBarColor; + final Color? backgroundColor; final double circleStartAngle; - final LinearGradient linearGradient; - final Color arcBackgroundColor; - final bool reverse; - final MaskFilter mask; - final GFProgressHeadType progressHeadType; + final LinearGradient? linearGradient; + final Color? arcBackgroundColor; + final bool? reverse; + final MaskFilter? mask; + final GFProgressHeadType? progressHeadType; @override void paint(Canvas canvas, Size size) { @@ -443,7 +442,7 @@ class CirclePainter extends CustomPainter { _paintLine.maskFilter = mask; } if (linearGradient != null) { - _paintLine.shader = linearGradient.createShader( + _paintLine.shader = linearGradient?.createShader( Rect.fromCircle( center: child, radius: radius, @@ -452,10 +451,10 @@ class CirclePainter extends CustomPainter { } final double fixedStartAngle = circleStartAngle; const double circleStartAngleFixedMargin = 1; - if (reverse) { + if (reverse!) { final start = radians(360 * circleStartAngleFixedMargin - 90.0 + fixedStartAngle); - final end = radians(-progress * circleStartAngleFixedMargin); + final end = radians(-progress! * circleStartAngleFixedMargin); canvas.drawArc( Rect.fromCircle( center: child, @@ -468,7 +467,7 @@ class CirclePainter extends CustomPainter { ); } else { final start = radians(-90.0 + fixedStartAngle); - final end = radians(progress * circleStartAngleFixedMargin); + final end = radians(progress! * circleStartAngleFixedMargin); canvas.drawArc( Rect.fromCircle( center: child, diff --git a/lib/components/radio/gf_radio.dart b/lib/components/radio/gf_radio.dart index 4ff86546..4c42a181 100644 --- a/lib/components/radio/gf_radio.dart +++ b/lib/components/radio/gf_radio.dart @@ -7,10 +7,10 @@ import 'package:flutter/widgets.dart'; class GFRadio extends StatefulWidget { /// [GFRadio] is one type of selection indicator in a list of options. const GFRadio( - {Key key, - @required this.value, - @required this.groupValue, - @required this.onChanged, + {Key? key, + required this.value, + required this.groupValue, + required this.onChanged, this.size = GFSize.SMALL, this.type = GFRadioType.basic, this.radioColor = GFColors.SUCCESS, @@ -28,9 +28,7 @@ class GFRadio extends StatefulWidget { this.autofocus = false, this.focusNode, this.toggleable = false}) - : assert(autofocus != null), - assert(toggleable != null), - super(key: key); + : super(key: key); /// type of [GFRadioType] which is of four type is basic, sqaure, circular and custom final GFRadioType type; @@ -54,13 +52,13 @@ class GFRadio extends StatefulWidget { final Color inactiveBorderColor; /// Called when the user checks or unchecks the radio button - final ValueChanged onChanged; + final ValueChanged? onChanged; ///type of Widget used to change the radio button's active icon final Widget activeIcon; ///type of [Widget] used to change the radio button's inactive icon - final Widget inactiveIcon; + final Widget? inactiveIcon; /// type of [Color] used to change the background color of the custom active radio button only final Color customBgColor; @@ -70,7 +68,7 @@ class GFRadio extends StatefulWidget { final bool autofocus; /// an optional focus node to use as the focus node for this widget. - final FocusNode focusNode; + final FocusNode? focusNode; /// The value represented by this radio button. final T value; @@ -89,7 +87,7 @@ class GFRadio extends StatefulWidget { class _GFRadioState extends State> with TickerProviderStateMixin { bool get enabled => widget.onChanged != null; bool selected = false; - T groupValue; + T? groupValue; void onStatusChange() { groupValue = widget.value; @@ -97,12 +95,8 @@ class _GFRadioState extends State> with TickerProviderStateMixin { } void _handleChanged(bool selected) { - if (selected == null) { - widget.onChanged(null); - return; - } if (selected) { - widget.onChanged(widget.value); + widget.onChanged!(widget.value); } } diff --git a/lib/components/radio_list_tile/gf_radio_list_tile.dart b/lib/components/radio_list_tile/gf_radio_list_tile.dart index 86c738d0..92f9f6f0 100644 --- a/lib/components/radio_list_tile/gf_radio_list_tile.dart +++ b/lib/components/radio_list_tile/gf_radio_list_tile.dart @@ -4,10 +4,10 @@ import 'package:getwidget/getwidget.dart'; class GFRadioListTile extends StatelessWidget { /// [GFRadioListTile] is a list title of with [GFRadio] in it. const GFRadioListTile({ - Key key, - @required this.value, - @required this.groupValue, - @required this.onChanged, + Key? key, + required this.value, + required this.groupValue, + required this.onChanged, this.size = GFSize.SMALL, this.type = GFRadioType.basic, this.radioColor = GFColors.SUCCESS, @@ -27,11 +27,11 @@ class GFRadioListTile extends StatelessWidget { this.focusNode, this.toggleable = false, this.titleText, - this.subtitleText, + this.subTitleText, this.color, this.avatar, this.title, - this.subtitle, + this.subTitle, this.description, this.icon, this.padding = const EdgeInsets.all(8), @@ -42,35 +42,31 @@ class GFRadioListTile extends StatelessWidget { this.selected = false, this.focusColor, this.hoverColor, - }) : assert(autofocus != null), - assert(toggleable != null), - assert(enabled != null), - assert(selected != null), - super(key: key); + }) : super(key: key); ///type of [String] used to pass text, alternative to title property and gets higher priority than title - final String titleText; + final String? titleText; - ///type of [String] used to pass text, alternative to subtitle property and gets higher priority than subtitle - final String subtitleText; + ///type of [String] used to pass text, alternative to subTitle property and gets higher priority than subTitle + final String? subTitleText; /// The GFListTile's background color. Can be given [Color] or [GFColors] - final Color color; + final Color? color; /// If position is start Checkbox will come instead of avatar, type of [Widget] or [GFAvatar] used to create rounded user profile - final Widget avatar; + final Widget? avatar; /// The title to display inside the [GFListTile]. see [Text] - final Widget title; + final Widget? title; - /// The subtitle to display inside the [GFListTile]. see [Text] - final Widget subtitle; + /// The subTitle to display inside the [GFListTile]. see [Text] + final Widget? subTitle; /// The description to display inside the [GFListTile]. see [Text] - final Widget description; + final Widget? description; /// If position is end Checkbox will come instead of icon, The icon to display inside the [GFListTile]. see [Icon] - final Widget icon; + final Widget? icon; /// defines the margin of GFListTile final EdgeInsets margin; @@ -88,7 +84,7 @@ class GFRadioListTile extends StatelessWidget { /// Called when the user taps this list tile. /// /// Inoperative if [enabled] is false. - final GestureTapCallback onTap; + final GestureTapCallback? onTap; /// Position allows user to set position of [GFCheckbox] based on given [GFPosition] final GFPosition position; @@ -96,7 +92,7 @@ class GFRadioListTile extends StatelessWidget { /// Called when the user long-presses on this list tile. /// /// Inoperative if [enabled] is false. - final GestureLongPressCallback onLongPress; + final GestureLongPressCallback? onLongPress; /// If this tile is also [enabled] then icons and text are rendered with the same color. /// @@ -105,17 +101,17 @@ class GFRadioListTile extends StatelessWidget { final bool selected; /// The color for the tile's [Material] when it has the input focus. - final Color focusColor; + final Color? focusColor; /// The color for the tile's [Material] when a pointer is hovering over it. - final Color hoverColor; + final Color? hoverColor; /// on true state this widget will be selected as the initial focus /// when no other node in its scope is currently focused final bool autofocus; /// an optional focus node to use as the focus node for this widget. - final FocusNode focusNode; + final FocusNode? focusNode; /// type of [GFRadioType] which is of four type is basic, sqaure, circular and custom final GFRadioType type; @@ -139,13 +135,13 @@ class GFRadioListTile extends StatelessWidget { final Color inactiveBorderColor; /// Called when the user checks or unchecks the checkbox. - final ValueChanged onChanged; + final ValueChanged? onChanged; ///type of Widget used to change the checkbox's active icon final Widget activeIcon; ///type of [Widget] used to change the checkbox's inactive icon - final Widget inactiveIcon; + final Widget? inactiveIcon; /// type of [Color] used to change the background color of the custom active checkbox only final Color customBgColor; @@ -188,19 +184,19 @@ class GFRadioListTile extends StatelessWidget { onTap: onChanged != null ? () { if (toggleable && checked) { - onChanged(null); + onChanged!(null); return; } if (!checked) { - onChanged(value); + onChanged!(value); } } : null, selected: selected, avatar: position == GFPosition.start ? radio : avatar, titleText: titleText, - subtitle: subtitle, - subtitleText: subtitleText, + subTitle: subTitle, + subTitleText: subTitleText, description: description, color: color, padding: padding, diff --git a/lib/components/rating/gf_rating.dart b/lib/components/rating/gf_rating.dart index 48f68962..975e48db 100644 --- a/lib/components/rating/gf_rating.dart +++ b/lib/components/rating/gf_rating.dart @@ -6,11 +6,12 @@ typedef RatingChangeCallback = void Function(double rating); class GFRating extends StatefulWidget { /// [GFRating] to show ratings with many custimazation options. const GFRating({ + Key? key, + required this.onChanged, + required this.value, this.itemCount = 5, this.spacing = 0.0, - this.value = 0.0, this.defaultIcon, - this.onChanged, this.color, this.borderColor, this.size = GFSize.MEDIUM, @@ -23,31 +24,31 @@ class GFRating extends StatefulWidget { this.inputDecorations, this.margin = const EdgeInsets.symmetric(vertical: 16), this.padding = const EdgeInsets.symmetric(horizontal: 16), - }) : assert(value != null); + }) : super(key: key); /// defines total number of rating items final int itemCount; /// defines the color of items - final Color color; + final Color? color; /// defines the border color of [halfFilledIcon] - final Color borderColor; + final Color? borderColor; /// defines the size of items. GFSize can be used for size variations like small. medium. large final double size; - /// if true, allow half rating of items. Default it will be in true state + /// if true, allow half rating of items on dragging. Default it will be in true state final bool allowHalfRating; /// defines the items when filled - final Widget filledIcon; + final Widget? filledIcon; /// defines the items when half-filled - final Widget halfFilledIcon; + final Widget? halfFilledIcon; /// defines the default items, when having filledIcon && halfFilledIcon - final Widget defaultIcon; + final Widget? defaultIcon; /// defines the space bestuckValue items final double spacing; @@ -62,13 +63,13 @@ class GFRating extends StatefulWidget { final bool showTextForm; /// defines the design and funtion of rating [TextFormField]'s suffix icon - final Widget suffixIcon; + final Widget? suffixIcon; /// controls the [TextField] Controller of rating [TextFormField] - final TextEditingController controller; + final TextEditingController? controller; /// defines the [InputDecoration] of rating [TextFormField] - final InputDecoration inputDecorations; + final InputDecoration? inputDecorations; /// defines the margin of rating [TextFormField] final EdgeInsets margin; @@ -111,12 +112,11 @@ class _GFRatingState extends State { return GestureDetector( onTap: () { - if (widget.onChanged != null) { - widget.onChanged(index + 1.0); - } + widget.onChanged(index + 1.0); }, onHorizontalDragUpdate: (dragDetails) { - final RenderBox box = context.findRenderObject(); + // ignore: avoid_as + final RenderBox box = context.findRenderObject() as RenderBox; final _pos = box.globalToLocal(dragDetails.globalPosition); final i = _pos.dx / widget.size; var newRating = widget.allowHalfRating ? i : i.round().toDouble(); @@ -126,9 +126,7 @@ class _GFRatingState extends State { if (newRating < 0) { newRating = 0.0; } - if (widget.onChanged != null) { - widget.onChanged(newRating); - } + widget.onChanged(newRating); }, child: icon, ); diff --git a/lib/components/search_bar/gf_search_bar.dart b/lib/components/search_bar/gf_search_bar.dart index 5e9aafe2..4cce9ff6 100644 --- a/lib/components/search_bar/gf_search_bar.dart +++ b/lib/components/search_bar/gf_search_bar.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:getwidget/getwidget.dart'; typedef QueryListItemBuilder = Widget Function(T item); typedef OnItemSelected = void Function(T item); @@ -9,12 +8,12 @@ typedef QueryBuilder = List Function( ); class GFSearchBar extends StatefulWidget { - /// search bar with variuos customization option + /// search bar with various customization option const GFSearchBar({ - @required this.searchList, - @required this.overlaySearchListItemBuilder, - @required this.searchQueryBuilder, - Key key, + required this.searchList, + required this.overlaySearchListItemBuilder, + required this.searchQueryBuilder, + Key? key, this.controller, this.onItemSelected, this.hideSearchBoxWhenItemSelected = false, @@ -33,38 +32,38 @@ class GFSearchBar extends StatefulWidget { final bool hideSearchBoxWhenItemSelected; /// defines the height of [searchList] overlay container - final double overlaySearchListHeight; + final double? overlaySearchListHeight; /// can search and filter the [searchList] final QueryBuilder searchQueryBuilder; /// displays the [Widget] when the search item failed - final Widget noItemsFoundWidget; + final Widget? noItemsFoundWidget; /// defines what to do with onSelect SearchList item - final OnItemSelected onItemSelected; + final OnItemSelected? onItemSelected; /// defines the input decoration of searchBox - final InputDecoration searchBoxInputDecoration; + final InputDecoration? searchBoxInputDecoration; /// defines the input controller of searchBox - final TextEditingController controller; + final TextEditingController? controller; @override MySingleChoiceSearchState createState() => MySingleChoiceSearchState(); } -class MySingleChoiceSearchState extends State> { - List _list; - List _searchList; - bool isFocused; - FocusNode _focusNode; - ValueNotifier notifier; - bool isRequiredCheckFailed; - Widget searchBox; - OverlayEntry overlaySearchList; +class MySingleChoiceSearchState extends State> { + late List _list; + late List _searchList; + bool? isFocused; + late FocusNode _focusNode; + late ValueNotifier notifier; + bool? isRequiredCheckFailed; + Widget? searchBox; + OverlayEntry? overlaySearchList; bool showTextBox = false; - double overlaySearchListHeight; + double? overlaySearchListHeight; final LayerLink _layerLink = LayerLink(); final double textBoxHeight = 48; TextEditingController textController = TextEditingController(); @@ -87,9 +86,9 @@ class MySingleChoiceSearchState extends State> { _focusNode.addListener(() { if (!_focusNode.hasFocus) { textController.clear(); - if (overlaySearchList != null) { - overlaySearchList.remove(); - } + + overlaySearchList?.remove(); + overlaySearchList = null; } else { _searchList @@ -98,7 +97,7 @@ class MySingleChoiceSearchState extends State> { if (overlaySearchList == null) { onTextFieldFocus(); } else { - overlaySearchList.markNeedsBuild(); + overlaySearchList?.markNeedsBuild(); } } }); @@ -106,15 +105,14 @@ class MySingleChoiceSearchState extends State> { final text = textController.text; if (text.trim().isNotEmpty) { _searchList.clear(); - final filterList = widget.searchQueryBuilder(text, widget.searchList); - if (filterList == null) { - throw Exception('List cannot be null'); - } + + final List filterList = + widget.searchQueryBuilder(text, widget.searchList); _searchList.addAll(filterList); if (overlaySearchList == null) { onTextFieldFocus(); } else { - overlaySearchList.markNeedsBuild(); + overlaySearchList?.markNeedsBuild(); } } else { _searchList @@ -123,7 +121,7 @@ class MySingleChoiceSearchState extends State> { if (overlaySearchList == null) { onTextFieldFocus(); } else { - overlaySearchList.markNeedsBuild(); + overlaySearchList?.markNeedsBuild(); } } }); @@ -134,7 +132,8 @@ class MySingleChoiceSearchState extends State> { if (oldWidget.searchList != widget.searchList) { init(); } - super.didUpdateWidget(oldWidget); + // ignore: avoid_as + super.didUpdateWidget(oldWidget as GFSearchBar); } @override @@ -195,10 +194,9 @@ class MySingleChoiceSearchState extends State> { onSearchListItemSelected(null); } - void onSearchListItemSelected(T item) { - if (overlaySearchList != null) { - overlaySearchList.remove(); - } + void onSearchListItemSelected(T? item) { + overlaySearchList?.remove(); + overlaySearchList = null; _focusNode.unfocus(); setState(() { @@ -207,7 +205,7 @@ class MySingleChoiceSearchState extends State> { isRequiredCheckFailed = false; }); if (widget.onItemSelected != null) { - widget.onItemSelected(item); + widget.onItemSelected!(item); } } @@ -215,8 +213,12 @@ class MySingleChoiceSearchState extends State> { setState(() { isSearchBoxSelected = true; }); - final RenderBox searchBoxRenderBox = context.findRenderObject(); - final RenderBox overlay = Overlay.of(context).context.findRenderObject(); + final RenderBox searchBoxRenderBox = + // ignore: avoid_as + context.findRenderObject() as RenderBox; + final RenderBox overlay = + // ignore: avoid_as + Overlay.of(context)?.context.findRenderObject() as RenderBox; final width = searchBoxRenderBox.size.width; final position = RelativeRect.fromRect( Rect.fromPoints( @@ -242,23 +244,27 @@ class MySingleChoiceSearchState extends State> { ), showWhenUnlinked: false, link: _layerLink, - child: GFCard( + child: Card( + margin: const EdgeInsets.all(12), color: Colors.white, elevation: 5, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(4)), ), - content: _searchList.isNotEmpty + child: _searchList.isNotEmpty ? Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ - InkWell( - child: const Icon( - Icons.close, - size: 22, + Padding( + padding: const EdgeInsets.all(8), + child: InkWell( + child: const Icon( + Icons.close, + size: 22, + ), + onTap: onCloseOverlaySearchList, ), - onTap: onCloseOverlaySearchList, ), Container( height: overlaySearchListHeight, @@ -297,6 +303,6 @@ class MySingleChoiceSearchState extends State> { ), ), )); - Overlay.of(context).insert(overlaySearchList); + Overlay.of(context)?.insert(overlaySearchList!); } } diff --git a/lib/components/shimmer/gf_shimmer.dart b/lib/components/shimmer/gf_shimmer.dart index 8fb31cb5..74588ea2 100644 --- a/lib/components/shimmer/gf_shimmer.dart +++ b/lib/components/shimmer/gf_shimmer.dart @@ -5,8 +5,8 @@ import 'package:getwidget/getwidget.dart'; class GFShimmer extends StatefulWidget { /// [GFShimmer] shows shimmer effect. const GFShimmer({ - Key key, - @required this.child, + Key? key, + required this.child, this.gradient, this.direction = GFShimmerDirection.leftToRight, this.duration = const Duration(milliseconds: 1500), @@ -30,7 +30,7 @@ class GFShimmer extends StatefulWidget { /// Controls the [child]'s shades of color using Linear gradient. /// Child [Widget] only takes gradient color, If [showGradient] is true. - final Gradient gradient; + final Gradient? gradient; /// Controls the animation shimmerEffectCount. /// The default value is '0', that displays child [Widget]'s shimmer effect forever. @@ -59,8 +59,8 @@ class GFShimmer extends StatefulWidget { class _GFShimmerState extends State with SingleTickerProviderStateMixin { - AnimationController _controller; - int _count; + late AnimationController _controller; + late int _count; @override void initState() { @@ -97,7 +97,7 @@ class _GFShimmerState extends State Widget build(BuildContext context) => AnimatedBuilder( animation: _controller, child: widget.child, - builder: (BuildContext context, Widget child) => _GFShimmer( + builder: (BuildContext context, Widget? child) => _GFShimmer( child: child, direction: widget.direction, gradient: widget.showGradient @@ -126,9 +126,7 @@ class _GFShimmerState extends State @override void dispose() { - if (_controller != null) { - _controller.dispose(); - } + _controller.dispose(); super.dispose(); } } @@ -136,7 +134,7 @@ class _GFShimmerState extends State @immutable class _GFShimmer extends SingleChildRenderObjectWidget { const _GFShimmer({ - Widget child, + Widget? child, this.controllerValue, this.direction, this.gradient, @@ -144,17 +142,17 @@ class _GFShimmer extends SingleChildRenderObjectWidget { }) : super(child: child); /// value that controls the animation controller - final double controllerValue; + final double? controllerValue; /// Controls the direction of the shimmer effect. /// The default direction is GFShimmerDirection.leftToRight. - final GFShimmerDirection direction; + final GFShimmerDirection? direction; /// Controls the [child]'s shades of color. - final Gradient gradient; + final Gradient? gradient; /// Controls animation effect, defaults true state that makes animation active. - final bool showShimmerEffect; + final bool? showShimmerEffect; @override GFShimmerFilter createRenderObject(BuildContext context) => GFShimmerFilter( @@ -182,27 +180,27 @@ class GFShimmerFilter extends RenderProxyBox { final Paint gradientPaint; /// Controls the [child]'s shades of color. - final Gradient gradient; + final Gradient? gradient; /// Controls the direction of the shimmer effect. /// The default direction is GFShimmerDirection.leftToRight. - final GFShimmerDirection direction; + final GFShimmerDirection? direction; /// Controls animation effect, defaults true state that makes animation active. - bool showShimmerEffect; + bool? showShimmerEffect; /// value that controls the animation controller. - double value; + double? value; /// Construct a rectangle from its left, top, right, and bottom edges. - Rect _rect; + Rect? _rect; @override bool get alwaysNeedsCompositing => child != null; - double get controllerValue => value; + double? get controllerValue => value; - set controllerValue(double newValue) { + set controllerValue(double? newValue) { if (newValue == value) { return; } @@ -217,32 +215,32 @@ class GFShimmerFilter extends RenderProxyBox { } assert(needsCompositing); - context.canvas.saveLayer(offset & child.size, initialPaint); - context.paintChild(child, offset); + context.canvas.saveLayer(offset & child!.size, initialPaint); + context.paintChild(child!, offset); Rect rect; double dx, dy; - final double width = child.size.width; - final double height = child.size.height; + final double width = child!.size.width; + final double height = child!.size.height; if (direction == GFShimmerDirection.leftToRight) { - dx = _offset(-width, width, value); + dx = _offset(-width, width, value!); dy = 0.0; rect = Rect.fromLTWH(offset.dx - width, offset.dy, 3 * width, height); } else if (direction == GFShimmerDirection.bottomToTop) { dx = 0.0; - dy = _offset(height, -height, value); + dy = _offset(height, -height, value!); rect = Rect.fromLTWH(offset.dx, offset.dy - height, width, 3 * height); } else if (direction == GFShimmerDirection.topToBottom) { dx = 0.0; - dy = _offset(-height, height, value); + dy = _offset(-height, height, value!); rect = Rect.fromLTWH(offset.dx, offset.dy - height, width, 3 * height); } else { - dx = _offset(width, -width, value); + dx = _offset(width, -width, value!); dy = 0.0; rect = Rect.fromLTWH(offset.dx - width, offset.dy, 3 * width, height); } if (_rect != rect) { - gradientPaint.shader = gradient.createShader(rect); + gradientPaint.shader = gradient?.createShader(rect); _rect = rect; } context.canvas.translate(dx, dy); diff --git a/lib/components/slidable/gf_slidable.dart b/lib/components/slidable/gf_slidable.dart deleted file mode 100644 index 8b137891..00000000 --- a/lib/components/slidable/gf_slidable.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/components/sticky_header/gf_sticky_header.dart b/lib/components/sticky_header/gf_sticky_header.dart index c28a3b59..434b947a 100644 --- a/lib/components/sticky_header/gf_sticky_header.dart +++ b/lib/components/sticky_header/gf_sticky_header.dart @@ -8,15 +8,14 @@ class GFStickyHeader extends MultiChildRenderObjectWidget { /// GF Sticky Header will the stick header at top when content is being scrolled. /// Place this widget inside a [ListView], [GridView], [CustomScrollView], [SingleChildScrollView] or similar. GFStickyHeader( - {Key key, - @required this.stickyContent, - @required this.content, + {Key? key, + required this.stickyContent, + required this.content, this.direction = Axis.vertical, this.enableHeaderOverlap = false, this.callback, this.stickyContentPosition = GFPosition.start}) - : assert(direction != null), - super( + : super( key: key, children: stickyContentPosition == GFPosition.start && direction == Axis.horizontal @@ -41,7 +40,7 @@ class GFStickyHeader extends MultiChildRenderObjectWidget { final GFPosition stickyContentPosition; /// Allows to add custom stickyHeader stuck offset value - final RenderGFStickyHeaderCallback callback; + final RenderGFStickyHeaderCallback? callback; /// [direction] allows children to align in vertical / horizontal way /// Defaults to [Axis.vertical] @@ -49,8 +48,7 @@ class GFStickyHeader extends MultiChildRenderObjectWidget { @override RenderGFStickyHeader createRenderObject(BuildContext context) { - final scrollable = Scrollable.of(context); - assert(scrollable != null); + final scrollable = Scrollable.of(context)!; return RenderGFStickyHeader( direction: direction, scrollable: scrollable, diff --git a/lib/components/sticky_header/gf_sticky_header_builder.dart b/lib/components/sticky_header/gf_sticky_header_builder.dart index 4ac3992d..fe790dce 100644 --- a/lib/components/sticky_header/gf_sticky_header_builder.dart +++ b/lib/components/sticky_header/gf_sticky_header_builder.dart @@ -10,15 +10,14 @@ class GFStickyHeaderBuilder extends StatefulWidget { /// Place this widget inside a [ListView], [GridView], [CustomScrollView], [SingleChildScrollView] or similar. /// Constructs a new [GFStickyHeaderBuilder] widget. const GFStickyHeaderBuilder({ - Key key, - @required this.stickyContentBuilder, - @required this.content, + Key? key, + required this.stickyContentBuilder, + required this.content, this.direction = Axis.vertical, this.enableHeaderOverlap = false, this.callback, this.stickyContentPosition = GFPosition.start, - }) : assert(direction != null), - super(key: key); + }) : super(key: key); /// builder widget can be used to define [stickyContentBuilder]. final StickyHeaderWidgetBuilder stickyContentBuilder; @@ -35,7 +34,7 @@ class GFStickyHeaderBuilder extends StatefulWidget { final GFPosition stickyContentPosition; /// Allows to add custom stickyHeader stuck offset value - final RenderGFStickyHeaderCallback callback; + final RenderGFStickyHeaderCallback? callback; /// [direction] allows children to align in vertical / horizontal way /// Defaults to [Axis.vertical] @@ -46,7 +45,7 @@ class GFStickyHeaderBuilder extends StatefulWidget { } class _GFStickyHeaderBuilderState extends State { - double _stuckValue; + double? _stuckValue; @override Widget build(BuildContext context) => GFStickyHeader( @@ -61,7 +60,7 @@ class _GFStickyHeaderBuilderState extends State { callback: (double stuckValue) { if (_stuckValue != stuckValue) { _stuckValue = stuckValue; - WidgetsBinding.instance.endOfFrame.then((_) { + WidgetsBinding.instance?.endOfFrame.then((_) { if (mounted) { setState(() {}); } diff --git a/lib/components/sticky_header/render_gf_sticky_header.dart b/lib/components/sticky_header/render_gf_sticky_header.dart index 58b890ec..e3dd905f 100644 --- a/lib/components/sticky_header/render_gf_sticky_header.dart +++ b/lib/components/sticky_header/render_gf_sticky_header.dart @@ -14,15 +14,13 @@ class RenderGFStickyHeader extends RenderBox RenderBoxContainerDefaultsMixin, DebugOverflowIndicatorMixin { RenderGFStickyHeader({ - List children, + List? children, Axis direction = Axis.horizontal, bool enableHeaderOverlap = false, - @required ScrollableState scrollable, - RenderGFStickyHeaderCallback callback, - GFPosition stickyContentPosition, - }) : assert(direction != null), - assert(scrollable != null), - _scrollable = scrollable, + required ScrollableState scrollable, + RenderGFStickyHeaderCallback? callback, + GFPosition? stickyContentPosition, + }) : _scrollable = scrollable, _direction = direction, _callback = callback, _stickyContentPosition = stickyContentPosition, @@ -30,15 +28,14 @@ class RenderGFStickyHeader extends RenderBox addAll(children); } - RenderGFStickyHeaderCallback _callback; + RenderGFStickyHeaderCallback? _callback; final ScrollableState _scrollable; final bool _enableHeaderOverlap; - final GFPosition _stickyContentPosition; + final GFPosition? _stickyContentPosition; Axis get direction => _direction; Axis _direction; set direction(Axis value) { - assert(value != null); if (_direction != value) { _direction = value; markNeedsLayout(); @@ -62,7 +59,8 @@ class RenderGFStickyHeader extends RenderBox } int _getFlex(RenderBox child) { - final FlexParentData childParentData = child.parentData; + // ignore: avoid_as + final FlexParentData childParentData = child.parentData as FlexParentData; return childParentData.flex ?? 0; } @@ -73,8 +71,6 @@ class RenderGFStickyHeader extends RenderBox case Axis.vertical: return child.size.width; } - // ignore: avoid_returning_null - return null; } double _getMainSize(RenderBox child) { @@ -84,8 +80,6 @@ class RenderGFStickyHeader extends RenderBox case Axis.vertical: return child.size.height; } - // ignore: avoid_returning_null - return null; } @override @@ -100,14 +94,14 @@ class RenderGFStickyHeader extends RenderBox super.detach(); } - RenderBox get _stickyContentBody => _stickyContentPosition == + RenderBox? get _stickyContentBody => _stickyContentPosition == GFPosition.start && _direction == Axis.horizontal ? firstChild : _stickyContentPosition == GFPosition.end && _direction == Axis.vertical ? firstChild : lastChild; - RenderBox get _contentBody => _stickyContentPosition == GFPosition.start && + RenderBox? get _contentBody => _stickyContentPosition == GFPosition.start && _direction == Axis.horizontal ? lastChild : _stickyContentPosition == GFPosition.end && _direction == Axis.vertical @@ -115,7 +109,7 @@ class RenderGFStickyHeader extends RenderBox : firstChild; double getHeaderTileStuckOffset() { - final scrollableContent = _scrollable.context.findRenderObject(); + final scrollableContent = _scrollable.context.findRenderObject()!; if (scrollableContent.attached) { try { return localToGlobal(Offset.zero, ancestor: scrollableContent).dy; @@ -130,10 +124,10 @@ class RenderGFStickyHeader extends RenderBox @override void performLayout() { assert(childCount == 2); - _stickyContentBody.layout(constraints.loosen(), parentUsesSize: true); - _contentBody.layout(constraints.loosen(), parentUsesSize: true); - final stickyContentBodyHeight = _stickyContentBody.size.height; - final contentBodyHeight = _contentBody.size.height; + _stickyContentBody?.layout(constraints.loosen(), parentUsesSize: true); + _contentBody?.layout(constraints.loosen(), parentUsesSize: true); + final stickyContentBodyHeight = _stickyContentBody!.size.height; + final contentBodyHeight = _contentBody!.size.height; final height = max( constraints.minHeight, _enableHeaderOverlap @@ -144,18 +138,19 @@ class RenderGFStickyHeader extends RenderBox size = Size( constraints.constrainWidth(width), constraints.constrainHeight(height)); final double stickyContentBodyOffset = getHeaderTileStuckOffset(); - assert(constraints != null); double crossSize = 0; double allottedSize = 0; - RenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { // ignore: avoid_as - final FlexParentData childParentData = child.parentData as FlexParentData; + final FlexParentData? childParentData = + // ignore: avoid_as + child.parentData as FlexParentData?; final int flex = _getFlex(child); if (flex > 0) { } else { - BoxConstraints innerConstraints; + late BoxConstraints innerConstraints; switch (_direction) { case Axis.horizontal: innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight); @@ -169,11 +164,11 @@ class RenderGFStickyHeader extends RenderBox crossSize = math.max(crossSize, _getCrossSize(child)); } assert(child.parentData == childParentData); - child = childParentData.nextSibling; + child = childParentData?.nextSibling; } final double idealSize = allottedSize; - double actualSize; + late double actualSize; switch (_direction) { case Axis.horizontal: size = constraints.constrain(Size(idealSize, crossSize)); @@ -190,25 +185,31 @@ class RenderGFStickyHeader extends RenderBox const double betweenSpace = 0; const bool flipMainAxis = !true; double childMainPosition = + // ignore: dead_code flipMainAxis ? actualSize - startingSpace : startingSpace; child = _contentBody; // ignore: invariant_booleans while (child != null) { // ignore: avoid_as - final FlexParentData childParentData = child.parentData as FlexParentData; + final FlexParentData? childParentData = + // ignore: avoid_as + child.parentData as FlexParentData?; - if (flipMainAxis) { - childMainPosition = _getMainSize(child); - } + // if (flipMainAxis) { + // childMainPosition = _getMainSize(child); + // } switch (_direction) { case Axis.horizontal: - final FlexParentData contentBodyParentData = _contentBody.parentData; + final FlexParentData contentBodyParentData = + // ignore: avoid_as + _contentBody!.parentData as FlexParentData; contentBodyParentData.offset = _stickyContentPosition == GFPosition.start - ? Offset(_stickyContentBody.size.width, 0) + ? Offset(_stickyContentBody!.size.width, 0) : const Offset(0, 0); final FlexParentData stickyContentBodyParentData = - _stickyContentBody.parentData; + // ignore: avoid_as + _stickyContentBody!.parentData as FlexParentData; stickyContentBodyParentData.offset = Offset( childMainPosition, max( @@ -217,11 +218,14 @@ class RenderGFStickyHeader extends RenderBox 0)); break; case Axis.vertical: - final FlexParentData contentBodyParentData = _contentBody.parentData; + final FlexParentData contentBodyParentData = + // ignore: avoid_as + _contentBody!.parentData as FlexParentData; contentBodyParentData.offset = Offset(0, _enableHeaderOverlap ? 0.0 : stickyContentBodyHeight); final FlexParentData stickyContentBodyParentData = - _stickyContentBody.parentData; + // ignore: avoid_as + _stickyContentBody!.parentData as FlexParentData; stickyContentBodyParentData.offset = Offset( 0, max( @@ -235,19 +239,20 @@ class RenderGFStickyHeader extends RenderBox min(stickyContentBodyHeight, stickyContentBodyOffset), -stickyContentBodyHeight) / stickyContentBodyHeight; - _callback(stuckValue); + _callback!(stuckValue); } - if (flipMainAxis) { - childMainPosition -= betweenSpace; - } else { + // if (flipMainAxis) { + // childMainPosition -= betweenSpace; + // } + if (!flipMainAxis) { childMainPosition += _getMainSize(child) + betweenSpace; } - child = childParentData.nextSibling; + child = childParentData!.nextSibling; } } @override - bool hitTestChildren(BoxHitTestResult result, {Offset position}) => + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) => defaultHitTestChildren(result, position: position); @override diff --git a/lib/components/tabs/gf_segment_tabs.dart b/lib/components/tabs/gf_segment_tabs.dart index e5adde9b..f5c58b29 100644 --- a/lib/components/tabs/gf_segment_tabs.dart +++ b/lib/components/tabs/gf_segment_tabs.dart @@ -9,45 +9,38 @@ import 'package:getwidget/getwidget.dart'; class GFSegmentTabs extends StatefulWidget { /// Creates pills like structured tab bar. See [GFTabBar] /// [GFSegmentTabs] are best used as an alternative for [GFTabBar]. - const GFSegmentTabs( - {Key key, - @required this.length, - // this.initialIndex = 0, - this.height, - this.width, - this.border, - this.borderRadius, - this.tabBarColor, - this.indicatorColor, - this.indicatorWeight = 2.0, - this.indicatorPadding = EdgeInsets.zero, - this.indicator, - this.indicatorSize, - this.labelColor, - this.labelStyle, - this.labelPadding, - this.unselectedLabelColor, - this.unselectedLabelStyle, - this.tabs, - this.tabController}) - : assert(length != null && length >= 0), - // assert(initialIndex != null && - // initialIndex >= 0 && - // (length == 0 || initialIndex < length)), + const GFSegmentTabs({ + Key? key, + required this.length, + required this.tabs, + required this.tabController, + this.height, + this.width, + this.border, + this.borderRadius, + this.tabBarColor, + this.indicatorColor, + this.indicatorWeight = 2.0, + this.indicatorPadding = EdgeInsets.zero, + this.indicator, + this.indicatorSize, + this.labelColor, + this.labelStyle, + this.labelPadding, + this.unselectedLabelColor, + this.unselectedLabelStyle, + }) : assert(length != null && length >= 0), super(key: key); - // /// The initial index of the selected tab. Defaults to zero. - // final int initialIndex; - /// The total number of tabs. Typically greater than one. Must match [TabBar.tabs]'s and /// [TabBarView.children]'s length. - final int length; + final int? length; /// Sets [GFSegmentTabs] height - final double height; + final double? height; /// Sets [TabBar] color using material color [Color] - final Color tabBarColor; + final Color? tabBarColor; /// The color of the line that appears below the selected tab. /// @@ -55,7 +48,7 @@ class GFSegmentTabs extends StatefulWidget { /// property is used. /// /// If [indicator] is specified, this property is ignored. - final Color indicatorColor; + final Color? indicatorColor; /// The thickness of the line that appears below the selected tab. /// @@ -92,7 +85,7 @@ class GFSegmentTabs extends StatefulWidget { /// occupied by the tab in the tab bar. If [indicatorSize] is /// [TabBarIndicatorSize.label], then the tab's bounds are only as wide as /// the tab widget itself. - final Decoration indicator; + final Decoration? indicator; /// Defines how the selected tab indicator's size is computed. /// @@ -104,7 +97,7 @@ class GFSegmentTabs extends StatefulWidget { /// The selected tab's location appearance can be refined further with /// the [indicatorColor], [indicatorWeight], [indicatorPadding], and /// [indicator] properties. - final TabBarIndicatorSize indicatorSize; + final TabBarIndicatorSize? indicatorSize; /// The color of selected tab labels. /// @@ -113,13 +106,13 @@ class GFSegmentTabs extends StatefulWidget { /// /// If this parameter is null, then the color of the [ThemeData.primaryTextTheme]'s /// body2 text color is used. - final Color labelColor; + final Color? labelColor; /// The color of unselected tab labels. /// /// If this property is null, unselected tab labels are rendered with the /// [labelColor] with 70% opacity. - final Color unselectedLabelColor; + final Color? unselectedLabelColor; /// The text style of the selected tab labels. /// @@ -128,19 +121,19 @@ class GFSegmentTabs extends StatefulWidget { /// /// If this property is null, then the text style of the /// [ThemeData.primaryTextTheme]'s body2 definition is used. - final TextStyle labelStyle; + final TextStyle? labelStyle; /// The padding added to each of the tab labels. /// /// If this property is null, then kTabLabelPadding is used. - final EdgeInsetsGeometry labelPadding; + final EdgeInsetsGeometry? labelPadding; /// The text style of the unselected tab labels /// /// If this property is null, then the [labelStyle] value is used. If [labelStyle] /// is null, then the text style of the [ThemeData.primaryTextTheme]'s /// body2 definition is used. - final TextStyle unselectedLabelStyle; + final TextStyle? unselectedLabelStyle; /// Typically a list of two or more [Tab] widgets. /// @@ -149,19 +142,19 @@ class GFSegmentTabs extends StatefulWidget { final List tabs; /// defines outer border of the tabs - final Border border; + final Border? border; /// defines outer border, border radius - final BorderRadius borderRadius; + final BorderRadius? borderRadius; /// This widget's selection and animation state. /// /// If [TabController] is not provided, then the value of [DefaultTabController.of] /// will be used. - final TabController tabController; + final TabController? tabController; /// defines width of the tab row - final double width; + final double? width; @override _GFSegmentTabsState createState() => _GFSegmentTabsState(); @@ -179,7 +172,7 @@ class _GFSegmentTabsState extends State { ), child: DefaultTabController( // initialIndex: widget.initialIndex, - length: widget.length, + length: widget.length!, child: Material( borderRadius: widget.borderRadius ?? BorderRadius.circular(2), type: MaterialType.button, diff --git a/lib/components/tabs/gf_tabbar.dart b/lib/components/tabs/gf_tabbar.dart index 1e856bd0..f5fe92cd 100644 --- a/lib/components/tabs/gf_tabbar.dart +++ b/lib/components/tabs/gf_tabbar.dart @@ -14,9 +14,10 @@ class GFTabBar extends StatefulWidget { /// The [tabs] argument must not be null and its length must match the [controller]'s /// [TabController.length]. const GFTabBar({ - Key key, - // this.initialIndex = 0, - @required this.length, + Key? key, + required this.length, + required this.tabs, + required this.controller, this.isScrollable = false, this.tabBarHeight, this.tabBarColor, @@ -30,28 +31,19 @@ class GFTabBar extends StatefulWidget { this.labelPadding, this.unselectedLabelColor, this.unselectedLabelStyle, - this.tabs, - this.controller, this.shape, }) : assert(length != null && length >= 0), - assert(isScrollable != null), - // assert(initialIndex != null && - // initialIndex >= 0 && - // (length == 0 || initialIndex < length)), super(key: key); - // /// The initial index of the selected tab. Defaults to zero. - // final int initialIndex; - /// The total number of tabs. Typically greater than one. Must match [TabBar.tabs]'s and /// [TabBarView.children]'s length. - final int length; + final int? length; /// Sets [GFTabBar] height - final double tabBarHeight; + final double? tabBarHeight; /// Sets [TabBar] color using material color [Color] - final Color tabBarColor; + final Color? tabBarColor; /// The color of the line that appears below the selected tab. /// @@ -59,7 +51,7 @@ class GFTabBar extends StatefulWidget { /// property is used. /// /// If [indicator] is specified, this property is ignored. - final Color indicatorColor; + final Color? indicatorColor; /// The thickness of the line that appears below the selected tab. /// @@ -96,7 +88,7 @@ class GFTabBar extends StatefulWidget { /// occupied by the tab in the tab bar. If [indicatorSize] is /// [TabBarIndicatorSize.label], then the tab's bounds are only as wide as /// the tab widget itself. - final Decoration indicator; + final Decoration? indicator; /// Defines how the selected tab indicator's size is computed. /// @@ -108,7 +100,7 @@ class GFTabBar extends StatefulWidget { /// The selected tab's location appearance can be refined further with /// the [indicatorColor], [indicatorWeight], [indicatorPadding], and /// [indicator] properties. - final TabBarIndicatorSize indicatorSize; + final TabBarIndicatorSize? indicatorSize; /// The color of selected tab labels. /// @@ -117,13 +109,13 @@ class GFTabBar extends StatefulWidget { /// /// If this parameter is null, then the color of the [ThemeData.primaryTextTheme]'s /// body2 text color is used. - final Color labelColor; + final Color? labelColor; /// The color of unselected tab labels. /// /// If this property is null, unselected tab labels are rendered with the /// [labelColor] with 70% opacity. - final Color unselectedLabelColor; + final Color? unselectedLabelColor; /// The text style of the selected tab labels. /// @@ -132,19 +124,19 @@ class GFTabBar extends StatefulWidget { /// /// If this property is null, then the text style of the /// [ThemeData.primaryTextTheme]'s body2 definition is used. - final TextStyle labelStyle; + final TextStyle? labelStyle; /// The padding added to each of the tab labels. /// /// If this property is null, then kTabLabelPadding is used. - final EdgeInsetsGeometry labelPadding; + final EdgeInsetsGeometry? labelPadding; /// The text style of the unselected tab labels /// /// If this property is null, then the [labelStyle] value is used. If [labelStyle] /// is null, then the text style of the [ThemeData.primaryTextTheme]'s /// body2 definition is used. - final TextStyle unselectedLabelStyle; + final TextStyle? unselectedLabelStyle; /// Typically a list of two or more [Tab] widgets. /// @@ -163,17 +155,17 @@ class GFTabBar extends StatefulWidget { /// /// If [TabController] is not provided, then the value of [DefaultTabController.of] /// will be used. - final TabController controller; + final TabController? controller; /// defines the shape of tabBar - final ShapeBorder shape; + final ShapeBorder? shape; @override _GFTabBarState createState() => _GFTabBarState(); } class _GFTabBarState extends State { - ScrollController _scrollController; + ScrollController? _scrollController; DragStartBehavior dragStartBehavior = DragStartBehavior.start; @override diff --git a/lib/components/tabs/gf_tabbar_view.dart b/lib/components/tabs/gf_tabbar_view.dart index a4e6959c..bbab8342 100644 --- a/lib/components/tabs/gf_tabbar_view.dart +++ b/lib/components/tabs/gf_tabbar_view.dart @@ -11,18 +11,16 @@ class GFTabBarView extends StatefulWidget { /// Creates a page view with one child per tab. /// The length of [children] must be the same as the [controller]'s length. const GFTabBarView({ - Key key, - @required this.children, - this.controller, + Key? key, + required this.children, + required this.controller, this.physics, this.height, this.dragStartBehavior = DragStartBehavior.start, - }) : assert(children != null), - assert(dragStartBehavior != null), - super(key: key); + }) : super(key: key); /// This widget's selection and animation state. - final TabController controller; + final TabController? controller; /// One widget per tab. /// Its length must match the length of the [GFTabBar.tabs] @@ -35,13 +33,13 @@ class GFTabBarView extends StatefulWidget { /// The physics are modified to snap to page boundaries using /// [PageScrollPhysics] prior to being used. /// Defaults to matching platform conventions. - final ScrollPhysics physics; + final ScrollPhysics? physics; /// {@macro flutter.widgets.scrollable.dragStartBehavior} final DragStartBehavior dragStartBehavior; /// [GFTabBarView] height can be fixed using [double] - final double height; + final double? height; @override _GFTabBarViewState createState() => _GFTabBarViewState(); @@ -51,11 +49,11 @@ final PageScrollPhysics _kGFTabBarViewPhysics = const PageScrollPhysics().applyTo(const ClampingScrollPhysics()); class _GFTabBarViewState extends State { - TabController _controller; - PageController _pageController; - List _children; - List _childrenWithKey; - int _currentIndex; + TabController? _controller; + PageController? _pageController; + List? _children; + List? _childrenWithKey; + int? _currentIndex; int _warpUnderwayCount = 0; // If the GFTabBarView is rebuilt with a new tab controller, the caller should @@ -64,7 +62,7 @@ class _GFTabBarViewState extends State { bool get _controllerIsValid => _controller?.animation != null; void _updateTabController() { - final TabController newController = + final TabController? newController = widget.controller ?? DefaultTabController.of(context); assert(() { if (newController == null) { @@ -80,11 +78,11 @@ class _GFTabBarViewState extends State { return; } if (_controllerIsValid) { - _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller?.animation?.removeListener(_handleTabControllerAnimationTick); } _controller = newController; if (_controller != null) { - _controller.animation.addListener(_handleTabControllerAnimationTick); + _controller?.animation?.addListener(_handleTabControllerAnimationTick); } } @@ -116,7 +114,7 @@ class _GFTabBarViewState extends State { @override void dispose() { if (_controllerIsValid) { - _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller?.animation?.removeListener(_handleTabControllerAnimationTick); } _controller = null; // We don't own the _controller Animation, so it's not disposed here. @@ -129,44 +127,47 @@ class _GFTabBarViewState extends State { } void _handleTabControllerAnimationTick() { - if (_warpUnderwayCount > 0 || !_controller.indexIsChanging) { - return; - } // This widget is driving the controller's animation. - if (_controller.index != _currentIndex) { - _currentIndex = _controller.index; - _warpToCurrentIndex(); + if (_controller != null) { + if (_warpUnderwayCount > 0 || !_controller!.indexIsChanging) { + return; + } // This widget is driving the controller's animation. + if (_controller!.index != _currentIndex) { + _currentIndex = _controller!.index; + _warpToCurrentIndex(); + } } } Future _warpToCurrentIndex() async { - if (!mounted) { + if (!mounted || _pageController == null || _currentIndex == null) { return Future.value(); } - if (_pageController.page == _currentIndex.toDouble()) { + if (_pageController!.page == _currentIndex!.toDouble()) { return Future.value(); } - final int previousIndex = _controller.previousIndex; - if ((_currentIndex - previousIndex).abs() == 1) { - return _pageController.animateToPage(_currentIndex, + final int previousIndex = _controller!.previousIndex; + if ((_currentIndex! - previousIndex).abs() == 1) { + return _pageController?.animateToPage(_currentIndex!, duration: kTabScrollDuration, curve: Curves.ease); } - assert((_currentIndex - previousIndex).abs() > 1); - final int initialPage = - _currentIndex > previousIndex ? _currentIndex - 1 : _currentIndex + 1; - final List originalChildren = _childrenWithKey; + assert((_currentIndex! - previousIndex).abs() > 1); + final int initialPage = _currentIndex! > previousIndex + ? _currentIndex! - 1 + : _currentIndex! + 1; + final List? originalChildren = _childrenWithKey; setState(() { _warpUnderwayCount += 1; - _childrenWithKey = List.from(_childrenWithKey, growable: false); - final Widget temp = _childrenWithKey[initialPage]; - _childrenWithKey[initialPage] = _childrenWithKey[previousIndex]; - _childrenWithKey[previousIndex] = temp; + _childrenWithKey = List.from(_childrenWithKey!, growable: false); + final Widget temp = _childrenWithKey![initialPage]; + _childrenWithKey![initialPage] = _childrenWithKey![previousIndex]; + _childrenWithKey![previousIndex] = temp; }); - _pageController.jumpToPage(initialPage); + _pageController?.jumpToPage(initialPage); - await _pageController.animateToPage(_currentIndex, + await _pageController?.animateToPage(_currentIndex!, duration: kTabScrollDuration, curve: Curves.ease); if (!mounted) { return Future.value(); @@ -189,19 +190,25 @@ class _GFTabBarViewState extends State { if (notification.depth != 0) { return false; } + if (_controller == null || + _pageController == null || + _pageController?.page != null || + _controller?.index == null) { + return false; + } _warpUnderwayCount += 1; if (notification is ScrollUpdateNotification && - !_controller.indexIsChanging) { - if ((_pageController.page - _controller.index).abs() > 1.0) { - _controller.index = _pageController.page.floor(); - _currentIndex = _controller.index; + !_controller!.indexIsChanging) { + if ((_pageController!.page! - _controller!.index).abs() > 1.0) { + _controller!.index = _pageController!.page!.floor(); + _currentIndex = _controller!.index; } - _controller.offset = - (_pageController.page - _controller.index).clamp(-1.0, 1.0); + _controller!.offset = + (_pageController!.page! - _controller!.index).clamp(-1.0, 1.0); } else if (notification is ScrollEndNotification) { - _controller.index = _pageController.page.round(); - _currentIndex = _controller.index; + _controller!.index = _pageController!.page!.round(); + _currentIndex = _controller!.index; } _warpUnderwayCount -= 1; return false; @@ -210,9 +217,9 @@ class _GFTabBarViewState extends State { @override Widget build(BuildContext context) { assert(() { - if (_controller.length != widget.children.length) { + if (_controller?.length != widget.children.length) { throw FlutterError( - 'Controller\'s length property (${_controller.length}) does not match the \n' + 'Controller\'s length property (${_controller?.length}) does not match the \n' 'number of tabs (${widget.children.length}) present in TabBar\'s tabs property.'); } return true; @@ -227,7 +234,7 @@ class _GFTabBarViewState extends State { physics: widget.physics == null ? _kGFTabBarViewPhysics : _kGFTabBarViewPhysics.applyTo(widget.physics), - children: _childrenWithKey, + children: _childrenWithKey!, ), ), ); diff --git a/lib/components/tabs/gf_tabs.dart b/lib/components/tabs/gf_tabs.dart index 033f8e73..99be239d 100644 --- a/lib/components/tabs/gf_tabs.dart +++ b/lib/components/tabs/gf_tabs.dart @@ -12,9 +12,12 @@ class GFTabs extends StatefulWidget { /// /// The [initialIndex] argument must not be null. const GFTabs({ - Key key, + Key? key, this.initialIndex = 0, - @required this.length, + required this.length, + required this.tabs, + required this.tabBarView, + required this.controller, this.isScrollable = false, this.height, this.tabBarColor, @@ -28,29 +31,26 @@ class GFTabs extends StatefulWidget { this.labelPadding, this.unselectedLabelColor, this.unselectedLabelStyle, - this.tabBarView, - this.tabs, - this.controller, this.tabBarHeight, this.shape, - }) : assert(length != null && length >= 0), + }) : assert(length >= 0), assert(initialIndex != null && initialIndex >= 0 && (length == 0 || initialIndex < length)), super(key: key); /// The initial index of the selected tab. Defaults to zero. - final int initialIndex; + final int? initialIndex; /// The total number of tabs. Typically greater than one. Must match [TabBar.tabs]'s and /// [TabBarView.children]'s length. final int length; /// Sets [GFTabs] height - final double height; + final double? height; /// Sets [TabBar] color using material color [Color] - final Color tabBarColor; + final Color? tabBarColor; /// The color of the line that appears below the selected tab. /// @@ -58,7 +58,7 @@ class GFTabs extends StatefulWidget { /// property is used. /// /// If [indicator] is specified, this property is ignored. - final Color indicatorColor; + final Color? indicatorColor; /// The thickness of the line that appears below the selected tab. /// @@ -95,7 +95,7 @@ class GFTabs extends StatefulWidget { /// occupied by the tab in the tab bar. If [indicatorSize] is /// [TabBarIndicatorSize.label], then the tab's bounds are only as wide as /// the tab widget itself. - final Decoration indicator; + final Decoration? indicator; /// Defines how the selected tab indicator's size is computed. /// @@ -107,7 +107,7 @@ class GFTabs extends StatefulWidget { /// The selected tab's location appearance can be refined further with /// the [indicatorColor], [indicatorWeight], [indicatorPadding], and /// [indicator] properties. - final TabBarIndicatorSize indicatorSize; + final TabBarIndicatorSize? indicatorSize; /// The color of selected tab labels. /// @@ -116,13 +116,13 @@ class GFTabs extends StatefulWidget { /// /// If this parameter is null, then the color of the [ThemeData.primaryTextTheme]'s /// body2 text color is used. - final Color labelColor; + final Color? labelColor; /// The color of unselected tab labels. /// /// If this property is null, unselected tab labels are rendered with the /// [labelColor] with 70% opacity. - final Color unselectedLabelColor; + final Color? unselectedLabelColor; /// The text style of the selected tab labels. /// @@ -131,19 +131,19 @@ class GFTabs extends StatefulWidget { /// /// If this property is null, then the text style of the /// [ThemeData.primaryTextTheme]'s body2 definition is used. - final TextStyle labelStyle; + final TextStyle? labelStyle; /// The padding added to each of the tab labels. /// /// If this property is null, then kTabLabelPadding is used. - final EdgeInsetsGeometry labelPadding; + final EdgeInsetsGeometry? labelPadding; /// The text style of the unselected tab labels /// /// If this property is null, then the [labelStyle] value is used. If [labelStyle] /// is null, then the text style of the [ThemeData.primaryTextTheme]'s /// body2 definition is used. - final TextStyle unselectedLabelStyle; + final TextStyle? unselectedLabelStyle; /// One widget per tab. /// Its length must match the length of the [GFTabs.tabs] @@ -163,10 +163,10 @@ class GFTabs extends StatefulWidget { final TabController controller; /// defines the tabBar height - final double tabBarHeight; + final double? tabBarHeight; /// defines the shape of tabBar - final ShapeBorder shape; + final ShapeBorder? shape; /// Whether this tab bar can be scrolled horizontally. /// @@ -183,7 +183,7 @@ class _GFTabsState extends State { @override Widget build(BuildContext context) => Container( child: DefaultTabController( - initialIndex: widget.initialIndex, + initialIndex: widget.initialIndex!, length: widget.length, child: Container( height: widget.height ?? MediaQuery.of(context).size.height * 0.5, diff --git a/lib/components/toast/gf_toast.dart b/lib/components/toast/gf_toast.dart index 030444b5..1b50bea8 100644 --- a/lib/components/toast/gf_toast.dart +++ b/lib/components/toast/gf_toast.dart @@ -7,7 +7,7 @@ class GFToast extends StatefulWidget { ///Creates [GFToast] that can be used to display quick warning or error messages. /// Toast has to be wrap inside the body like [GFFloatingWidget]. See [GFFloatingWidget] const GFToast({ - Key key, + Key? key, this.child, this.button, this.backgroundColor, @@ -23,22 +23,22 @@ class GFToast extends StatefulWidget { }) : super(key: key); /// child of type [Widget]is alternative to text key. text will get priority over child - final Widget child; + final Widget? child; /// button of type [Widget],or you can use [GFButton] for easy implementation with [GFToast] - final Widget button; + final Widget? button; ///pass color of type [Color] or [GFColors] for background of [GFToast] - final Color backgroundColor; + final Color? backgroundColor; /// text of type [String] is alternative to child. text will get priority over child - final String text; + final String? text; /// textStyle of type [textStyle] will be applicable to text only and not for the child final TextStyle textStyle; /// width of type [double] used to control the width of the [GFToast] - final double width; + final double? width; ///type of [GFToastType] which takes the type ie, basic, rounded and fullWidth for the [GFToast] final GFToastType type; @@ -56,15 +56,16 @@ class GFToast extends StatefulWidget { final Duration autoDismissDuration; /// type of [Alignment] used to align the text inside the toast - final Alignment alignment; + final Alignment? alignment; @override _GFToastState createState() => _GFToastState(); } class _GFToastState extends State with TickerProviderStateMixin { - AnimationController animationController, fadeAnimationController; - Animation animation, fadeAnimation; + late AnimationController animationController, fadeAnimationController; + late Animation animation, fadeAnimation; + Timer? timer; bool hideToast = false; @override @@ -85,7 +86,7 @@ class _GFToastState extends State with TickerProviderStateMixin { begin: 0, end: 1, ).animate(fadeAnimationController); - Timer(widget.duration, () { + timer = Timer(widget.duration, () { if (mounted) { fadeAnimationController.forward(); } @@ -110,6 +111,7 @@ class _GFToastState extends State with TickerProviderStateMixin { void dispose() { animationController.dispose(); fadeAnimationController.dispose(); + timer?.cancel(); super.dispose(); } @@ -150,7 +152,7 @@ class _GFToastState extends State with TickerProviderStateMixin { child: Align( alignment: widget.alignment ?? Alignment.topLeft, child: widget.text != null - ? Text(widget.text, style: widget.textStyle) + ? Text(widget.text!, style: widget.textStyle) : (widget.child ?? Container()), ), ), diff --git a/lib/components/toggle/gf_toggle.dart b/lib/components/toggle/gf_toggle.dart index 248e9bfa..3e272362 100644 --- a/lib/components/toggle/gf_toggle.dart +++ b/lib/components/toggle/gf_toggle.dart @@ -14,9 +14,9 @@ import 'package:getwidget/getwidget.dart'; class GFToggle extends StatefulWidget { /// Creates toggle button to switch between states onChanged. const GFToggle( - {Key key, - @required this.onChanged, - this.value, + {Key? key, + required this.onChanged, + required this.value, this.enabledText, this.disabledText, this.enabledTextStyle, @@ -32,63 +32,62 @@ class GFToggle extends StatefulWidget { : super(key: key); ///type [String] used to add custom text i.e, ON,ENABLE - final String enabledText; + final String? enabledText; ///type [String] used to add custom text i.e, ON,DISABLE - final String disabledText; + final String? disabledText; ///type of [TextStyle] used to define the style properties of the enabled text - final TextStyle enabledTextStyle; + final TextStyle? enabledTextStyle; ///type of [TextStyle] used to define the style peoperties of the disabled text - final TextStyle disabledTextStyle; + final TextStyle? disabledTextStyle; ///type of [Color] used for the active thumb color - final Color enabledThumbColor; + final Color? enabledThumbColor; ///type of [Color] used for the inactive thumb color - final Color disabledThumbColor; + final Color? disabledThumbColor; ///type of [Color] used for the active track color - final Color enabledTrackColor; + final Color? enabledTrackColor; ///type of [Color] used for the inactive thumb color - final Color disabledTrackColor; + final Color? disabledTrackColor; ///type of [BoxShape] used to define shapes i.e, Circle , Rectangle - final BoxShape boxShape; + final BoxShape? boxShape; ///type of [BorderRadius] used to define the radius of the Container - final BorderRadius borderRadius; + final BorderRadius? borderRadius; ///type of animation [Duration] called when the switch animates during the specific duration final Duration duration; /// Button type of [GFToggleType] i.e, android, ios, custom, sqaure - final GFToggleType type; + final GFToggleType? type; /// This property must not be null. Used to set the current state of toggle final bool value; /// Called when the user toggles the switch on or off. - final ValueChanged onChanged; + final ValueChanged onChanged; @override _GFToggleState createState() => _GFToggleState(); } class _GFToggleState extends State with TickerProviderStateMixin { - AnimationController animationController; - Animation animation; - AnimationController controller; - Animation offset; - bool isOn; + AnimationController? animationController; + Animation? animation; + late AnimationController controller; + late Animation offset; + late bool isOn; @override void initState() { - setState(() { - isOn = widget.value ?? false; - }); + isOn = widget.value; + controller = AnimationController(duration: widget.duration, vsync: this); offset = (isOn ? Tween( @@ -105,31 +104,26 @@ class _GFToggleState extends State with TickerProviderStateMixin { @override void dispose() { - if (animationController != null) { - animationController.dispose(); - } - if (controller != null) { - controller.dispose(); - } + animationController?.dispose(); + + controller.dispose(); super.dispose(); } void onStatusChange() { - if (widget.onChanged != null) { - setState(() { - isOn = !isOn; - }); - switch (controller.status) { - case AnimationStatus.dismissed: - controller.forward(); - break; - case AnimationStatus.completed: - controller.reverse(); - break; - default: - } - widget.onChanged(isOn); + setState(() { + isOn = !isOn; + }); + switch (controller.status) { + case AnimationStatus.dismissed: + controller.forward(); + break; + case AnimationStatus.completed: + controller.reverse(); + break; + default: } + widget.onChanged(isOn); } @override diff --git a/lib/components/typography/gf_typography.dart b/lib/components/typography/gf_typography.dart index d1eac57b..fed7f2fc 100644 --- a/lib/components/typography/gf_typography.dart +++ b/lib/components/typography/gf_typography.dart @@ -5,10 +5,10 @@ class GFTypography extends StatelessWidget { /// Creates simple title with underline. Style of title can be changed using [GFTypographyType] /// showDivider is default true, can be set false. const GFTypography({ - Key key, + Key? key, this.type = GFTypographyType.typo4, this.child, - this.text, + required this.text, this.icon, this.dividerBorderRadius, this.textColor, @@ -18,48 +18,49 @@ class GFTypography extends StatelessWidget { this.dividerWidth, this.backgroundImage, this.backgroundImagecolorFilter, - }) : super(key: key); + }) : assert(text != null || child != null), + super(key: key); /// child of type [Widget] is alternative to text key. text will get priority over child - final Widget child; + final Widget? child; /// text of type [String] is alternative to child. text will get priority over child - final String text; + final String? text; ///icon of type [Widget] used to pass icon or image - final Widget icon; + final Widget? icon; /// Pass [GFColors] or [Color] for dividerColor - final Color dividerColor; + final Color? dividerColor; /// Pass [GFColors] or [Color] for textColor - final Color textColor; + final Color? textColor; /// dividerBorderRadius of type [BorderRadius] to alter the radius of the divider - final BorderRadius dividerBorderRadius; + final BorderRadius? dividerBorderRadius; ///dividerAlignment of type [Alignment] used for aligning the divider to required alignment - final Alignment dividerAlignment; + final Alignment? dividerAlignment; ///Pass [bool] value to show or hide the divider final bool showDivider; ///pass [double] type to increase or decrease the width of the divider - final double dividerWidth; + final double? dividerWidth; ///backgroundImage of type [ImageProvider] to set the background of [GFTypography] - final ImageProvider backgroundImage; + final ImageProvider? backgroundImage; ///backgroundImagecolorFilter of type [ColorFilter] to set the ///background color of [GFTypography] only when backgroundImage is available - final ColorFilter backgroundImagecolorFilter; + final ColorFilter? backgroundImagecolorFilter; /// header type of [GFTypographyType] i.e, typo1, typo2, typo3, typo4, typo5, typo6 final GFTypographyType type; @override Widget build(BuildContext context) { - double fontSize; + double? fontSize; if (type == GFTypographyType.typo1) { fontSize = 25.0; @@ -80,7 +81,7 @@ class GFTypography extends StatelessWidget { decoration: BoxDecoration( image: backgroundImage != null ? DecorationImage( - image: backgroundImage, + image: backgroundImage!, fit: BoxFit.cover, colorFilter: backgroundImagecolorFilter ?? const ColorFilter.mode(Colors.black54, BlendMode.darken), @@ -99,7 +100,7 @@ class GFTypography extends StatelessWidget { text != null ? Expanded( child: Text( - text, + text!, style: TextStyle( color: textColor ?? (backgroundImage != null @@ -110,10 +111,10 @@ class GFTypography extends StatelessWidget { fontWeight: FontWeight.w500), ), ) - : child + : child! ], ), - showDivider + showDivider && fontSize != null ? Container( margin: const EdgeInsets.only(top: 3, bottom: 3), alignment: dividerAlignment, diff --git a/lib/getwidget.dart b/lib/getwidget.dart index c6c38e86..0efb5b59 100644 --- a/lib/getwidget.dart +++ b/lib/getwidget.dart @@ -1,7 +1,7 @@ library getwidget; //exports components -export 'package:getwidget/components/accordian/gf_accordian.dart'; +export 'package:getwidget/components/accordion/gf_accordion.dart'; export 'package:getwidget/components/alert/gf_alert.dart'; export 'package:getwidget/components/animation/gf_animation.dart'; export 'package:getwidget/components/appbar/gf_appbar.dart'; @@ -36,7 +36,6 @@ export 'package:getwidget/components/radio_list_tile/gf_radio_list_tile.dart'; export 'package:getwidget/components/rating/gf_rating.dart'; export 'package:getwidget/components/search_bar/gf_search_bar.dart'; export 'package:getwidget/components/shimmer/gf_shimmer.dart'; -export 'package:getwidget/components/slidable/gf_slidable.dart'; export 'package:getwidget/components/sticky_header/gf_sticky_header.dart'; export 'package:getwidget/components/sticky_header/gf_sticky_header_builder.dart'; export 'package:getwidget/components/sticky_header/render_gf_sticky_header.dart'; diff --git a/pubspec.lock b/pubspec.lock index b308ac32..49a0ce66 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,49 +7,49 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -66,21 +66,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -92,55 +92,55 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" sdks: - dart: ">=2.10.0-110 <2.11.0" + dart: ">=2.12.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 27978722..5171bb78 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: getwidget -description: GetWidget is open source libraries that come with pre-build 1000+ UI components. It makes development faster & more enjoyable. You can customize the component as per your need. -version: 1.2.4 +description: GetWidget is open source library that come with pre-build 1000+ UI components. It makes development faster & more enjoyable. You can customize the component as per your need. +version: 2.0.1-nullsafety.4 homepage: https://github.com/ionicfirebaseapp/getwidget environment: - sdk: ">=2.2.2 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: diff --git a/test/accordion_test.dart b/test/accordion_test.dart new file mode 100644 index 00000000..a4bb5def --- /dev/null +++ b/test/accordion_test.dart @@ -0,0 +1,213 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + testWidgets('GFAccordion can be created.', (tester) async { + const GFAccordion accordion = GFAccordion( + title: 'title bar', + content: 'content body', + ); + + const TestApp app = TestApp(accordion); + await tester.pumpWidget(app); + + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsNothing); + await tester.tap(find.text('title bar')); + await tester.pump(); + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsOneWidget); + }); + + testWidgets('GFAccordion can be constructed with child.', (tester) async { + const GFAccordion accordion = GFAccordion( + titleChild: Text('title bar'), + contentChild: Text('content body'), + ); + + const TestApp app = TestApp(accordion); + await tester.pumpWidget(app); + + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsNothing); + await tester.tap(find.text('title bar')); + await tester.pump(); + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsOneWidget); + }); + + testWidgets( + 'GFAccordion can be constructed with expanded and collapsed icons', + (tester) async { + const GFAccordion accordion = GFAccordion( + titleChild: Text('title bar'), + contentChild: Text('content body'), + collapsedIcon: Icon(Icons.add), + expandedIcon: Icon(Icons.remove), + ); + + const TestApp app = TestApp(accordion); + await tester.pumpWidget(app); + + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsNothing); + expect(find.byIcon(Icons.add), findsOneWidget); + await tester.tap(find.text('title bar')); + await tester.pump(); + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsOneWidget); + expect(find.byIcon(Icons.remove), findsOneWidget); + }); + + testWidgets('GFAccordion can be constructed with show accordion', + (tester) async { + const GFAccordion accordion = GFAccordion( + titleChild: Text('title bar'), + contentChild: Text('content body'), + showAccordion: true, + ); + + const TestApp app = TestApp(accordion); + await tester.pumpWidget(app); + + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsOneWidget); + }); + + testWidgets( + 'GFAccordion can be constructed with show button on toggle collapsed', + (tester) async { + bool showButton = false; + + final accordion = MaterialApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFAccordion( + titleChild: Row( + children: [ + const Text('title bar'), + showButton + ? const GFButton( + onPressed: null, + text: 'Select one', + ) + : Container(), + ], + ), + contentChild: const Text('content body'), + onToggleCollapsed: (value) { + print('collapsed $value'); + setState(() { + showButton = value; + }); + }, + ), + ), + ), + ), + ); + + await tester.pumpWidget(accordion); + + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsNothing); + expect(find.text('Select one'), findsNothing); + await tester.tap(find.text('title bar')); + await tester.pump(); + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsOneWidget); + expect(find.text('Select one'), findsOneWidget); + }); + + testWidgets('GFAccordion can be constructed with show accordion', + (tester) async { + const GFAccordion accordion = GFAccordion( + titleChild: Text('title bar'), + contentChild: Text('content body'), + showAccordion: true, + ); + + const TestApp app = TestApp(accordion); + await tester.pumpWidget(app); + + expect(find.text('title bar'), findsOneWidget); + expect(find.text('content body'), findsOneWidget); + }); + + testWidgets('GFAccordion with all properties.', (WidgetTester tester) async { + final GFAccordion accordion = GFAccordion( + title: 'title', + content: 'content', + titleChild: const Text('title bar'), + contentChild: const Text('content body'), + textStyle: const TextStyle(color: Colors.deepOrange, fontSize: 16), + collapsedIcon: const Icon(Icons.ac_unit), + expandedIcon: const Icon(Icons.baby_changing_station), + collapsedTitleBackgroundColor: Colors.amber, + expandedTitleBackgroundColor: Colors.greenAccent, + contentBackgroundColor: Colors.tealAccent, + titlePadding: const EdgeInsets.all(13), + contentPadding: const EdgeInsets.all(16), + margin: const EdgeInsets.all(5), + titleBorder: Border.all(color: Colors.blueAccent, width: 1), + contentBorder: Border.all(color: Colors.deepPurpleAccent, width: 1), + titleBorderRadius: const BorderRadius.all(Radius.circular(2)), + contentBorderRadius: const BorderRadius.all(Radius.circular(6)), + showAccordion: true, + onToggleCollapsed: (value) { + print('collapsed $value'); + }, + ); + + final TestApp app = TestApp(accordion); + await tester.pumpWidget(app); + + expect(app.accordion.title, 'title'); + expect(app.accordion.content, 'content'); + expect(app.accordion.textStyle, + const TextStyle(color: Colors.deepOrange, fontSize: 16)); + expect(app.accordion.collapsedTitleBackgroundColor, Colors.amber); + expect(app.accordion.expandedTitleBackgroundColor, Colors.greenAccent); + expect(app.accordion.contentBackgroundColor, Colors.tealAccent); + expect(app.accordion.titlePadding, const EdgeInsets.all(13)); + expect(app.accordion.contentPadding, const EdgeInsets.all(16)); + expect(app.accordion.margin, const EdgeInsets.all(5)); + expect( + app.accordion.titleBorder, + Border.all(color: Colors.blueAccent, width: 1), + ); + expect(app.accordion.contentBorder, + Border.all(color: Colors.deepPurpleAccent, width: 1)); + expect(app.accordion.contentBorder, + Border.all(color: Colors.deepPurpleAccent, width: 1)); + expect(app.accordion.titleBorderRadius, + const BorderRadius.all(Radius.circular(2))); + expect(app.accordion.contentBorderRadius, + const BorderRadius.all(Radius.circular(6))); + expect(app.accordion.showAccordion, isTrue); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.accordion); + final GFAccordion accordion; + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.accordion, + ], + ), + ), + ); +} diff --git a/test/alert_test.dart b/test/alert_test.dart new file mode 100644 index 00000000..5eeb2dc4 --- /dev/null +++ b/test/alert_test.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + final childWidget = Container( + width: 100, + height: 100, + ); + const bgColor = Colors.teal; + const contentChild = Icon(Icons.umbrella); + const child = Text('Get Widget'); + + testWidgets('GFAlert can be created.', (tester) async { + final GFAlert alert = GFAlert( + backgroundColor: bgColor, + contentChild: contentChild, + child: childWidget); + + final TestApp app = TestApp(alert); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.alert.backgroundColor, bgColor); + expect(app.alert.contentChild, contentChild); + expect(app.alert.child, childWidget); + }); + + testWidgets('Basic GFAlert.', (tester) async { + const basicType = GFAlertType.basic; + + const GFAlert alert = GFAlert( + backgroundColor: bgColor, + contentChild: contentChild, + type: basicType, + child: child); + + const TestApp app = TestApp(alert); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.alert.backgroundColor, bgColor); + expect(app.alert.contentChild, contentChild); + expect(app.alert.type, basicType); + expect(app.alert.child, child); + }); + + testWidgets('Rounded GFAlert.', (tester) async { + const roundedType = GFAlertType.rounded; + + const GFAlert alert = + GFAlert(contentChild: contentChild, type: roundedType, child: child); + + const TestApp app = TestApp(alert); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.alert.contentChild, contentChild); + expect(app.alert.type, roundedType); + expect(app.alert.child, child); + }); + + testWidgets('FullWidth GFAlert.', (tester) async { + const basicType = GFAlertType.basic; + const fullWidth = 400.0; + + const GFAlert alert = GFAlert( + width: fullWidth, + contentChild: contentChild, + type: basicType, + child: child); + + const TestApp app = TestApp(alert); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.alert.width, fullWidth); + expect(app.alert.contentChild, contentChild); + expect(app.alert.type, basicType); + expect(app.alert.child, child); + }); + + testWidgets('Customized GFAlert.', (tester) async { + const basicType = GFAlertType.basic; + final customBottombar = Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GFButton( + shape: GFButtonShape.square, + color: GFColors.LIGHT, + onPressed: () {}, + child: const Text( + 'Skip', + style: TextStyle(color: Colors.black), + ), + ), + const SizedBox( + width: 5, + ), + GFButton( + shape: GFButtonShape.square, + type: GFButtonType.outline2x, + icon: const Icon( + Icons.keyboard_arrow_right, + color: GFColors.PRIMARY, + ), + position: GFPosition.end, + text: 'Learn More', + onPressed: () {}, + ) + ], + ); + final GFAlert alert = GFAlert( + contentChild: contentChild, + type: basicType, + bottombar: customBottombar, + child: child); + + final TestApp app = TestApp(alert); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.alert.contentChild, contentChild); + expect(app.alert.type, basicType); + expect(app.alert.child, child); + }); + + testWidgets('GFAlert with alignment.', (tester) async { + const alignment = Alignment.bottomRight; + + const GFAlert alert = + GFAlert(contentChild: contentChild, alignment: alignment, child: child); + + const TestApp app = TestApp(alert); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.alert.contentChild, contentChild); + expect(app.alert.alignment, alignment); + expect(app.alert.child, child); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.alert); + final GFAlert alert; + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.alert, + ], + ), + ), + ); +} diff --git a/test/animation_test.dart b/test/animation_test.dart new file mode 100644 index 00000000..49b2b0c1 --- /dev/null +++ b/test/animation_test.dart @@ -0,0 +1,516 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +class TestPaintingContext implements PaintingContext { + final List invocations = []; + @override + void noSuchMethod(Invocation invocation) { + invocations.add(invocation); + } +} + +void main() { + testWidgets('AnimatedAlign.debugFillProperties', (WidgetTester tester) async { + final GFAnimation box = GFAnimation( + duration: const Duration(seconds: 2), + alignment: Alignment.bottomLeft, + type: GFAnimationType.align, + child: Container( + width: 80, + height: 80, + ), + ); + expect(box, hasOneLineDescription); + }); + + testWidgets('AnimatedAlign alignment visual-to-directional animation', + (WidgetTester tester) async { + final Key alignKey = UniqueKey(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.rtl, + child: GFAnimation( + duration: const Duration(milliseconds: 200), + alignment: Alignment.topRight, + type: GFAnimationType.align, + child: SizedBox(key: alignKey, width: 100, height: 200), + ), + ), + ); + + expect(tester.getSize(find.byKey(alignKey)), const Size(100, 200)); + expect(tester.getTopRight(find.byKey(alignKey)), const Offset(800, 0)); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.rtl, + child: GFAnimation( + duration: const Duration(milliseconds: 200), + alignment: Alignment.bottomRight, + type: GFAnimationType.align, + child: SizedBox(key: alignKey, width: 100, height: 200), + ), + ), + ); + expect(tester.getSize(find.byKey(alignKey)), const Size(100, 200)); + expect(tester.getTopRight(find.byKey(alignKey)), const Offset(800, 0)); + await tester.pump(const Duration(milliseconds: 100)); + expect(tester.getSize(find.byKey(alignKey)), const Size(100, 200)); + expect(tester.getTopRight(find.byKey(alignKey)), const Offset(800, 200)); + await tester.pump(const Duration(milliseconds: 500)); + expect(tester.getSize(find.byKey(alignKey)), const Size(100, 200)); + expect(tester.getTopRight(find.byKey(alignKey)), const Offset(800, 400)); + }); + + testWidgets('AnimatedContainer.debugFillProperties', + (WidgetTester tester) async { + final GFAnimation container = GFAnimation( + width: 50, + changedWidth: 100, + height: 50, + changedHeight: 100, + activeColor: Colors.transparent, + color: Colors.transparent, + fontSize: 35, + type: GFAnimationType.container, + child: Container( + width: 80, + height: 80, + ), + ); + expect(container, hasOneLineDescription); + }); + + testWidgets('AnimatedContainer test animation control ', + (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + + final GFAnimation animate = GFAnimation( + key: key, + duration: const Duration(milliseconds: 300), + color: Colors.amberAccent, + width: 50, + changedWidth: 100, + height: 50, + changedHeight: 100, + activeColor: Colors.transparent, + fontSize: 35, + type: GFAnimationType.container, + child: Container( + width: 80, + height: 80, + ), + ); + + final TestApp app = TestApp(animate); + + await tester.pumpWidget(app); + expect(app.animate.color, equals(Colors.amberAccent)); + await tester.pump(const Duration(seconds: 1)); + }); + + testWidgets('AnimatedContainer testing over animate ', + (WidgetTester tester) async { + await tester.pumpWidget(const GFAnimation( + duration: Duration(milliseconds: 300), + type: GFAnimationType.container, + color: Colors.teal, + child: SizedBox( + width: 45, + height: 45, + ), + )); + expect(tester.binding.transientCallbackCount, 0); + await tester.pump(const Duration(seconds: 1)); + expect(tester.binding.transientCallbackCount, 0); + await tester.pumpWidget(const GFAnimation( + duration: Duration(milliseconds: 300), + type: GFAnimationType.container, + color: Colors.teal, + child: SizedBox( + width: 45, + height: 45, + ), + )); + expect(tester.binding.transientCallbackCount, 0); + await tester.pump(const Duration(seconds: 1)); + expect(tester.binding.transientCallbackCount, 0); + await tester.pumpWidget(const GFAnimation( + duration: Duration(milliseconds: 300), + type: GFAnimationType.container, + color: Colors.teal, + child: SizedBox( + width: 45, + height: 45, + ), + )); + expect(tester.binding.transientCallbackCount, + 0); // this is the only time an animation should have started! + await tester.pump(const Duration(seconds: 1)); + expect(tester.binding.transientCallbackCount, 0); + await tester.pumpWidget(const GFAnimation( + duration: Duration(milliseconds: 300), + type: GFAnimationType.container, + color: Colors.teal, + child: SizedBox( + width: 45, + height: 45, + ), + )); + expect(tester.binding.transientCallbackCount, 0); + }); + + testWidgets('AnimatedContainer alignment visual-to-directional animation', + (WidgetTester tester) async { + final Key target = UniqueKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.rtl, + child: GFAnimation( + duration: const Duration(milliseconds: 200), + alignment: Alignment.topRight, + type: GFAnimationType.container, + child: SizedBox(key: target, width: 100, height: 200), + ), + ), + ); + expect(tester.getSize(find.byKey(target)), const Size(100, 200)); + expect(tester.getTopRight(find.byKey(target)), const Offset(792, 8)); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.rtl, + child: GFAnimation( + duration: const Duration(milliseconds: 200), + alignment: Alignment.bottomRight, + type: GFAnimationType.container, + child: SizedBox(key: target, width: 100, height: 200), + ), + ), + ); + expect(tester.getSize(find.byKey(target)), const Size(100, 200)); + expect(tester.getTopRight(find.byKey(target)), const Offset(792, 8)); + await tester.pump(const Duration(milliseconds: 100)); + expect(tester.getSize(find.byKey(target)), const Size(100, 200)); + expect(tester.getTopRight(find.byKey(target)), const Offset(792, 200)); + await tester.pump(const Duration(milliseconds: 500)); + expect(tester.getSize(find.byKey(target)), const Size(100, 200)); + expect(tester.getTopRight(find.byKey(target)), const Offset(792, 392)); + }); + + testWidgets('Animation rerun', (WidgetTester tester) async { + await tester.pumpWidget(Center( + child: GFAnimation( + duration: const Duration(milliseconds: 200), + type: GFAnimationType.container, + child: Container( + child: const Text('X', textDirection: TextDirection.ltr), + width: 100, + height: 100, + )))); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + RenderBox text = tester.renderObject(find.text('X')); + expect(text.size.width, equals(84)); + expect(text.size.height, equals(84)); + await tester.pump(const Duration(milliseconds: 1000)); + await tester.pumpWidget(Center( + child: GFAnimation( + duration: const Duration(milliseconds: 200), + type: GFAnimationType.container, + child: Container( + child: const Text('X', textDirection: TextDirection.ltr), + width: 200, + height: 200, + )))); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + text = tester.renderObject(find.text('X')); + expect(text.size.width, greaterThan(60)); + expect(text.size.width, lessThan(220)); + expect(text.size.height, greaterThan(60)); + expect(text.size.height, lessThan(220)); + await tester.pump(const Duration(milliseconds: 1000)); + expect(text.size.width, equals(84)); + expect(text.size.height, equals(84)); + await tester.pumpWidget(Center( + child: GFAnimation( + duration: const Duration(milliseconds: 200), + child: Container( + child: const Text('X', textDirection: TextDirection.ltr), + width: 200, + height: 200, + )))); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + expect(text.size.width, equals(84)); + expect(text.size.height, greaterThan(60)); + expect(text.size.height, lessThan(190)); + await tester.pump(const Duration(milliseconds: 1000)); + expect(text.size.width, equals(84)); + expect(text.size.height, equals(84)); + }); + + // --------------------------- size + + testWidgets('animates forwards then backwards with stable-sized children', + (WidgetTester tester) async { + await tester.pumpWidget( + const Center( + child: GFAnimation( + type: GFAnimationType.size, + duration: Duration(milliseconds: 200), + child: SizedBox( + width: 100, + height: 100, + ), + ), + ), + ); + RenderBox box = tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, equals(100)); + expect(box.size.height, equals(100)); + await tester.pumpWidget( + const Center( + child: GFAnimation( + width: 200, + height: 200, + duration: Duration(milliseconds: 200), + type: GFAnimationType.size, + child: SizedBox( + width: 200, + height: 200, + ), + ), + ), + ); + await tester.pump(const Duration(milliseconds: 100)); + box = tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, equals(150)); + expect(box.size.height, equals(150)); + TestPaintingContext context = TestPaintingContext(); + box.paint(context, Offset.zero); + expect(context.invocations.first.memberName, equals(#pushClipRect)); + await tester.pump(const Duration(milliseconds: 100)); + box = tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, equals(200)); + expect(box.size.height, equals(200)); + await tester.pumpWidget( + const Center( + child: GFAnimation( + duration: Duration(milliseconds: 200), + type: GFAnimationType.size, + width: 200, + height: 200, + child: SizedBox( + width: 100, + height: 100, + ), + ), + ), + ); + await tester.pump(const Duration(milliseconds: 100)); + box = tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, equals(200)); + expect(box.size.height, equals(200)); + context = TestPaintingContext(); + box.paint(context, Offset.zero); + expect(context.invocations.first.memberName, equals(#paintChild)); + await tester.pump(const Duration(milliseconds: 100)); + box = tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, equals(200)); + expect(box.size.height, equals(200)); + }); + + testWidgets('clamps animated size to constraints', + (WidgetTester tester) async { + await tester.pumpWidget( + const Center( + child: SizedBox( + width: 100, + height: 100, + child: GFAnimation( + duration: Duration(milliseconds: 200), + type: GFAnimationType.size, + child: SizedBox( + width: 100, + height: 100, + ), + ), + ), + ), + ); + RenderBox box = tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, equals(100)); + expect(box.size.height, equals(100)); + // Attempt to animate beyond the outer SizedBox. + await tester.pumpWidget( + const Center( + child: SizedBox( + width: 100, + height: 100, + child: GFAnimation( + duration: Duration(milliseconds: 200), + type: GFAnimationType.size, + child: SizedBox( + width: 200, + height: 200, + ), + ), + ), + ), + ); + // Verify that animated size is the same as the outer SizedBox. + await tester.pump(const Duration(milliseconds: 100)); + box = tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, equals(100)); + expect(box.size.height, equals(100)); + }); + + testWidgets('resyncs its animation controller', (WidgetTester tester) async { + await tester.pumpWidget( + const Center( + child: AnimatedSize( + duration: Duration(milliseconds: 200), + vsync: TestVSync(), + child: SizedBox( + width: 100, + height: 100, + ), + ), + ), + ); + await tester.pumpWidget( + const Center( + child: GFAnimation( + duration: Duration(milliseconds: 200), + type: GFAnimationType.size, + child: SizedBox( + width: 200, + height: 100, + ), + ), + ), + ); + await tester.pump(const Duration(milliseconds: 100)); + final RenderBox box = tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, equals(100)); + }); + testWidgets('does not run animation unnecessarily', + (WidgetTester tester) async { + await tester.pumpWidget( + const Center( + child: GFAnimation( + duration: Duration(milliseconds: 200), + type: GFAnimationType.size, + child: SizedBox( + width: 100, + height: 100, + ), + ), + ), + ); + for (int i = 0; i < 20; i++) { + final RenderAnimatedSize box = + tester.renderObject(find.byType(AnimatedSize)); + expect(box.size.width, 100); + expect(box.size.height, 100); + expect(box.state, RenderAnimatedSizeState.stable); + expect(box.isAnimating, false); + await tester.pump(const Duration(milliseconds: 10)); + } + }); + + testWidgets('RotationTransition maintains chosen alignment during animation', + (WidgetTester tester) async { + AnimationController controller; + Animation animation; + + controller = AnimationController( + duration: const Duration(seconds: 5), vsync: const TestVSync()); + animation = CurvedAnimation(parent: controller, curve: Curves.linear); + + final Widget widget = GFAnimation( + turnsAnimation: animation, + controller: controller, + type: GFAnimationType.rotateTransition, + alignment: Alignment.topRight, + child: const Text('Rotation', textDirection: TextDirection.ltr), + ); + + await tester.pumpWidget(widget); + RotationTransition actualRotatedBox = + tester.widget(find.byType(RotationTransition)); + Alignment actualAlignment = actualRotatedBox.alignment; + expect(actualAlignment, Alignment.topRight); + + controller.value = 0.5; + await tester.pump(); + actualRotatedBox = tester.widget(find.byType(RotationTransition)); + actualAlignment = actualRotatedBox.alignment; + expect(actualAlignment, Alignment.topRight); + }); + + testWidgets('RotationTransition animates', (WidgetTester tester) async { + AnimationController controller; + Animation animation; + + controller = AnimationController( + duration: const Duration(seconds: 5), vsync: const TestVSync()); + animation = CurvedAnimation(parent: controller, curve: Curves.linear); + + final Widget widget = GFAnimation( + turnsAnimation: animation, + controller: controller, + type: GFAnimationType.rotateTransition, + alignment: Alignment.topRight, + child: const Text('Rotation', textDirection: TextDirection.ltr), + ); + + await tester.pumpWidget(widget); + Transform actualRotatedBox = tester.widget(find.byType(Transform)); + Matrix4 actualTurns = actualRotatedBox.transform; + expect(actualTurns, equals(Matrix4.rotationZ(0))); + + controller.value = 0.5; + await tester.pump(); + actualRotatedBox = tester.widget(find.byType(Transform)); + actualTurns = actualRotatedBox.transform; + expect(actualTurns, Matrix4.rotationZ(math.pi)); + + controller.value = 0.75; + await tester.pump(); + actualRotatedBox = tester.widget(find.byType(Transform)); + actualTurns = actualRotatedBox.transform; + expect(actualTurns, Matrix4.rotationZ(math.pi * 1.5)); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.animate); + + final GFAnimation animate; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + bool fav = false; + + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.animate, + ], + ), + ), + ); +} diff --git a/test/appbar_test.dart b/test/appbar_test.dart new file mode 100644 index 00000000..6c04a1c4 --- /dev/null +++ b/test/appbar_test.dart @@ -0,0 +1,364 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + const title = Text('Appbar'); + final leading = GFIconButton( + icon: const Icon( + Icons.wb_sunny, + color: GFColors.WHITE, + ), + onPressed: () {}, + type: GFButtonType.transparent, + ); + final actionButton = GFIconButton( + icon: const Icon( + Icons.favorite, + color: GFColors.WHITE, + ), + onPressed: () {}, + type: GFButtonType.transparent, + ); + final flexibleSpace = Container(color: Colors.amber); + final shape = RoundedRectangleBorder( + side: const BorderSide( + color: Colors.tealAccent, width: 1, style: BorderStyle.solid), + borderRadius: BorderRadius.circular(25)); + + final bottom = PreferredSize( + child: Container( + color: Colors.orange, + height: 4, + ), + preferredSize: const Size.fromHeight(4)); + + const textTheme = TextTheme(headline5: TextStyle(color: Colors.tealAccent)); + const iconTheme = IconThemeData(color: Colors.red); + const actionsIconTheme = IconThemeData(color: Colors.amber); + + testWidgets('GF AppBar can be constructed', (tester) async { + final GFAppBar appbar = GFAppBar(); + + final TestApp app = TestApp(appbar); + + await tester.pumpWidget(app); + }); + + testWidgets('GF AppBar can be constructed with title, leading and actions', + (tester) async { + final GFAppBar appbar = GFAppBar( + leading: leading, + title: title, + actions: [actionButton], + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.wb_sunny), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + + expect(app.appbar.leading, leading); + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + }); + + testWidgets('GF AppBar with title with no titleSpace', (tester) async { + // when GFAppBar.titleSpacing is 0, title takes all the space available + // with or without leading and actions + final GFAppBar appbar = GFAppBar( + leading: leading, + title: title, + titleSpacing: 0, + actions: [actionButton], + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.wb_sunny), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + + expect(app.appbar.leading, leading); + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + expect(app.appbar.titleSpacing, 0); + }); + + testWidgets('GF AppBar with centerTitle', (tester) async { + // when GFAppBar.centerTitle is true, title will align at center. + final GFAppBar appbar = GFAppBar( + leading: leading, + title: title, + actions: [actionButton], + centerTitle: true, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.wb_sunny), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + + expect(app.appbar.leading, leading); + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + expect(app.appbar.centerTitle, true); + }); + + testWidgets('GF AppBar with automaticallyImplyLeading', (tester) async { + // when GFAppBar.automaticallyImplyLeading is true and [leading] is null, automatically try to deduce what the leading + // widget should be. If false and [leading] is null, leading space is given to [title]. + // If leading widget is not null, this parameter has no effect. + final GFAppBar appbar = GFAppBar( + title: title, + actions: [actionButton], + automaticallyImplyLeading: true, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + expect(app.appbar.automaticallyImplyLeading, true); + }); + + testWidgets('GF AppBar with flexibleSpace', (tester) async { + // GFAppbar.flexibleSpace stacked behind the toolbar and the tab bar. It's height will + // be the same as the app bar's overall height. A flexible space isn't actually + // flexible unless the [GFAppBar]'s container changes the [GFAppBar]'s size. + final GFAppBar appbar = GFAppBar( + title: title, + actions: [actionButton], + flexibleSpace: flexibleSpace, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + expect(find.byWidget(flexibleSpace), findsOneWidget); + + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + expect(app.appbar.flexibleSpace, flexibleSpace); + }); + + testWidgets('GF AppBar with primary and brightness', (tester) async { + // GFAppbar.primary on false displays title at the top of the screen. + // If true, the app bar's toolbar elements and [bottom] widget will be + // padded on top by the height of the system status bar. + + final GFAppBar appbar = GFAppBar( + title: title, + primary: false, + brightness: Brightness.dark, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + + expect(app.appbar.title, title); + expect(app.appbar.primary, false); + expect(app.appbar.brightness, Brightness.dark); + }); + + testWidgets('GF AppBar with backgroundColor and shape', (tester) async { + // GFAppbar.shape to customize shape of the appbar + final GFAppBar appbar = GFAppBar( + title: title, + actions: [actionButton], + backgroundColor: Colors.teal, + shape: shape, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + expect(app.appbar.backgroundColor, Colors.teal); + expect(app.appbar.shape, shape); + }); + + testWidgets('GF AppBar with bottom and elevation', (tester) async { + final GFAppBar appbar = GFAppBar( + title: title, + actions: [actionButton], + bottom: bottom, + elevation: 1, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + expect(find.byWidget(bottom), findsOneWidget); + + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + expect(app.appbar.bottom, bottom); + expect(app.appbar.elevation, 1); + }); + + testWidgets('GF AppBar with textTheme, iconTheme, actionsIconTheme', + (tester) async { + const leadingIcon = Icon(Icons.wb_sunny); + + final GFAppBar appbar = GFAppBar( + leading: leadingIcon, + title: title, + actions: const [Icon(Icons.favorite)], + textTheme: textTheme, + actionsIconTheme: actionsIconTheme, + iconTheme: iconTheme, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.wb_sunny), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + + expect(app.appbar.leading, leadingIcon); + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + expect(app.appbar.textTheme, textTheme); + expect(app.appbar.actionsIconTheme, actionsIconTheme); + expect(app.appbar.iconTheme, iconTheme); + }); + testWidgets( + 'GF AppBar with bottomOpacity, toolbarOpacity and test searchBar with default searchBar value', + (tester) async { + final GFAppBar appbar = GFAppBar( + title: title, + actions: [actionButton], + bottomOpacity: 0.5, + toolbarOpacity: 0.6, + bottom: bottom, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + expect(find.text('Appbar'), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + expect(find.byWidget(bottom), findsOneWidget); + + expect(app.appbar.title, title); + expect(app.appbar.actions!.length, 1); + expect(app.appbar.bottom, bottom); + expect(app.appbar.bottomOpacity, 0.5); + expect(app.appbar.toolbarOpacity, 0.6); + // set appbar.searchBar = false state to disable search bar + expect(app.appbar.searchBar, isFalse); + // try to find search icon when appbar.searchBar = false + expect(find.byIcon(Icons.search), findsNothing); + }); + + testWidgets('GF AppBar with searchBar', (tester) async { + final Key appbarKey = UniqueKey(); + final TextEditingController _searchController = TextEditingController(); + + final GFAppBar appbar = GFAppBar( + key: appbarKey, + title: title, + actions: [actionButton], + searchBar: true, + searchController: _searchController, + searchHintText: 'Search', + searchHintStyle: const TextStyle(fontSize: 14, color: Colors.white70), + searchTextStyle: const TextStyle(fontSize: 16, color: Colors.white), + searchBarColorTheme: Colors.white, + onChanged: (value) { + print('on change $value'); + }, + onSubmitted: (value) { + print('submitted $value'); + }, + onTap: () { + print('tapped'); + }, + ); + + final TestApp app = TestApp(appbar); + await tester.pumpWidget(app); + + // find appbar by key + expect(find.byKey(appbarKey), findsOneWidget); + // find appbar title text + expect(find.text('Appbar'), findsOneWidget); + // find appbar action button icon + expect(find.byIcon(Icons.favorite), findsOneWidget); + // set appbar.searchBar = true state to enable search bar + expect(app.appbar.searchBar, isTrue); + // find appbar search icon + expect(find.byIcon(Icons.search), findsOneWidget); + // tap the search icon + await tester.tap(find.byIcon(Icons.search)); + // rebuild the widget + await tester.pump(); + // enter 'flutter' to the textField + await tester.enterText(find.byWidget(appbar), 'flutter'); + // find the text 'flutter' to test onChanged + expect(find.text('flutter'), findsOneWidget); + // to test onSubmitted when TextInputAction.done + await tester.testTextInput.receiveAction(TextInputAction.done); + // rebuild the widget + await tester.pump(); + // find the text 'flutter' in textField + expect(find.text('flutter'), findsOneWidget); + // tap the close icon to close the searchBar + await tester.tap(find.byIcon(Icons.close)); + // rebuild the widget + await tester.pump(); + // try to find textField + expect(find.byType(TextField), findsNothing); + // tap the search icon to reopen the searchBar + await tester.tap(find.byIcon(Icons.search)); + // rebuild the widget + await tester.pump(); + // find the text 'flutter' in textField + expect(find.text('flutter'), findsOneWidget); + // tap the textField to test onTap + await tester.tap(find.byType(TextField)); + // rebuild the widget + await tester.pump(); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.appbar); + + final GFAppBar appbar; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + appBar: widget.appbar, + body: Container( + child: const Text('body'), + )), + ); +} diff --git a/test/avatar_test.dart b/test/avatar_test.dart new file mode 100644 index 00000000..84e278f3 --- /dev/null +++ b/test/avatar_test.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + final childWidget = Container( + width: 60, + height: 60, + ); + const bgColor = Colors.teal; + const fgColor = Colors.transparent; + + testWidgets('GFAvatar can be created', (tester) async { + final GFAvatar avatar = GFAvatar( + backgroundColor: bgColor, + foregroundColor: fgColor, + size: GFSize.MEDIUM, + child: childWidget); + final TestApp app = TestApp(avatar); + await tester.pumpWidget(app); + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.avatar.backgroundColor, bgColor); + expect(app.avatar.foregroundColor, fgColor); + expect(app.avatar.size, GFSize.MEDIUM); + expect(app.avatar.child, childWidget); + }); + + testWidgets('GFAvatar with minRadius & maxRadius', (tester) async { + const minRadius = 10.0; + const maxRadius = 20.0; + final GFAvatar avatar = GFAvatar( + backgroundColor: bgColor, + foregroundColor: fgColor, + minRadius: minRadius, + maxRadius: maxRadius, + size: GFSize.MEDIUM, + child: childWidget); + final TestApp app = TestApp(avatar); + await tester.pumpWidget(app); + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.avatar.backgroundColor, bgColor); + expect(app.avatar.foregroundColor, fgColor); + expect(app.avatar.minRadius, minRadius); + expect(app.avatar.maxRadius, maxRadius); + expect(app.avatar.size, GFSize.MEDIUM); + expect(app.avatar.child, childWidget); + }); + + testWidgets('Circular GFAvatar with bgImage', (tester) async { + const bgImage = NetworkImage( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + + const GFAvatar avatar = GFAvatar( + backgroundImage: bgImage, + shape: GFAvatarShape.circle, + ); + + const TestApp app = TestApp(avatar); + + expect(app.avatar.backgroundImage, bgImage); + expect(app.avatar.shape, GFAvatarShape.circle); + }); + + testWidgets('Square GFAvatar with bgImage', (tester) async { + const bgImage = NetworkImage( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + + const GFAvatar avatar = GFAvatar( + backgroundImage: bgImage, + shape: GFAvatarShape.square, + ); + + const TestApp app = TestApp(avatar); + + expect(app.avatar.backgroundImage, bgImage); + expect(app.avatar.shape, GFAvatarShape.square); + }); + + testWidgets('Standard shape GFAvatar with bgImage', (tester) async { + const bgImage = NetworkImage( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + + const GFAvatar avatar = GFAvatar( + backgroundImage: bgImage, + shape: GFAvatarShape.standard, + ); + + const TestApp app = TestApp(avatar); + + expect(app.avatar.backgroundImage, bgImage); + expect(app.avatar.shape, GFAvatarShape.standard); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.avatar); + final GFAvatar avatar; + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.avatar, + ], + ), + ), + ); +} diff --git a/test/badge_test.dart b/test/badge_test.dart new file mode 100644 index 00000000..4dbfb70a --- /dev/null +++ b/test/badge_test.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + const text = '1'; + const textStyle = TextStyle(fontSize: 14, color: Colors.black); + const size = GFSize.SMALL; + const textColor = GFColors.INFO; + const color = Colors.amber; + const border = + BorderSide(color: Colors.tealAccent, width: 1, style: BorderStyle.solid); + const shape = RoundedRectangleBorder( + side: BorderSide(color: Colors.pink, width: 2, style: BorderStyle.solid), + borderRadius: BorderRadius.zero); + const childWidget = Text('hey'); + + testWidgets('GF Badge can be constructed', (tester) async { + const GFBadge badge = GFBadge( + text: text, + textStyle: textStyle, + size: size, + ); + + const TestApp app = TestApp(badge); + + await tester.pumpWidget(app); + + expect(find.text('1'), findsOneWidget); + + expect(app.badge.text, text); + expect(app.badge.textStyle, textStyle); + expect(app.badge.size, size); + + await tester.pumpWidget(app); + }); + + testWidgets('GF Badge with color and textColor', (tester) async { + const GFBadge badge = GFBadge( + text: text, + size: size, + textColor: textColor, + color: color, + ); + + const TestApp app = TestApp(badge); + + await tester.pumpWidget(app); + + expect(find.text('1'), findsOneWidget); + + expect(app.badge.text, text); + expect(app.badge.size, size); + expect(app.badge.textColor, textColor); + expect(app.badge.color, color); + + await tester.pumpWidget(app); + }); + + testWidgets( + 'GF Badge with GFBadgeShape like circle, standard, square and pills', + (tester) async { + const GFBadge badge = GFBadge( + text: text, + size: size, + textColor: textColor, + color: color, + shape: GFBadgeShape.circle, + ); + + const TestApp app = TestApp(badge); + + await tester.pumpWidget(app); + + expect(find.text('1'), findsOneWidget); + + expect(app.badge.text, text); + expect(app.badge.size, size); + expect(app.badge.textColor, textColor); + expect(app.badge.color, color); + expect(app.badge.shape, GFBadgeShape.circle); + + await tester.pumpWidget(app); + }); + + testWidgets('GF Badge with custom border and shape', (tester) async { + const GFBadge badge = GFBadge( + text: text, + size: size, + textColor: textColor, + color: color, + borderShape: shape, + border: border, + ); + + const TestApp app = TestApp(badge); + + await tester.pumpWidget(app); + + expect(find.text('1'), findsOneWidget); + + expect(app.badge.text, text); + expect(app.badge.size, size); + expect(app.badge.textColor, textColor); + expect(app.badge.color, color); + expect(app.badge.borderShape, shape); + expect(app.badge.border, border); + + await tester.pumpWidget(app); + }); + + testWidgets('GF Badge with custom child', (tester) async { + const GFBadge badge = GFBadge( + child: childWidget, + size: size, + ); + + const TestApp app = TestApp(badge); + + await tester.pumpWidget(app); + + expect(find.text('hey'), findsOneWidget); + + expect(app.badge.child, childWidget); + + await tester.pumpWidget(app); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.badge); + + final GFBadge badge; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.badge, + ], + ), + ), + ); +} diff --git a/test/border_test.dart b/test/border_test.dart new file mode 100644 index 00000000..aced0b83 --- /dev/null +++ b/test/border_test.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + final childWidget = Container( + width: 222, + height: 333, + ); + const color = Colors.teal; + const padding = EdgeInsets.all(5); + const type = GFBorderType.rect; + const text = Text('Hello'); + + testWidgets('GFBorder can be created around different components.', + (tester) async { + final GFBorder border = GFBorder( + color: color, + padding: padding, + type: type, + dashedLine: const [2, 0], + child: childWidget); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.padding, padding); + expect(app.border.type, type); + expect(app.border.dashedLine, [2, 0]); + expect(app.border.child, childWidget); + }); + + testWidgets('Solid GFBorder around image.', (tester) async { + final bgImage = Image.network( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + final GFBorder border = GFBorder( + color: color, type: type, dashedLine: const [2, 0], child: bgImage); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, type); + expect(app.border.dashedLine, [2, 0]); + expect(app.border.child, bgImage); + }); + + testWidgets('Solid GFBorder with radius.', (tester) async { + const radius = Radius.circular(20); + + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.rRect, + dashedLine: const [2, 0], + radius: radius, + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.rRect); + expect(app.border.dashedLine, [2, 0]); + expect(app.border.child, text); + }); + + testWidgets('Oval GFBorder.', (tester) async { + const stroke = 2.0; + + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.oval, + dashedLine: const [6, 0], + strokeWidth: stroke, + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.oval); + expect(app.border.dashedLine, [6, 0]); + expect(app.border.strokeWidth, stroke); + expect(app.border.child, text); + }); + + testWidgets('Circular GFBorder.', (tester) async { + const stroke = 2.0; + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.circle, + dashedLine: const [2, 0], + strokeWidth: stroke, + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.circle); + expect(app.border.dashedLine, [2, 0]); + expect(app.border.strokeWidth, stroke); + expect(app.border.child, text); + }); + + testWidgets('Dashed GFBorder.', (tester) async { + final GFBorder border = + GFBorder(color: color, type: GFBorderType.rRect, child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.rRect); + expect(app.border.child, text); + }); + + testWidgets('Dashed GFBorder with radius.', (tester) async { + const radius = Radius.circular(20); + + final GFBorder border = GFBorder( + color: color, type: GFBorderType.rRect, radius: radius, child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.rRect); + expect(app.border.radius, radius); + expect(app.border.child, text); + }); + + testWidgets('Oval dashed GFBorder.', (tester) async { + const stroke = 2.0; + + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.oval, + strokeWidth: stroke, + dashedLine: const [3, 1], + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.oval); + expect(app.border.strokeWidth, stroke); + expect(app.border.dashedLine, [3, 1]); + expect(app.border.child, text); + }); + + testWidgets('Circular dashed GFBorder.', (tester) async { + const stroke = 2.0; + + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.circle, + dashedLine: const [3, 1], + strokeWidth: stroke, + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.circle); + expect(app.border.dashedLine, [3, 1]); + expect(app.border.strokeWidth, stroke); + expect(app.border.child, text); + }); + + testWidgets('Dotted GFBorder.', (tester) async { + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.rect, + dashedLine: const [2, 1], + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.rect); + expect(app.border.dashedLine, [2, 1]); + expect(app.border.child, text); + }); + + testWidgets('Dotted GFBorder with radius.', (tester) async { + const radius = Radius.circular(20); + + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.rRect, + dashedLine: const [2, 1], + radius: radius, + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.rRect); + expect(app.border.dashedLine, [2, 1]); + expect(app.border.radius, radius); + expect(app.border.child, text); + }); + + testWidgets('Oval dotted GFBorder.', (tester) async { + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.oval, + dashedLine: const [2, 1], + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.oval); + expect(app.border.dashedLine, [2, 1]); + expect(app.border.child, text); + }); + + testWidgets('Circular dotted GFBorder.', (tester) async { + final GFBorder border = GFBorder( + color: color, + type: GFBorderType.circle, + dashedLine: const [2, 1], + child: text); + + final TestApp app = TestApp(border); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.border.color, color); + expect(app.border.type, GFBorderType.circle); + expect(app.border.dashedLine, [2, 1]); + expect(app.border.child, text); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.border); + final GFBorder border; + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.border, + ], + ), + ), + ); +} diff --git a/test/bottom_sheet_test.dart b/test/bottom_sheet_test.dart new file mode 100644 index 00000000..d587ecc2 --- /dev/null +++ b/test/bottom_sheet_test.dart @@ -0,0 +1,308 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + final Key bottomSheetKey = UniqueKey(); + final Key contentKey = UniqueKey(); + final GFBottomSheetController _controller = GFBottomSheetController(); + + testWidgets('GFBottomSheet can be constructed', (tester) async { + final GFBottomSheet bottomSheet = GFBottomSheet( + key: bottomSheetKey, + controller: _controller, + stickyHeaderHeight: 70, + stickyHeader: const GFListTile( + avatar: GFAvatar( + backgroundColor: Colors.amber, + ), + titleText: 'Header Title', + subTitleText: '11 minutes ago', + ), + contentBody: ListView( + shrinkWrap: true, + physics: const ScrollPhysics(), + children: [ + ListView.builder( + shrinkWrap: true, + physics: const ScrollPhysics(), + scrollDirection: Axis.vertical, + itemCount: 3, + itemBuilder: (BuildContext context, int index) => GFListTile( + color: Colors.black12, + avatar: const GFAvatar( + backgroundColor: Colors.tealAccent, + size: 20, + ), + subTitleText: 'Content Title', + icon: GFButton( + onPressed: () {}, + color: Colors.teal, + child: const Text( + 'Send', + style: TextStyle(color: Colors.white), + ), + ), + ), + ), + const Text('Content'), + ], + ), + ); + + final TestApp app = TestApp(bottomSheet); + await tester.pumpWidget(app); + + // find bottom sheet by key + expect(find.byKey(bottomSheetKey), findsOneWidget); + // find header tile text + expect(find.text('Header Title'), findsOneWidget); + // tap on header tile to open bottomSheet + await tester.tap(find.text('Header Title')); + await tester.pump(); + await tester.pump(const Duration(seconds: 2)); + // find the content tile + expect(find.text('Content Title'), findsNWidgets(3)); + // tap on header tile to close bottomSheet + await tester.tap(find.text('Header Title')); + await tester.pump(); + await tester.pump(const Duration(seconds: 2)); + // try to find content finds nothing + expect(find.text('Content Title'), findsNothing); + // Swipe the header tile to open bottomSheet + await tester.drag(find.text('Header Title'), const Offset(0, -4.6)); + await tester.pumpAndSettle(); + // find the content tile + expect(find.text('Content Title'), findsNWidgets(3)); + // Swipe the header tile to close bottomSheet + await tester.drag(find.text('Header Title'), const Offset(0, 6.5)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + // try to find content finds nothing + expect(find.text('Content Title'), findsNothing); + + // Swipe the header tile to open bottomSheet + await tester.drag(find.text('Header Title'), const Offset(0, -4.6)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + // find the content tile + expect(find.text('Content Title'), findsNWidgets(3)); + // Swipe the content tile to close bottomSheet + await tester.drag(find.text('Content'), const Offset(0, 6.5)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + // try to find content finds nothing + expect(find.text('Content Title'), findsNothing); + }); + + testWidgets('GFBottomSheet can be constructed with footer', (tester) async { + final GFBottomSheet bottomSheet = GFBottomSheet( + key: bottomSheetKey, + controller: _controller, + stickyHeaderHeight: 70, + stickyHeader: const GFListTile( + avatar: GFAvatar( + backgroundColor: Colors.amber, + ), + titleText: 'Header Title', + subTitleText: '11 minutes ago', + ), + contentBody: ListView( + shrinkWrap: true, + physics: const ScrollPhysics(), + children: [ + const Text('Content GetWidget'), + Container( + padding: const EdgeInsets.all(16), + child: const Text( + 'Get Widget is one of the largest Flutter open-source ' + 'UI library for mobile or web apps. It has more than 1000+ pre-built reusable widgets.'), + ), + ], + ), + stickyFooterHeight: 60, + stickyFooter: Container( + alignment: AlignmentDirectional.center, + width: 444, + color: Colors.amber, + child: const Text('Footer Title'), + ), + ); + + final TestApp app = TestApp(bottomSheet); + await tester.pumpWidget(app); + + // find bottom sheet by key + expect(find.byKey(bottomSheetKey), findsOneWidget); + // find header tile text + expect(find.text('Header Title'), findsOneWidget); + + // Swipe the header tile to open bottomSheet + await tester.drag(find.text('Header Title'), const Offset(0, -4.6)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + // find the content tile + expect(find.text('Content GetWidget'), findsOneWidget); + // find the footer title + expect(find.text('Footer Title'), findsOneWidget); + // Swipe the content tile to close bottomSheet + await tester.drag(find.text('Footer Title'), const Offset(0, 5.3)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + // try to find content finds nothing + expect(find.text('Content GetWidget'), findsNothing); + }); + + testWidgets('GFBottomSheet can be constructed with expandable content', + (tester) async { + final GFBottomSheet bottomSheet = GFBottomSheet( + key: bottomSheetKey, + controller: _controller, + enableExpandableContent: true, + stickyHeaderHeight: 70, + stickyHeader: const GFListTile( + avatar: GFAvatar( + backgroundColor: Colors.amber, + ), + titleText: 'Header Title', + subTitleText: '11 minutes ago', + ), + contentBody: ListView( + key: contentKey, + shrinkWrap: true, + physics: const ScrollPhysics(), + children: [ + const Text('Content GetWidget'), + Container( + padding: const EdgeInsets.all(16), + child: const Text( + 'Get Widget is one of the largest Flutter open-source ' + 'UI library for mobile or web apps. It has more than 1000+ pre-built reusable widgets.'), + ), + ], + ), + stickyFooterHeight: 60, + stickyFooter: Container( + alignment: AlignmentDirectional.center, + width: 444, + color: Colors.amber, + child: const Text('Footer Title'), + ), + ); + + final TestApp app = TestApp(bottomSheet); + await tester.pumpWidget(app); + + // find bottom sheet by key + expect(find.byKey(bottomSheetKey), findsOneWidget); + // find header tile text + expect(find.text('Header Title'), findsOneWidget); + // set enableExpandableContent = true + expect(bottomSheet.enableExpandableContent, isTrue); + // Swipe the header tile to open bottomSheet + await tester.drag(find.text('Header Title'), const Offset(0, -4.6)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + // find the content tile + expect(find.text('Content GetWidget'), findsOneWidget); + // find the footer title + expect(find.text('Footer Title'), findsOneWidget); + // Swipe the content tile to close bottomSheet + await tester.drag(find.text('Content GetWidget'), const Offset(0, 6.5)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + // try to find content finds nothing + expect(find.text('Content GetWidget'), findsNothing); + + // Swipe the header tile to open bottomSheet + await tester.drag(find.text('Header Title'), const Offset(0, -4.6)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 2)); + // find the content tile + expect(find.text('Content GetWidget'), findsOneWidget); + // find the footer title + expect(find.text('Footer Title'), findsOneWidget); + // drag up content body to expand + await tester.drag(find.byKey(contentKey), const Offset(0, -22.9)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 5)); + // find content body + expect(find.text('Content GetWidget'), findsOneWidget); + + // drag down content body to close the bottom sheet + await tester.drag(find.byKey(contentKey), const Offset(0, 5.6)); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 5)); + // try to find content body, finds nothing + expect(find.text('Content GetWidget'), findsNothing); + }); + + testWidgets('GFBottomSheet with properties', (tester) async { + final GFBottomSheet bottomSheet = GFBottomSheet( + controller: _controller, + enableExpandableContent: true, + stickyHeaderHeight: 70, + stickyHeader: const GFListTile( + avatar: GFAvatar( + backgroundColor: Colors.amber, + ), + titleText: 'Header Title', + subTitleText: '11 minutes ago', + ), + contentBody: ListView( + key: contentKey, + shrinkWrap: true, + physics: const ScrollPhysics(), + children: [ + const Text('Content GetWidget'), + Container( + padding: const EdgeInsets.all(16), + child: const Text( + 'Get Widget is one of the largest Flutter open-source ' + 'UI library for mobile or web apps. It has more than 1000+ pre-built reusable widgets.'), + ), + ], + ), + stickyFooterHeight: 60, + stickyFooter: Container( + alignment: AlignmentDirectional.center, + width: 444, + color: Colors.amber, + child: const Text('Footer Title'), + ), + elevation: 5, + minContentHeight: 150, + maxContentHeight: 450, + animationDuration: 1500, + ); + + final TestApp app = TestApp(bottomSheet); + + await tester.pumpWidget(app); + expect(app.bottomSheet.controller, _controller); + expect(app.bottomSheet.stickyHeaderHeight, 70); + expect(app.bottomSheet.stickyFooterHeight, 60); + expect(app.bottomSheet.enableExpandableContent, isTrue); + expect(app.bottomSheet.elevation, 5); + expect(app.bottomSheet.minContentHeight, 150); + expect(app.bottomSheet.maxContentHeight, 450); + expect(app.bottomSheet.animationDuration, 1500); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.bottomSheet); + final GFBottomSheet bottomSheet; + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + bottomSheet: widget.bottomSheet, + ), + ); +} diff --git a/test/button_badge_test.dart b/test/button_badge_test.dart new file mode 100644 index 00000000..b5c6f389 --- /dev/null +++ b/test/button_badge_test.dart @@ -0,0 +1,365 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + const badge = GFBadge( + child: Text('12'), + ); + const text = 'click me'; + const textStyle = TextStyle(fontSize: 14, color: Colors.black); + const size = GFSize.SMALL; + + testWidgets('Disabled GF ButtonBadge can be constructed', (tester) async { + const buttonBadgeKey = Key('header'); + + const GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: null, + text: text, + size: size, + icon: badge, + disabledColor: Colors.teal, + disabledElevation: 3, + disabledTextColor: GFColors.LIGHT, + ); + + const TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.onPressed, null); + expect(app.buttonBadge.disabledColor, Colors.teal); + expect(app.buttonBadge.disabledElevation, 3); + expect(app.buttonBadge.disabledTextColor, GFColors.LIGHT); + + await tester.tap(find.byKey(buttonBadgeKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets('GF ButtonBadge can be constructed', (tester) async { + const buttonBadgeKey = Key('header'); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + icon: badge, + size: size, + color: GFColors.ALT, + textStyle: textStyle, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.textStyle, textStyle); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.color, GFColors.ALT); + + await tester.tap(find.byKey(buttonBadgeKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets( + 'GF ButtonBadge can be constructed with standard type and shape i.e default', + (tester) async { + const buttonBadgeKey = Key('header'); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + icon: badge, + size: size, + textColor: GFColors.DARK, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.textColor, GFColors.DARK); + expect(app.buttonBadge.size, size); + + await tester.tap(find.byKey(buttonBadgeKey)); + await tester.pump(); + await tester.press(find.byKey(buttonBadgeKey)); + await tester.pump(); + await tester.longPress(find.byKey(buttonBadgeKey)); + await tester.pump(); + + // await expectLater(() => tester.tap(find.byKey(buttonBadgeKey)), prints('onHighlight changed')); + + await tester.pumpWidget(app); + }); + + testWidgets('GF ButtonBadge with Block Button and badge position', + (tester) async { + const buttonBadgeKey = Key('header'); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + size: size, + icon: badge, + position: GFPosition.start, + blockButton: true, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.position, GFPosition.start); + expect(app.buttonBadge.blockButton, isTrue); + }); + + testWidgets( + 'GF ButtonBadge with solid type and pills shape ' + 'and Full Width Button', (tester) async { + const buttonBadgeKey = Key('header'); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + size: size, + icon: badge, + position: GFPosition.start, + type: GFButtonType.solid, + shape: GFButtonShape.pills, + fullWidthButton: true, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.position, GFPosition.start); + expect(app.buttonBadge.type, GFButtonType.solid); + expect(app.buttonBadge.shape, GFButtonShape.pills); + expect(app.buttonBadge.fullWidthButton, isTrue); + }); + + testWidgets('GF ButtonBadge with type outline and square shape ', + (tester) async { + const buttonBadgeKey = Key('header'); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + size: size, + icon: badge, + position: GFPosition.start, + type: GFButtonType.outline, + shape: GFButtonShape.square, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.position, GFPosition.start); + expect(app.buttonBadge.type, GFButtonType.outline); + expect(app.buttonBadge.shape, GFButtonShape.square); + }); + + testWidgets('GF ButtonBadge with custom border and shape ', (tester) async { + const buttonBadgeKey = Key('header'); + + const border = BorderSide( + color: Colors.tealAccent, width: 1, style: BorderStyle.solid); + const shape = RoundedRectangleBorder( + side: + BorderSide(color: Colors.pink, width: 2, style: BorderStyle.solid), + borderRadius: BorderRadius.zero); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + icon: badge, + size: size, + borderShape: shape, + borderSide: border, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.borderShape, shape); + expect(app.buttonBadge.borderSide, border); + }); + + testWidgets('GF ButtonBadge with boxshadow ', (tester) async { + const buttonBadgeKey = Key('header'); + + const boxshadow = BoxShadow( + color: Colors.pink, + blurRadius: 2, + spreadRadius: 1, + offset: Offset.zero, + ); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + icon: badge, + size: size, + badgeBoxShadow: true, + boxShadow: boxshadow, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.badgeBoxShadow, isTrue); + expect(app.buttonBadge.boxShadow, boxshadow); + }); + + testWidgets( + 'GF ButtonBadge with hover, focus, highlight and splash color. Works only in web', + (tester) async { + const buttonBadgeKey = Key('header'); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + icon: badge, + size: size, + hoverColor: Colors.tealAccent, + focusColor: Colors.teal, + highlightColor: Colors.amber, + splashColor: Colors.red, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.hoverColor, Colors.tealAccent); + expect(app.buttonBadge.focusColor, Colors.teal); + expect(app.buttonBadge.highlightColor, Colors.amber); + expect(app.buttonBadge.splashColor, Colors.red); + }); + + testWidgets( + 'GF ButtonBadge with hover, focus, highlight and splash color. Works only in web', + (tester) async { + const buttonBadgeKey = Key('header'); + + final GFButtonBadge buttonBadge = GFButtonBadge( + key: buttonBadgeKey, + onPressed: () {}, + text: text, + icon: badge, + size: size, + elevation: 3, + focusElevation: 2, + hoverElevation: 2, + highlightElevation: 4, + ); + + final TestApp app = TestApp(buttonBadge); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.text('12'), findsOneWidget); + + expect(app.buttonBadge.icon, badge); + expect(app.buttonBadge.text, text); + expect(app.buttonBadge.size, size); + expect(app.buttonBadge.elevation, 3); + expect(app.buttonBadge.focusElevation, 2); + expect(app.buttonBadge.hoverElevation, 2); + expect(app.buttonBadge.highlightElevation, 4); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.buttonBadge); + + final GFButtonBadge buttonBadge; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.buttonBadge, + ], + ), + ), + ); +} diff --git a/test/button_test.dart b/test/button_test.dart new file mode 100644 index 00000000..6a54b11f --- /dev/null +++ b/test/button_test.dart @@ -0,0 +1,380 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + const text = 'click me'; + const textStyle = TextStyle(fontSize: 14, color: Colors.black); + const size = GFSize.SMALL; + const childWidget = Text('tap me'); + + testWidgets('Disabled GF Button can be constructed', (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: null, + text: text, + size: size, + disabledColor: Colors.teal.shade300, + disabledElevation: 3, + disabledTextColor: GFColors.LIGHT, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.onPressed, null); + expect(app.button.disabledColor, Colors.teal.shade300); + expect(app.button.disabledElevation, 3); + expect(app.button.disabledTextColor, GFColors.LIGHT); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets('GF Button can be constructed', (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + color: GFColors.ALT, + textStyle: textStyle, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.textStyle, textStyle); + expect(app.button.size, size); + expect(app.button.color, GFColors.ALT); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets('GF Button can be constructed with child for custom text', + (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + child: childWidget, + color: Colors.amber, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(app.button.child, childWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.color, Colors.amber); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets( + 'GF Button can be constructed with standard type and shape i.e default', + (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + textColor: GFColors.DARK, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.textColor, GFColors.DARK); + expect(app.button.size, size); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + await tester.press(find.byKey(buttonKey)); + await tester.pump(); + await tester.longPress(find.byKey(buttonKey)); + await tester.pump(); + + // await expectLater(() => tester.tap(find.byKey(buttonKey)), prints('onHighlight changed')); + + await tester.pumpWidget(app); + }); + + testWidgets( + 'GF Button with icon and icon position' + 'and with Block Button', (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + child: childWidget, + icon: const Icon( + Icons.directions_bike_sharp, + ), + position: GFPosition.start, + blockButton: true, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.position, GFPosition.start); + expect(app.button.blockButton, isTrue); + }); + + testWidgets( + 'GF Button with solid type and pills shape ' + 'and Full Width Button', (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + child: childWidget, + icon: const Icon( + Icons.directions_bike_sharp, + ), + position: GFPosition.start, + type: GFButtonType.solid, + shape: GFButtonShape.pills, + fullWidthButton: true, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.position, GFPosition.start); + expect(app.button.type, GFButtonType.solid); + expect(app.button.shape, GFButtonShape.pills); + expect(app.button.fullWidthButton, isTrue); + }); + + testWidgets('GF Button with type outline and square shape ', (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + child: childWidget, + icon: const Icon( + Icons.directions_bike_sharp, + ), + position: GFPosition.start, + type: GFButtonType.outline, + shape: GFButtonShape.square, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.position, GFPosition.start); + expect(app.button.type, GFButtonType.outline); + expect(app.button.shape, GFButtonShape.square); + }); + + testWidgets('GF Button with custom border and shape ', (tester) async { + const buttonKey = Key('header'); + + const border = BorderSide( + color: Colors.tealAccent, width: 1, style: BorderStyle.solid); + const shape = RoundedRectangleBorder( + side: + BorderSide(color: Colors.pink, width: 2, style: BorderStyle.solid), + borderRadius: BorderRadius.zero); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + borderShape: shape, + borderSide: border, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.borderShape, shape); + expect(app.button.borderSide, border); + }); + + testWidgets('GF Button with boxshadow ', (tester) async { + const buttonKey = Key('header'); + + const boxshadow = BoxShadow( + color: Colors.pink, + blurRadius: 2, + spreadRadius: 1, + offset: Offset.zero, + ); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + buttonBoxShadow: true, + boxShadow: boxshadow, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.buttonBoxShadow, isTrue); + expect(app.button.boxShadow, boxshadow); + }); + + testWidgets( + 'GF Button with hover, focus, highlight and splash color. Works only in web', + (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + hoverColor: Colors.tealAccent, + focusColor: Colors.teal, + highlightColor: Colors.amber, + splashColor: Colors.red, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.hoverColor, Colors.tealAccent); + expect(app.button.focusColor, Colors.teal); + expect(app.button.highlightColor, Colors.amber); + expect(app.button.splashColor, Colors.red); + }); + + testWidgets( + 'GF Button with hover, focus, highlight and splash color. Works only in web', + (tester) async { + const buttonKey = Key('header'); + + final GFButton button = GFButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + elevation: 3, + focusElevation: 2, + hoverElevation: 2, + highlightElevation: 4, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.elevation, 3); + expect(app.button.focusElevation, 2); + expect(app.button.hoverElevation, 2); + expect(app.button.highlightElevation, 4); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.button); + + final GFButton button; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.button, + ], + ), + ), + ); +} diff --git a/test/card_test.dart b/test/card_test.dart new file mode 100644 index 00000000..e654244b --- /dev/null +++ b/test/card_test.dart @@ -0,0 +1,405 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +Future main() async { + final childWidget = Container( + width: 100, + height: 100, + ); + + const color = Colors.white; + const elevation = 2.0; + const borderOnForeground = false; + const padding = EdgeInsets.symmetric(horizontal: 12, vertical: 8); + const margin = EdgeInsets.all(1); + const title = GFListTile( + titleText: 'Card Title', + icon: Icon(Icons.favorite_border), + ); + const content = Text('Hello'); + final borderRadius = BorderRadius.circular(10); + const border = Border(top: BorderSide(color: Colors.black)); + + testWidgets('GF Card can be created.', (tester) async { + final GFCard card = GFCard( + color: color, + elevation: elevation, + borderOnForeground: borderOnForeground, + padding: padding, + margin: margin, + title: title, + content: childWidget, + image: Image.network( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(app); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.color, color); + expect(app.card.elevation, elevation); + expect(app.card.borderOnForeground, borderOnForeground); + expect(app.card.padding, padding); + expect(app.card.margin, margin); + expect(app.card.title, title); + expect(app.card.content, childWidget); + }); + + testWidgets('GF Card with color gradient.', (tester) async { + const gradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.red, Colors.pink]); + final GFCard card = GFCard( + gradient: gradient, + elevation: elevation, + borderOnForeground: borderOnForeground, + padding: padding, + margin: margin, + title: title, + image: Image.network( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(app); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.gradient, gradient); + expect(app.card.elevation, elevation); + expect(app.card.borderOnForeground, borderOnForeground); + expect(app.card.padding, padding); + expect(app.card.margin, margin); + expect(app.card.title, title); + }); + + testWidgets('GF Card with clip behaviour.', (tester) async { + final bgImage = Image.network( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + const clip = Clip.none; + + final GFCard card = GFCard( + image: bgImage, + title: title, + clipBehavior: clip, + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + expect(app.card.image, bgImage); + expect(app.card.title, title); + expect(app.card.clipBehavior, clip); + }); + + testWidgets('GF Card with background image.', (tester) async { + final bgImage = Image.network( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + const boxFit = BoxFit.cover; + + final GFCard card = GFCard( + image: bgImage, + title: title, + boxFit: boxFit, + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + expect(app.card.image, bgImage); + expect(app.card.title, title); + expect(app.card.boxFit, boxFit); + }); + + testWidgets('GF Card with background image & image overlay.', (tester) async { + final bgImage = Image.network( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + const boxFit = BoxFit.cover; + const imageOverlay = NetworkImage( + 'https://cdn.pixabay.com/photo/2016/11/22/07/09/spruce-1848543__340.jpg'); + + final GFCard card = GFCard( + image: bgImage, + title: title, + boxFit: boxFit, + imageOverlay: imageOverlay, + ); + + final TestApp app = TestApp(card); + + expect(app.card.image, bgImage); + expect(app.card.title, title); + expect(app.card.boxFit, boxFit); + expect(app.card.imageOverlay, imageOverlay); + }); + + testWidgets('GF Card with semantic container.', (tester) async { + const isSemanticContainer = false; + + final GFCard card = GFCard( + semanticContainer: isSemanticContainer, + elevation: elevation, + borderOnForeground: borderOnForeground, + title: title, + image: Image.network( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(app); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.semanticContainer, isSemanticContainer); + expect(app.card.elevation, elevation); + expect(app.card.borderOnForeground, borderOnForeground); + expect(app.card.title, title); + }); + + testWidgets('GF Card with title position.', (tester) async { + final bgImage = Image.network( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + const titlePosition = GFPosition.end; + + final GFCard card = GFCard( + image: bgImage, + title: title, + titlePosition: titlePosition, + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + expect(app.card.image, bgImage); + expect(app.card.title, title); + expect(app.card.titlePosition, titlePosition); + }); + + testWidgets('GF Card with border & border radius.', (tester) async { + final bgImage = Image.network( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + final GFCard card = GFCard( + title: title, + border: border, + borderRadius: borderRadius, + image: bgImage, + content: content, + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.title, title); + expect(app.card.border, border); + expect(app.card.borderRadius, borderRadius); + expect(app.card.content, content); + expect(app.card.image, bgImage); + }); + + testWidgets('GF Card with custom button bar.', (tester) async { + final bgImage = Image.network( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + const customButtonBar = GFButtonBar( + children: [ + GFButton( + onPressed: null, + child: Text('Read More'), + icon: Icon(Icons.keyboard_arrow_right), + type: GFButtonType.transparent, + ), + ], + ); + final GFCard card = GFCard( + color: color, + elevation: elevation, + buttonBar: customButtonBar, + title: title, + image: bgImage, + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.color, color); + expect(app.card.elevation, elevation); + expect(app.card.buttonBar, customButtonBar); + expect(app.card.image, bgImage); + }); + + testWidgets('GF Card with custom title.', (tester) async { + const customTitle = GFListTile( + titleText: 'Card Title', + icon: Icon(Icons.favorite_border), + ); + final GFCard card = GFCard( + color: color, + title: customTitle, + image: Image.network( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(app); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.color, color); + expect(app.card.title, customTitle); + }); + + testWidgets('GF Card with GFAvatar & subTitle in title section.', + (tester) async { + const customTitle = GFListTile( + avatar: GFAvatar(), + titleText: 'Card Title', + subTitleText: 'Sub title', + icon: Icon(Icons.favorite_border), + ); + final GFCard card = GFCard( + color: color, + title: customTitle, + image: Image.network( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + imageOverlay: const NetworkImage( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(app); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.color, color); + expect(app.card.title, customTitle); + }); + + testWidgets('GF Card with full background image & button bar.', + (tester) async { + const imageOverlay = NetworkImage( + 'https://cdn.pixabay.com/photo/2016/11/22/07/09/spruce-1848543__340.jpg'); + final colorFilter = + ColorFilter.mode(Colors.black.withOpacity(0.67), BlendMode.darken); + const customTitle = GFListTile( + titleText: 'Card Title', + subTitleText: 'Sub title', + ); + final GFCard card = GFCard( + color: color, + title: customTitle, + imageOverlay: imageOverlay, + colorFilter: colorFilter, + image: Image.network( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.color, color); + expect(app.card.title, customTitle); + expect(app.card.imageOverlay, imageOverlay); + expect(app.card.colorFilter, colorFilter); + }); + + testWidgets( + 'GF Card with full background image, customized title & button bar.', + (tester) async { + const imageOverlay = NetworkImage( + 'https://cdn.pixabay.com/photo/2016/11/22/07/09/spruce-1848543__340.jpg'); + final colorFilter = + ColorFilter.mode(Colors.black.withOpacity(0.67), BlendMode.darken); + const customTitle = GFListTile( + avatar: GFAvatar(), + titleText: 'Card Title', + subTitleText: 'Sub title', + icon: Icon(Icons.favorite_border), + ); + const customButtonBar = GFButtonBar( + children: [ + GFButton( + onPressed: null, + child: Text('Read More'), + icon: Icon(Icons.keyboard_arrow_right), + type: GFButtonType.transparent, + ), + ], + ); + final GFCard card = GFCard( + color: color, + title: customTitle, + imageOverlay: imageOverlay, + colorFilter: colorFilter, + buttonBar: customButtonBar, + image: Image.network( + 'https://cdn.pixabay.com/photo/2021/02/01/16/22/flamingo-5971206__340.jpg'), + ); + + final TestApp app = TestApp(card); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.card.color, color); + expect(app.card.title, customTitle); + expect(app.card.imageOverlay, imageOverlay); + expect(app.card.colorFilter, colorFilter); + expect(app.card.buttonBar, customButtonBar); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.card); + + final GFCard card; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + bool fav = false; + + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.card, + ], + ), + ), + ); +} diff --git a/test/carousel_test.dart b/test/carousel_test.dart new file mode 100644 index 00000000..9b3e616a --- /dev/null +++ b/test/carousel_test.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +class StateMarker extends StatefulWidget { + const StateMarker({Key? key, this.child}) : super(key: key); + final Widget? child; + @override + StateMarkerState createState() => StateMarkerState(); +} + +class StateMarkerState extends State { + String? marker; + @override + Widget build(BuildContext context) { + if (widget.child != null) { + return widget.child!; + } + return Container(); + } +} + +void main() { + final Key carouselKey = UniqueKey(); + final List textList = [ + 'AAAAAA', + 'BBBBBB', + 'CCCCCC', + 'DDDDDD', + 'EEEEEE' + ]; + final List itemList = [ + Text(textList[0]), + Text(textList[1]), + Text(textList[2]), + Text(textList[3]), + Text(textList[4]) + ]; + + testWidgets('GFCarousel can be constructed', (tester) async { + String value = textList[0]; + late int changedIndex; + + final GFCarousel carousel = GFCarousel( + key: carouselKey, + items: itemList.map((text) => StateMarker(child: text)).toList(), + onPageChanged: (index) { + changedIndex = index; + print('inr $index $changedIndex'); + }, + ); + + StateMarkerState findStateMarkerState(String name) => + tester.state(find.widgetWithText(StateMarker, name)); + + final TestApp app = TestApp(carousel); + await tester.pumpWidget(app); + + // find carousel by key + expect(find.byKey(carouselKey), findsOneWidget); + // find the 'AAAAAA' text in carousel + expect(find.text('AAAAAA'), findsOneWidget); + + TestGesture gesture = + await tester.startGesture(tester.getCenter(find.text('AAAAAA'))); + await gesture.moveBy(const Offset(-600, 0)); + await tester.pump(); + expect(value, equals(textList[0])); + findStateMarkerState(textList[1]).marker = 'marked'; + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + value = textList[changedIndex]; + expect(value, equals(textList[1])); + await tester.pumpWidget(app); + expect(findStateMarkerState(textList[1]).marker, equals('marked')); + + // slide on to the third slide. + gesture = + await tester.startGesture(tester.getCenter(find.text(textList[1]))); + await gesture.moveBy(const Offset(-600, 0)); + await gesture.up(); + await tester.pump(); + expect(findStateMarkerState(textList[1]).marker, equals('marked')); + await tester.pump(const Duration(seconds: 1)); + value = textList[changedIndex]; + expect(value, equals(textList[2])); + await tester.pumpWidget(app); + expect(find.text(textList[2]), findsOneWidget); + // slide back to the second slide. + gesture = + await tester.startGesture(tester.getCenter(find.text(textList[2]))); + await gesture.moveBy(const Offset(600, 0)); + await tester.pump(); + final StateMarkerState markerState = findStateMarkerState(textList[1]); + markerState.marker = 'marked'; + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + value = textList[changedIndex]; + expect(value, equals(textList[1])); + await tester.pumpWidget(app); + expect(findStateMarkerState(textList[1]).marker, equals('marked')); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.carousel); + + final GFCarousel carousel; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.carousel, + ], + ), + ), + ); +} diff --git a/test/checkbox_list_tile_test.dart b/test/checkbox_list_tile_test.dart new file mode 100644 index 00000000..9a9f5c9d --- /dev/null +++ b/test/checkbox_list_tile_test.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/rendering.dart'; + +Widget wrap({required Widget child}) => MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: Material(child: child), + ), + ); + +void main() { + final UniqueKey checkboxlistileKey = UniqueKey(); + const bool isChecked = false; + testWidgets('GFCheckboxListTile can be constructed Function call Null', + (tester) async { + final GFCheckboxListTile checkboxListTile = GFCheckboxListTile( + key: checkboxlistileKey, + onChanged: null, + value: isChecked, + ); + + final TestApp app = TestApp(checkboxListTile); + await tester.pumpWidget(app); + await tester.pump(); + expect(find.byKey(checkboxlistileKey), findsOneWidget); + expect(app.checkboxListTile.onChanged, null); + }); + + testWidgets('CheckboxListTile can autofocus unless disabled', (tester) async { + final GlobalKey childKey = GlobalKey(); + final checkboxListTile = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFCheckboxListTile( + value: true, + onChanged: (_) {}, + title: Text('Hello', key: childKey), + autofocus: false, + ), + )), + ); + await tester.pumpWidget(checkboxListTile); + await tester.pump(); + expect(Focus.maybeOf(childKey.currentContext!)!.hasPrimaryFocus, isFalse); + }); + + testWidgets('CheckboxListTile can autofocus is True', (tester) async { + final GlobalKey childKey = GlobalKey(); + final checkboxListTile = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFCheckboxListTile( + value: true, + onChanged: (_) {}, + title: Text('Hello', key: childKey), + autofocus: true, + ), + )), + ); + await tester.pumpWidget(checkboxListTile); + await tester.pump(); + expect(Focus.maybeOf(childKey.currentContext!)!.hasPrimaryFocus, isTrue); + }); + + testWidgets('CheckboxListTile values test', (WidgetTester tester) async { + bool _value = false; + + await tester.pumpWidget( + Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => wrap( + child: GFCheckboxListTile( + title: const Text('Title'), + value: _value, + onChanged: (bool value) { + setState(() { + _value = value; + }); + }, + ), + ), + ), + ), + ); + + expect(tester.widget(find.byType(GFCheckbox)).value, false); + + // Tap the checkbox when value is disabled. + await tester.tap(find.byType(GFCheckbox)); + await tester.pumpAndSettle(); + expect(_value, true); + + await tester.tap(find.byType(GFCheckbox)); + await tester.pumpAndSettle(); + expect(_value, false); + + // Tap the listTile when value is disabled. + await tester.tap(find.byType(GFListTile)); + await tester.pumpAndSettle(); + expect(_value, true); + + await tester.tap(find.byType(GFListTile)); + await tester.pumpAndSettle(); + expect(_value, false); + }); + + testWidgets('CheckboxListTile selected item text Color', + (WidgetTester tester) async { + const Color colors = Color(0xff00ff00); + + Widget buildFrame({Color? colors, Color? toggleableActiveColor}) => + MaterialApp( + theme: ThemeData.light().copyWith( + toggleableActiveColor: toggleableActiveColor, + ), + home: Scaffold( + body: Center( + child: GFCheckboxListTile( + color: colors, + selected: true, + title: const Text('title'), + value: true, + onChanged: (bool? value) {}, + ), + ), + ), + ); + + Color? textColor(String text) => + tester.renderObject(find.text(text)).text.style?.color; + + await tester.pumpWidget(buildFrame(toggleableActiveColor: colors)); + print(textColor('title')); + expect(textColor('title'), const Color(0xdd000000)); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.checkboxListTile); + + final GFCheckboxListTile checkboxListTile; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.checkboxListTile, + ], + ), + ), + ); +} diff --git a/test/checkbox_test.dart b/test/checkbox_test.dart new file mode 100644 index 00000000..a76db4ec --- /dev/null +++ b/test/checkbox_test.dart @@ -0,0 +1,262 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; + +void main() { + final UniqueKey checkboxKey = UniqueKey(); + bool isChecked = false; + testWidgets('Checkbox button can be constructed Function call Null', + (tester) async { + final GFCheckbox checkbox = GFCheckbox( + key: checkboxKey, + onChanged: null, + value: isChecked, + ); + + final TestApp app = TestApp(checkbox); + await tester.pumpWidget(app); + await tester.pump(); + expect(find.byKey(checkboxKey), findsOneWidget); + expect(app.checkbox.onChanged, null); + expect( + app.checkbox.value, + isChecked, + ); + }); + + testWidgets('Basic Checkbox can be constructed with Value ', (tester) async { + final checkbox = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFCheckbox( + key: checkboxKey, + size: GFSize.SMALL, + activeBgColor: GFColors.DANGER, + onChanged: (value) { + setState(() { + isChecked = value; + }); + }, + value: isChecked, + ), + )), + ); + await tester.pumpWidget(checkbox); + expect(find.byKey(checkboxKey), findsOneWidget); + expect(isChecked, isFalse); + }); + + testWidgets('Checkbox button set True and False Values', + (WidgetTester tester) async { + final checkbox = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFCheckbox( + key: checkboxKey, + size: GFSize.SMALL, + activeBgColor: GFColors.DANGER, + onChanged: (value) { + setState(() { + isChecked = value; + }); + }, + value: isChecked, + ), + )), + ); + // find checkbox key + await tester.pumpWidget(checkbox); + expect(find.byKey(checkboxKey), findsOneWidget); + + // finds value + expect(isChecked, isFalse); + expect(tester.widget(find.byType(GFCheckbox)).value, isChecked); + await tester.tap(find.byType(GFCheckbox)); + + await tester.pumpAndSettle(); + print(isChecked); + expect(isChecked, isTrue); + + await tester.tap(find.byType(GFCheckbox)); + await tester.pumpAndSettle(); + print(isChecked); + expect(isChecked, isFalse); + + await tester.tap(find.byType(GFCheckbox)); + await tester.pumpAndSettle(); + print(isChecked); + expect(isChecked, isTrue); + + isChecked = true; + await tester.pumpAndSettle(); + expect(isChecked, true); + }); + + testWidgets('Checkbox is focusable and has correct focus color', + (WidgetTester tester) async { + final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox'); + tester.binding.focusManager.highlightStrategy = + FocusHighlightStrategy.alwaysTraditional; + + final checkbox = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: Center( + child: GFCheckbox( + key: checkboxKey, + size: GFSize.SMALL, + activeBgColor: GFColors.DANGER, + onChanged: (value) { + setState(() { + isChecked = value; + }); + }, + value: isChecked, + autofocus: true, + focusNode: focusNode, + ), + )), + ), + ); + + await tester.pumpWidget(checkbox); + expect(find.byKey(checkboxKey), findsOneWidget); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + }); + + testWidgets('Checkbox can be toggled by keyboard shortcuts', + (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = + FocusHighlightStrategy.alwaysTraditional; + bool value = true; + + Widget buildApp({bool enabled = true}) => MaterialApp( + home: Material( + child: Center( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => + GFCheckbox( + value: value, + onChanged: enabled + ? (bool newValue) { + setState(() { + value = newValue; + }); + } + : null, + autofocus: true, + )), + ), + ), + ); + + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + expect(value, isTrue); + }); + + testWidgets('Checkbox size is configurable by ThemeData', + (WidgetTester tester) async { + final checkbox = Theme( + data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), + child: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: Center( + child: GFCheckbox( + key: checkboxKey, + activeBgColor: GFColors.DANGER, + onChanged: (value) { + setState(() { + isChecked = value; + }); + }, + value: isChecked, + size: 48, + ), + ))), + ), + ); + await tester.pumpWidget(checkbox); + expect(find.byKey(checkboxKey), findsOneWidget); + expect(tester.getSize(find.byKey(checkboxKey)), const Size(48, 48)); + }); + + testWidgets('Checkbox can be constructed with Active & InactiveIcon ', + (tester) async { + const activeIcon = Icon(Icons.sentiment_satisfied); + const inactiveIcon = Icon(Icons.sentiment_dissatisfied); + + final GFCheckbox checkbox = GFCheckbox( + key: checkboxKey, + value: isChecked, + onChanged: null, + activeIcon: activeIcon, + inactiveIcon: inactiveIcon, + ); + + final TestApp app = TestApp(checkbox); + await tester.pumpWidget(app); + await tester.pump(); + expect(find.byKey(checkboxKey), findsOneWidget); + expect(app.checkbox.activeIcon, activeIcon); + expect(app.checkbox.inactiveIcon, inactiveIcon); + }); + + testWidgets('Checkbox can be constructed with Colors ', (tester) async { + final GFCheckbox checkbox = GFCheckbox( + key: checkboxKey, + activeBgColor: Colors.amber, + customBgColor: Colors.amberAccent, + inactiveBgColor: Colors.blue, + activeBorderColor: Colors.deepPurple, + inactiveBorderColor: Colors.green, + value: isChecked, + onChanged: (value) { + isChecked = true; + }, + ); + + final TestApp app = TestApp(checkbox); + await tester.pumpWidget(app); + await tester.pump(); + expect(find.byKey(checkboxKey), findsOneWidget); + expect(app.checkbox.activeBgColor, Colors.amber); + expect(app.checkbox.customBgColor, Colors.amberAccent); + expect(app.checkbox.inactiveBgColor, Colors.blue); + expect(app.checkbox.activeBorderColor, Colors.deepPurple); + expect(app.checkbox.inactiveBorderColor, Colors.green); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.checkbox); + + final GFCheckbox checkbox; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.checkbox, + ], + ), + ), + ); +} diff --git a/test/dropdown_test.dart b/test/dropdown_test.dart new file mode 100644 index 00000000..9a1b5502 --- /dev/null +++ b/test/dropdown_test.dart @@ -0,0 +1,408 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; + +void main() { + const iconSize = 24.0; + final List itemList = ['one', 'two', 'three', 'four', 'five', 'Six']; + + testWidgets('Dropdown button can be constructed Null Values', (tester) async { + const dropdownKey = Key('header'); + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemList + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + value: null, + onChanged: null, + ); + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + expect(find.byKey(dropdownKey), findsOneWidget); + await tester.pump(); + expect(app.dropdown.onChanged, null); + expect( + app.dropdown.value, + null, + ); + }); + + testWidgets('Dropdown button control can be constructed', (tester) async { + const dropdownKey = Key('header'); + String value = 'one'; + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemList + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + value: value, + onChanged: (newValue) { + value = newValue; + }, + ); + + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + + await tester.pumpWidget(app); + await tester.tap(find.text('one')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(value, equals('one')); + await tester.tap(find.text('three').last); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(value, equals('three')); + + await tester.tap(find.text('three')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(value, equals('three')); + + await tester.pumpWidget(app); + await tester.tap(find.text('two').last); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(value, equals('two')); + }); + + testWidgets('Dropdown button can be constructed with items length', + (tester) async { + const dropdownKey = Key('header'); + String dropdownValue = 'one'; + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemList + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + value: dropdownValue, + onChanged: (newValue) { + dropdownValue = newValue; + }, + ); + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + expect(find.byKey(dropdownKey), findsOneWidget); + await tester.pump(); + expect(app.dropdown.items!.length, itemList.length); + expect(app.dropdown.value, dropdownValue); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(itemList.length, equals(6)); + }); + + testWidgets('DropdownButton disabledHint is null by default', (tester) async { + const dropdownKey = Key('header'); + const String value = 'one'; + final List itemLists = []; + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemLists + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + value: value, + onChanged: null, + hint: const Text('hint used when disabled'), + ); + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + + // [hint] should display when [items] is an empty list + expect(find.text('hint used when disabled'), findsOneWidget); + }); + + testWidgets( + 'DropdowwnButton hint displays when the items list is empty, items is null, and disabledHint is null', + (tester) async { + const dropdownKey = Key('header'); + final List itemLists = []; + String value = 'one'; + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemLists + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + value: value, + onChanged: null, + disabledHint: null, + hint: const Text('hint used when disabled'), + ); + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + expect(find.text('hint used when disabled'), findsOneWidget); + + expect(find.byKey(dropdownKey), findsOneWidget); + + final RenderBox buttonBoxHintValue = + tester.renderObject(find.byKey(dropdownKey)); + assert(buttonBoxHintValue.attached); + + value = 'one'; + await tester.pumpWidget(app); + final RenderBox buttonBox = tester.renderObject(find.byKey(dropdownKey)); + assert(buttonBox.attached); + print(itemList[0]); + + expect(buttonBox.localToGlobal(Offset.zero), + equals(buttonBoxHintValue.localToGlobal(Offset.zero))); + expect(buttonBox.size, equals(buttonBoxHintValue.size)); + }); + + testWidgets('DropdownButton is activated with the enter key', (tester) async { + const dropdownKey = Key('header'); + final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton'); + String value = 'one'; + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemList + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + value: value, + itemHeight: null, + onChanged: (newValue) { + value = newValue; + }, + focusNode: focusNode, + autofocus: true, + ); + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + + expect(focusNode.hasPrimaryFocus, isTrue); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // finish the menu animation + expect(value, equals('one')); + + await tester.sendKeyEvent(LogicalKeyboardKey.tab); // Focus 'two' + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); // Select 'two'. + await tester.pump(); + + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // finish the menu animation + + expect(value, equals('two')); + }); + + testWidgets('DropdownButton is activated with the space key', (tester) async { + const dropdownKey = Key('header'); + final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton'); + String value = 'one'; + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemList + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + value: value, + itemHeight: null, + onChanged: (newValue) { + value = newValue; + }, + focusNode: focusNode, + autofocus: true, + ); + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + + expect(focusNode.hasPrimaryFocus, isTrue); + await tester.sendKeyEvent(LogicalKeyboardKey.space); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // finish the menu animation + expect(value, equals('one')); + + await tester.sendKeyEvent(LogicalKeyboardKey.tab); // Focus 'two' + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.space); // Select 'two'. + await tester.pump(); + + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // finish the menu animation + + expect(value, equals('two')); + }); + + testWidgets('DropdownButton won\'t be focused if not enabled', + (tester) async { + final UniqueKey dropdownKey = UniqueKey(); + final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton'); + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemList + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + onChanged: null, + focusNode: focusNode, + autofocus: true, + focusColor: const Color(0xff00ff00)); + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + + await tester + .pump(); // Pump a frame for autofocus to take effect (although it shouldn't). + expect(focusNode.hasPrimaryFocus, isFalse); + }); + + testWidgets('DropdownButton changes selected item with arrow keys', + (tester) async { + final UniqueKey dropdownKey = UniqueKey(); + final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton'); + String value = 'one'; + final GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: itemList + .map((value) => DropdownMenuItem( + value: value, + child: Text(value), + )) + .toList(), + value: value, + itemHeight: null, + onChanged: (newValue) { + value = newValue; + }, + focusNode: focusNode, + autofocus: true, + focusColor: const Color(0xff00ff00)); + final TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + + await tester.pumpWidget(app); + await tester.pump(); + expect(focusNode.hasPrimaryFocus, isTrue); + + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // finish the menu animation + expect(value, equals('one')); + + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); // Focus 'two'. + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); // Focus 'three'. + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); // Back to 'two'. + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); // Select 'two'. + await tester.pump(); + + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // finish the menu animation + + expect(value, equals('two')); + }); + + testWidgets('GFDropdown Color Property can be constructed ', (tester) async { + const dropdownKey = Key('header'); + const GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: null, + onChanged: null, + iconDisabledColor: Colors.amber, + iconEnabledColor: Colors.blue, + dropdownColor: Colors.redAccent, + focusColor: Colors.red, + ); + + const TestApp app = TestApp(dropdown); + + await tester.pumpWidget(app); + expect(app.dropdown.iconDisabledColor, Colors.amber); + expect( + app.dropdown.iconEnabledColor, + Colors.blue, + ); + expect( + app.dropdown.dropdownColor, + Colors.redAccent, + ); + expect( + app.dropdown.focusColor, + Colors.red, + ); + + await tester.tap(find.byKey(dropdownKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets('GFDropdown Button Property can be constructed', (tester) async { + const dropdownKey = Key('header'); + const GFDropdown dropdown = GFDropdown( + key: dropdownKey, + items: null, + isDense: true, + isExpanded: false, + autofocus: false, + onChanged: null, + iconSize: iconSize, + itemHeight: 40, + elevation: 8, + ); + + const TestApp app = TestApp(dropdown); + await tester.pumpWidget(app); + + expect(app.dropdown.iconSize, iconSize); + expect(app.dropdown.onChanged, null); + expect(app.dropdown.isExpanded, false); + expect(app.dropdown.isDense, true); + expect(app.dropdown.autofocus, false); + expect(app.dropdown.elevation, 8); + expect(app.dropdown.itemHeight, 40); + + await tester.tap(find.byKey(dropdownKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.dropdown); + + final GFDropdown dropdown; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.dropdown, + ], + ), + ), + ); +} diff --git a/test/icon_badge_test.dart b/test/icon_badge_test.dart new file mode 100644 index 00000000..f720a3ff --- /dev/null +++ b/test/icon_badge_test.dart @@ -0,0 +1,93 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + const badge = GFBadge( + child: Text('12'), + ); + const size = GFSize.SMALL; + + testWidgets('Disabled GF IconBadge can be constructed', (tester) async { + const iconBadgeKey = Key('header'); + + final GFIconBadge iconBadge = GFIconBadge( + counterChild: badge, + child: GFIconButton( + key: iconBadgeKey, + onPressed: null, + icon: const Icon( + Icons.directions_bike_sharp, + ), + size: size, + disabledColor: Colors.teal.shade300, + ), + ); + + final TestApp app = TestApp(iconBadge); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + expect(find.byWidget(badge), findsOneWidget); + expect(app.iconBadge.counterChild, badge); + + await tester.tap(find.byKey(iconBadgeKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets('GF IconBadge can be constructed', (tester) async { + const iconBadgeKey = Key('header'); + + final GFIconBadge iconBadge = GFIconBadge( + counterChild: badge, + child: GFIconButton( + key: iconBadgeKey, + onPressed: () {}, + icon: const Icon( + Icons.directions_bike_sharp, + ), + size: size, + color: Colors.teal, + ), + ); + + final TestApp app = TestApp(iconBadge); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + expect(find.byWidget(badge), findsOneWidget); + expect(app.iconBadge.counterChild, badge); + + await tester.tap(find.byKey(iconBadgeKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.iconBadge); + + final GFIconBadge iconBadge; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.iconBadge, + ], + ), + ), + ); +} diff --git a/test/icon_button_test.dart b/test/icon_button_test.dart new file mode 100644 index 00000000..ee8dbe1f --- /dev/null +++ b/test/icon_button_test.dart @@ -0,0 +1,262 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + const size = GFSize.SMALL; + + testWidgets('Disabled GF IconButton can be constructed', (tester) async { + const buttonKey = Key('header'); + + final GFIconButton button = GFIconButton( + key: buttonKey, + onPressed: null, + icon: const Icon( + Icons.directions_bike_sharp, + ), + size: size, + disabledColor: Colors.teal.shade300, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + expect(app.button.size, size); + expect(app.button.onPressed, null); + expect(app.button.disabledColor, Colors.teal.shade300); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets('GF IconButton can be constructed', (tester) async { + const buttonKey = Key('header'); + + final GFIconButton button = GFIconButton( + key: buttonKey, + onPressed: () {}, + icon: const Icon( + Icons.directions_bike_sharp, + ), + size: size, + color: GFColors.ALT, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + expect(app.button.size, size); + expect(app.button.color, GFColors.ALT); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets( + 'GF IconButton can be constructed with standard type and shape i.e default', + (tester) async { + const buttonKey = Key('header'); + + final GFIconButton button = GFIconButton( + key: buttonKey, + onPressed: () {}, + size: size, + icon: const Icon( + Icons.directions_bike_sharp, + ), + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + expect(app.button.size, size); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + await tester.press(find.byKey(buttonKey)); + await tester.pump(); + await tester.longPress(find.byKey(buttonKey)); + await tester.pump(); + + // await expectLater(() => tester.tap(find.byKey(buttonKey)), prints('onHighlight changed')); + + await tester.pumpWidget(app); + }); + + testWidgets('GF IconButton with solid type and pills shape ', (tester) async { + const buttonKey = Key('header'); + + final GFIconButton button = GFIconButton( + key: buttonKey, + onPressed: () {}, + iconSize: 24, + icon: const Icon( + Icons.directions_bike_sharp, + ), + type: GFButtonType.solid, + shape: GFIconButtonShape.pills, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + + expect(app.button.iconSize, 24); + expect(app.button.type, GFButtonType.solid); + expect(app.button.shape, GFIconButtonShape.pills); + }); + + testWidgets('GF IconButton with type outline and round shape ', + (tester) async { + const buttonKey = Key('header'); + + final GFIconButton button = GFIconButton( + key: buttonKey, + onPressed: () {}, + icon: const Icon( + Icons.directions_bike_sharp, + ), + size: size, + type: GFButtonType.outline, + shape: GFIconButtonShape.circle, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + + expect(app.button.size, size); + expect(app.button.type, GFButtonType.outline); + expect(app.button.shape, GFIconButtonShape.circle); + }); + + testWidgets('GF IconButton with custom border and shape ', (tester) async { + const buttonKey = Key('header'); + + const border = BorderSide( + color: Colors.tealAccent, width: 1, style: BorderStyle.solid); + const shape = RoundedRectangleBorder( + side: + BorderSide(color: Colors.pink, width: 2, style: BorderStyle.solid), + borderRadius: BorderRadius.zero); + + final GFIconButton button = GFIconButton( + key: buttonKey, + onPressed: () {}, + icon: const Icon( + Icons.directions_bike_sharp, + ), + size: size, + borderShape: shape, + borderSide: border, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + expect(app.button.size, size); + expect(app.button.borderShape, shape); + expect(app.button.borderSide, border); + }); + + testWidgets('GF IconButton with boxshadow ', (tester) async { + const buttonKey = Key('header'); + + const boxshadow = BoxShadow( + color: Colors.pink, + blurRadius: 2, + spreadRadius: 1, + offset: Offset.zero, + ); + + final GFIconButton button = GFIconButton( + key: buttonKey, + onPressed: () {}, + icon: const Icon( + Icons.directions_bike_sharp, + ), + size: size, + buttonBoxShadow: true, + boxShadow: boxshadow, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + expect(app.button.size, size); + expect(app.button.buttonBoxShadow, isTrue); + expect(app.button.boxShadow, boxshadow); + }); + + testWidgets( + 'GF IconButton with hover, focus, highlight and splash color. Works only in web', + (tester) async { + const buttonKey = Key('header'); + + final GFIconButton button = GFIconButton( + key: buttonKey, + onPressed: () {}, + icon: const Icon( + Icons.directions_bike_sharp, + ), + size: size, + hoverColor: Colors.tealAccent, + focusColor: Colors.teal, + highlightColor: Colors.amber, + splashColor: Colors.red, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(app.button.size, size); + expect(app.button.hoverColor, Colors.tealAccent); + expect(app.button.focusColor, Colors.teal); + expect(app.button.highlightColor, Colors.amber); + expect(app.button.splashColor, Colors.red); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.button); + + final GFIconButton button; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.button, + ], + ), + ), + ); +} diff --git a/test/image_test.dart b/test/image_test.dart new file mode 100644 index 00000000..c7f365d2 --- /dev/null +++ b/test/image_test.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + final childWidget = Container( + width: 100, + height: 100, + ); + const color = Colors.teal; + const padding = EdgeInsets.all(5); + const margin = EdgeInsets.all(5); + const boxFit = BoxFit.cover; + const bgImage = NetworkImage( + 'https://images.unsplash.com/photo-1547721064-da6cfb341d50', + ); + + testWidgets('GF Image can be created.', (tester) async { + final GFImageOverlay image = GFImageOverlay( + image: bgImage, + color: color, + padding: padding, + boxFit: boxFit, + margin: margin, + child: childWidget); + + final TestApp app = TestApp(image); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.image.image, bgImage); + expect(app.image.color, color); + expect(app.image.padding, padding); + expect(app.image.boxFit, boxFit); + expect(app.image.margin, margin); + expect(app.image.child, childWidget); + }); + + testWidgets('GF Image with border & border radius.', (tester) async { + final borderRadius = BorderRadius.circular(10); + final border = Border.all(color: Colors.red); + + final GFImageOverlay image = GFImageOverlay( + image: bgImage, + color: color, + border: border, + borderRadius: borderRadius, + padding: padding, + margin: margin, + ); + + final TestApp app = TestApp(image); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.image.image, bgImage); + expect(app.image.color, color); + expect(app.image.border, border); + expect(app.image.borderRadius, borderRadius); + expect(app.image.padding, padding); + expect(app.image.margin, margin); + }); + + testWidgets('GF Image with child icon.', (tester) async { + const content = Icon(Icons.car_rental); + + const GFImageOverlay image = GFImageOverlay( + image: bgImage, + color: color, + child: content, + ); + const TestApp app = TestApp(image); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + expect(app.image.image, bgImage); + expect(app.image.color, color); + expect(app.image.child, content); + }); + + testWidgets('GF Image with circular shape', (tester) async { + const shape = BoxShape.circle; + + const GFImageOverlay image = GFImageOverlay( + image: bgImage, + shape: shape, + ); + + const TestApp app = TestApp(image); + + expect(app.image.image, bgImage); + expect(app.image.shape, shape); + }); + + testWidgets('GF Image with image overlay', (tester) async { + const colorFilter = ColorFilter.mode(Colors.black26, BlendMode.colorBurn); + + const GFImageOverlay image = GFImageOverlay( + image: bgImage, + colorFilter: colorFilter, + ); + + const TestApp app = TestApp(image); + + expect(app.image.image, bgImage); + expect(app.image.colorFilter, colorFilter); + }); + + testWidgets('GF Image with alignment of child widget', (tester) async { + const alignment = Alignment.bottomCenter; + + const GFImageOverlay image = GFImageOverlay( + image: bgImage, + alignment: alignment, + ); + + const TestApp app = TestApp(image); + + expect(app.image.image, bgImage); + expect(app.image.alignment, alignment); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.image); + final GFImageOverlay image; + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.image, + ], + ), + ), + ); +} diff --git a/test/intro_screen_test.dart b/test/intro_screen_test.dart new file mode 100644 index 00000000..a20186f8 --- /dev/null +++ b/test/intro_screen_test.dart @@ -0,0 +1,537 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +class StateMarker extends StatefulWidget { + const StateMarker({Key? key, this.child}) : super(key: key); + final Widget? child; + @override + StateMarkerState createState() => StateMarkerState(); +} + +class StateMarkerState extends State { + String? marker; + @override + Widget build(BuildContext context) { + if (widget.child != null) { + return widget.child!; + } + return Container(); + } +} + +void main() { + final Key bottomBarKey = UniqueKey(); + final Key introScreenKey = UniqueKey(); + final PageController _pageController = PageController( + initialPage: 0, + ); + final int initialPage = _pageController.initialPage; + final List slideList = [ + Container(color: Colors.teal, child: const Text('Page 1')), + Container(color: Colors.amber, child: const Text('Page 2')), + Container(color: Colors.blueGrey, child: const Text('Page 3')), + Container(color: Colors.orange, child: const Text('Page 4')), + ]; + final textList = ['Page 1', 'Page 2', 'Page 3', 'Page 4']; + final List slide = [ + Container(color: Colors.teal, child: Text(textList[0])), + Container(color: Colors.amber, child: Text(textList[1])), + Container(color: Colors.blueGrey, child: Text(textList[2])), + Container(color: Colors.orange, child: Text(textList[3])), + ]; + String value = textList[0]; + + testWidgets('Basic GFIntroScreen can be constructed', (tester) async { + final GFIntroScreen introScreen = GFIntroScreen( + key: introScreenKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: slide.length, + slides: slide.map((data) => StateMarker(child: data)).toList()); + + StateMarkerState findStateMarkerState(String name) => + tester.state(find.widgetWithText(StateMarker, name)); + + final TestApp app = TestApp(introScreen); + await tester.pumpWidget(app); + + // find intro screen by key + expect(find.byKey(introScreenKey), findsOneWidget); + // find first screen text + expect(find.text('Page 1'), findsOneWidget); + + TestGesture gesture = + await tester.startGesture(tester.getCenter(find.text('Page 1'))); + await gesture.moveBy(const Offset(-600, 0)); + await tester.pump(); + expect(value, equals(textList[0])); + findStateMarkerState(textList[1]).marker = 'marked'; + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + value = textList[_pageController.page!.toInt()]; + expect(value, equals(textList[1])); + await tester.pumpWidget(app); + expect(findStateMarkerState(textList[1]).marker, equals('marked')); + + // slide on to the third tab. + gesture = + await tester.startGesture(tester.getCenter(find.text(textList[1]))); + await gesture.moveBy(const Offset(-600, 0)); + await gesture.up(); + await tester.pump(); + expect(findStateMarkerState(textList[1]).marker, equals('marked')); + await tester.pump(const Duration(seconds: 1)); + value = textList[_pageController.page!.toInt()]; + expect(value, equals(textList[2])); + await tester.pumpWidget(app); + expect(find.text(textList[1]), findsNothing); + // slide back to the second tab. + gesture = + await tester.startGesture(tester.getCenter(find.text(textList[2]))); + await gesture.moveBy(const Offset(600, 0)); + await tester.pump(); + final StateMarkerState markerState = findStateMarkerState(textList[1]); + expect(markerState.marker, isNull); + markerState.marker = 'marked'; + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + value = textList[_pageController.page!.toInt()]; + expect(value, equals(textList[1])); + await tester.pumpWidget(app); + expect(findStateMarkerState(textList[1]).marker, equals('marked')); + }); + + testWidgets( + 'Basic GFIntroScreen can be constructed with GFIntroScreenBottomNavigationBar', + (tester) async { + final PageController _pageController = PageController( + initialPage: 0, + ); + final int initialPage = _pageController.initialPage; + + final GFIntroScreen introScreen = GFIntroScreen( + key: introScreenKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: slide.length, + slides: slide.map((data) => StateMarker(child: data)).toList(), + showIntroScreenBottomNavigationBar: true, + introScreenBottomNavigationBar: GFIntroScreenBottomNavigationBar( + key: bottomBarKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: 4, + ), + ); + + final TestApp app = TestApp(introScreen); + await tester.pumpWidget(app); + + // find intro screen by key + expect(find.byKey(bottomBarKey), findsOneWidget); + // find intro screen by key + expect(find.byKey(introScreenKey), findsOneWidget); + // find first screen text + expect(find.text('Page 1'), findsOneWidget); + expect(find.byKey(bottomBarKey), findsOneWidget); + expect(introScreen.showIntroScreenBottomNavigationBar, isTrue); + expect(_pageController, isNotNull); + expect(_pageController.initialPage, 0); + expect(_pageController.page!.toInt(), 0); + await tester.tap(find.text('NEXT')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 1); + expect(find.text('Page 2'), findsOneWidget); + await tester.tap(find.text('NEXT')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 2); + expect(find.text('Page 3'), findsOneWidget); + await tester.tap(find.text('BACK')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 1); + expect(find.text('Page 2'), findsOneWidget); + }); + + testWidgets( + 'Basic GFIntroScreenBottomNavigationBar can be constructed with no pagination and custom button text', + (tester) async { + final PageController _pageController = PageController( + initialPage: 0, + ); + final int initialPage = _pageController.initialPage; + + final GFIntroScreen introScreen = GFIntroScreen( + key: introScreenKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: slide.length, + slides: slide.map((data) => StateMarker(child: data)).toList(), + showIntroScreenBottomNavigationBar: true, + introScreenBottomNavigationBar: GFIntroScreenBottomNavigationBar( + key: bottomBarKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: 4, + showPagination: false, + forwardButtonText: 'go to next', + backButtonText: 'back to previous', + skipButtonText: 'skip to last', + doneButtonText: 'done, start from first', + onForwardButtonTap: () { + _pageController.nextPage( + duration: const Duration(milliseconds: 500), + curve: Curves.linear); + }, + onBackButtonTap: () { + _pageController.previousPage( + duration: const Duration(milliseconds: 500), + curve: Curves.linear); + }, + onSkipTap: () { + _pageController.jumpToPage(3); + }, + onDoneTap: () { + _pageController.jumpToPage(0); + }, + backButtonTextStyle: const TextStyle(fontSize: 12), + doneButtonTextStyle: const TextStyle(fontSize: 12), + forwardButtonTextStyle: const TextStyle(fontSize: 12), + skipButtonTextStyle: const TextStyle(fontSize: 12), + ), + ); + + final TestApp app = TestApp(introScreen); + await tester.pumpWidget(app); + + // find intro screen by key + expect(find.byKey(introScreenKey), findsOneWidget); + // find first screen text + expect(find.text('Page 1'), findsOneWidget); + expect(find.byKey(bottomBarKey), findsOneWidget); + expect(introScreen.showIntroScreenBottomNavigationBar, isTrue); + expect(_pageController, isNotNull); + // on first screen + expect(_pageController.initialPage, 0); + expect(_pageController.page!.toInt(), 0); + // tap go to next, to go to second screen + await tester.tap(find.text('go to next')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 1); + // find second screen + expect(find.text('Page 2'), findsOneWidget); + // tap back to previous to go back to first screen + await tester.tap(find.text('back to previous')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 0); + // find first screen + expect(find.text('Page 1'), findsOneWidget); + // tap skip to last, to jump to last screen + await tester.tap(find.text('skip to last')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 3); + // find fourth screen + expect(find.text('Page 4'), findsOneWidget); + // tap done, start from first to jump to first screen + await tester.tap(find.text('done, start from first')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 0); + // find first screen + expect(find.text('Page 1'), findsOneWidget); + }); + + testWidgets( + 'Basic GFIntroScreenBottomNavigationBar can be constructed with no pagination and custom button', + (tester) async { + final PageController _pageController = PageController( + initialPage: 0, + ); + final int initialPage = _pageController.initialPage; + + final GFIntroScreen introScreen = GFIntroScreen( + key: introScreenKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: slide.length, + slides: slide.map((data) => StateMarker(child: data)).toList(), + showIntroScreenBottomNavigationBar: true, + introScreenBottomNavigationBar: GFIntroScreenBottomNavigationBar( + key: bottomBarKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: 4, + showPagination: false, + forwardButton: GFButton( + onPressed: () { + _pageController.nextPage( + duration: const Duration(milliseconds: 500), + curve: Curves.linear); + }, + child: const Text('go to next'), + ), + backButton: GFButton( + onPressed: () { + _pageController.previousPage( + duration: const Duration(milliseconds: 500), + curve: Curves.linear); + }, + child: const Text('back to previous'), + ), + skipButton: GFButton( + onPressed: () { + _pageController.jumpToPage(3); + }, + child: const Text('skip to last'), + ), + doneButton: GFButton( + onPressed: () { + _pageController.jumpToPage(0); + }, + child: const Text('done, start from first'), + ), + ), + ); + + final TestApp app = TestApp(introScreen); + await tester.pumpWidget(app); + + // find intro screen by key + expect(find.byKey(introScreenKey), findsOneWidget); + // find first screen text + expect(find.text('Page 1'), findsOneWidget); + expect(find.byKey(bottomBarKey), findsOneWidget); + expect(introScreen.showIntroScreenBottomNavigationBar, isTrue); + expect(_pageController, isNotNull); + // on first screen + expect(_pageController.initialPage, 0); + expect(_pageController.page!.toInt(), 0); + // tap go to next, to go to second screen + await tester.tap(find.text('go to next')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 1); + // find second screen + expect(find.text('Page 2'), findsOneWidget); + // tap back to previous to go back to first screen + await tester.tap(find.text('back to previous')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 0); + // find first screen + expect(find.text('Page 1'), findsOneWidget); + // tap skip to last, to jump to last screen + await tester.tap(find.text('skip to last')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 3); + // find fourth screen + expect(find.text('Page 4'), findsOneWidget); + // tap done, start from first to jump to first screen + await tester.tap(find.text('done, start from first')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(_pageController.page!.toInt(), 0); + // find first screen + expect(find.text('Page 1'), findsOneWidget); + }); + + testWidgets( + 'Basic GFIntroScreenBottomNavigationBar can be constructed with custom pagination and no buttons', + (tester) async { + final PageController _pageController = PageController( + initialPage: 0, + ); + final int initialPage = _pageController.initialPage; + + final GFIntroScreen introScreen = GFIntroScreen( + key: introScreenKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: slide.length, + slides: slide.map((data) => StateMarker(child: data)).toList(), + showIntroScreenBottomNavigationBar: true, + introScreenBottomNavigationBar: GFIntroScreenBottomNavigationBar( + key: bottomBarKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: 4, + showButton: false, + dotHeight: 10, + dotWidth: 16, + dotShape: RoundedRectangleBorder( + side: const BorderSide(color: Colors.red, width: 2), + borderRadius: BorderRadius.circular(5)), + inactiveColor: Colors.black12, + activeColor: Colors.black87, + dotMargin: const EdgeInsets.symmetric(horizontal: 6), + ), + ); + + final TestApp app = TestApp(introScreen); + await tester.pumpWidget(app); + + // find intro screen by key + expect(find.byKey(introScreenKey), findsOneWidget); + // find first screen text + expect(find.text('Page 1'), findsOneWidget); + expect(find.byKey(bottomBarKey), findsOneWidget); + expect(introScreen.showIntroScreenBottomNavigationBar, isTrue); + expect(_pageController, isNotNull); + // on first screen + expect(_pageController.initialPage, 0); + expect(_pageController.page!.toInt(), 0); + expect(introScreen.currentIndex, _pageController.page!.toInt()); + }); + + testWidgets( + 'Basic GFIntroScreenBottomNavigationBar can be constructed with custom child', + (tester) async { + final PageController _pageController = PageController( + initialPage: 0, + ); + final int initialPage = _pageController.initialPage; + + final GFIntroScreen introScreen = GFIntroScreen( + key: introScreenKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: slide.length, + slides: slide.map((data) => StateMarker(child: data)).toList(), + showIntroScreenBottomNavigationBar: true, + introScreenBottomNavigationBar: GFIntroScreenBottomNavigationBar( + key: bottomBarKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: 4, + child: const Text('bottom bar'), + ), + ); + + final TestApp app = TestApp(introScreen); + await tester.pumpWidget(app); + + // find intro screen by key + expect(find.byKey(introScreenKey), findsOneWidget); + // find first screen text + expect(find.text('Page 1'), findsOneWidget); + expect(find.byKey(bottomBarKey), findsOneWidget); + // find first screen text + expect(find.text('bottom bar'), findsOneWidget); + }); + + testWidgets( + 'Customized GFIntroScreenBottomNavigationBar can be constructed with properties', + (tester) async { + final PageController _pageController = PageController( + initialPage: 0, + ); + final int initialPage = _pageController.initialPage; + + final GFIntroScreen introScreen = GFIntroScreen( + key: introScreenKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: slide.length, + slides: slide.map((data) => StateMarker(child: data)).toList(), + showIntroScreenBottomNavigationBar: true, + introScreenBottomNavigationBar: GFIntroScreenBottomNavigationBar( + key: bottomBarKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: 4, + navigationBarHeight: 60, + navigationBarWidth: 400, + navigationBarMargin: const EdgeInsets.all(6), + navigationBarPadding: const EdgeInsets.all(5), + navigationBarShape: RoundedRectangleBorder( + side: const BorderSide(color: Colors.teal, width: 1), + borderRadius: BorderRadius.circular(5), + ), + navigationBarColor: Colors.amber, + showDivider: true, + dividerHeight: 2, + dividerThickness: 4, + dividerColor: Colors.black, + dotHeight: 10, + dotWidth: 16, + dotShape: RoundedRectangleBorder( + side: const BorderSide(color: Colors.red, width: 2), + borderRadius: BorderRadius.circular(5)), + inactiveColor: Colors.black12, + activeColor: Colors.black87, + dotMargin: const EdgeInsets.symmetric(horizontal: 6), + ), + ); + + final TestApp app = TestApp(introScreen); + await tester.pumpWidget(app); + + // find intro screen by key + expect(find.byKey(bottomBarKey), findsOneWidget); + expect(app.introScreen.pageController, _pageController); + expect(app.introScreen.currentIndex, initialPage); + expect(app.introScreen.pageCount, 4); + }); + + testWidgets('Customized GFIntroScreen can be constructed with properties', + (tester) async { + final GFIntroScreen introScreen = GFIntroScreen( + key: introScreenKey, + pageController: _pageController, + currentIndex: initialPage, + pageCount: slideList.length, + slides: slideList, + height: 500, + width: 300, + color: Colors.blueGrey, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: Colors.blueGrey.shade700, width: 3), + ); + + final TestApp app = TestApp(introScreen); + await tester.pumpWidget(app); + + // find introScreen by key + expect(find.byKey(introScreenKey), findsOneWidget); + // find first screen text + expect(find.text('Page 1'), findsOneWidget); + + expect(app.introScreen.pageCount, 4); + expect(app.introScreen.currentIndex, initialPage); + expect(app.introScreen.height, 500); + expect(app.introScreen.width, 300); + expect(app.introScreen.color, Colors.blueGrey); + expect(app.introScreen.borderRadius, BorderRadius.circular(4)); + expect(app.introScreen.border, + Border.all(color: Colors.blueGrey.shade700, width: 3)); + expect(app.introScreen.showIntroScreenBottomNavigationBar, isFalse); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.introScreen); + + final GFIntroScreen introScreen; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: widget.introScreen, + ), + ); +} diff --git a/test/items_carousel_test.dart b/test/items_carousel_test.dart new file mode 100644 index 00000000..002d3b33 --- /dev/null +++ b/test/items_carousel_test.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +class StateMarker extends StatefulWidget { + const StateMarker({Key? key, this.child}) : super(key: key); + final Widget? child; + @override + StateMarkerState createState() => StateMarkerState(); +} + +class StateMarkerState extends State { + String? marker; + @override + Widget build(BuildContext context) { + if (widget.child != null) { + return widget.child!; + } + return Container(); + } +} + +void main() { + final Key carouselKey = UniqueKey(); + final List textList = [ + 'AAAAAA', + 'BBBBBB', + 'CCCCCC', + 'DDDDDD', + 'EEEEEE' + ]; + final List itemList = [ + Text(textList[0]), + Text(textList[1]), + Text(textList[2]), + Text(textList[3]), + Text(textList[4]) + ]; + + testWidgets('GFItemsCarousel can be constructed', (tester) async { + final GFItemsCarousel carousel = GFItemsCarousel( + key: carouselKey, + rowCount: 3, + children: itemList.map((text) => StateMarker(child: text)).toList(), + ); + + StateMarkerState findStateMarkerState(String name) => + tester.state(find.widgetWithText(StateMarker, name)); + + final TestApp app = TestApp(carousel); + await tester.pumpWidget(app); + + // find carousel by key + expect(find.byKey(carouselKey), findsOneWidget); + // find the 'AAAAAA', 'BBBBBB' and 'CCCCCC' text in carousel + expect(find.text('AAAAAA'), findsOneWidget); + expect(find.text('BBBBBB'), findsOneWidget); + expect(find.text('CCCCCC'), findsOneWidget); + + // slide to the second slide. + + TestGesture gesture = + await tester.startGesture(tester.getCenter(find.text('AAAAAA'))); + await gesture.moveBy(const Offset(-600, 0)); + await tester.pump(); + findStateMarkerState(textList[1]).marker = 'marked'; + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + // find the 'CCCCCC', 'DDDDDD' and 'EEEEEE' text in carousel + expect(find.text('DDDDDD'), findsOneWidget); + expect(find.text('EEEEEE'), findsOneWidget); + expect(find.text('CCCCCC'), findsOneWidget); + await tester.pumpWidget(app); + expect(findStateMarkerState(textList[1]).marker, equals('marked')); + + // slide back to the first slide. + gesture = + await tester.startGesture(tester.getCenter(find.text(textList[2]))); + await gesture.moveBy(const Offset(600, 0)); + await tester.pump(); + final StateMarkerState markerState = findStateMarkerState(textList[1]); + markerState.marker = 'marked'; + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + // find the 'AAAAAA', 'BBBBBB' and 'CCCCCC' text in carousel + expect(find.text('AAAAAA'), findsOneWidget); + expect(find.text('BBBBBB'), findsOneWidget); + expect(find.text('CCCCCC'), findsOneWidget); + await tester.pumpWidget(app); + expect(findStateMarkerState(textList[1]).marker, equals('marked')); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.carousel); + + final GFItemsCarousel carousel; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.carousel, + ], + ), + ), + ); +} diff --git a/test/list_tile_test.dart b/test/list_tile_test.dart new file mode 100644 index 00000000..cd4de8c8 --- /dev/null +++ b/test/list_tile_test.dart @@ -0,0 +1,190 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + final Key listTileKey = UniqueKey(); + + testWidgets('GFListTile can be constructed', (tester) async { + const title = 'title text'; + const subTitle = 'sub title text'; + const icon = Icon(Icons.favorite); + const avatar = GFAvatar( + backgroundColor: Colors.tealAccent, + ); + const description = Text('description'); + + final GFListTile listTile = GFListTile( + key: listTileKey, + titleText: title, + subTitleText: subTitle, + color: Colors.teal, + avatar: avatar, + icon: icon, + description: description, + ); + + final TestApp app = TestApp(listTile); + await tester.pumpWidget(app); + + // find listTile by key + expect(find.byKey(listTileKey), findsOneWidget); + // find title text + expect(find.text('title text'), findsOneWidget); + // find sub title text + expect(find.text('sub title text'), findsOneWidget); + // find description + expect(find.text('description'), findsOneWidget); + // find icon + expect(find.byIcon(Icons.favorite), findsOneWidget); + // find avatar + expect(find.byType(GFAvatar), findsOneWidget); + + expect(app.tile.avatar, avatar); + expect(app.tile.titleText, title); + expect(app.tile.subTitleText, subTitle); + expect(app.tile.description, description); + expect(app.tile.icon, icon); + expect(app.tile.color, Colors.teal); + }); + + testWidgets('GFListTile can be constructed with text', (tester) async { + var i = 5; + final titleText = Text('title $i'); + const subTitleText = Text('sub title'); + const icon = Icon(Icons.favorite); + const avatar = GFAvatar( + backgroundColor: Colors.tealAccent, + ); + const description = Text('description'); + + final GFListTile listTile = GFListTile( + key: listTileKey, + title: titleText, + subTitle: subTitleText, + color: Colors.teal, + avatar: avatar, + icon: icon, + description: description, + padding: const EdgeInsets.all(6), + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 6), + onTap: () { + i++; + print('tapped $i'); + }, + onLongPress: () { + i--; + print('long pressed $i'); + }, + ); + + final TestApp app = TestApp(listTile); + await tester.pumpWidget(app); + + // find listTile by key + expect(find.byKey(listTileKey), findsOneWidget); + // find title text + expect(find.text('title 5'), findsOneWidget); + // find sub title text + expect(find.text('sub title'), findsOneWidget); + // find description + expect(find.text('description'), findsOneWidget); + // find icon + expect(find.byIcon(Icons.favorite), findsOneWidget); + // find avatar + expect(find.byType(GFAvatar), findsOneWidget); + await tester.tap(find.text('title 5')); + await tester.pump(); + await tester.longPress(find.text('sub title')); + await tester.pump(); + + expect(app.tile.avatar, avatar); + expect(app.tile.title, titleText); + expect(app.tile.subTitle, subTitleText); + expect(app.tile.description, description); + expect(app.tile.icon, icon); + expect(app.tile.color, Colors.teal); + expect(app.tile.padding, const EdgeInsets.all(6)); + expect(app.tile.margin, + const EdgeInsets.symmetric(vertical: 8, horizontal: 6)); + }); + + testWidgets('GFListTile can be constructed with hover color and focus color', + (tester) async { + const titleText = Text('title'); + const subTitleText = Text('sub title'); + const icon = Icon(Icons.favorite); + const avatar = GFAvatar( + backgroundColor: Colors.tealAccent, + ); + const description = Text('description'); + final FocusNode focusNode = FocusNode(debugLabel: 'GFListTile'); + + final GFListTile listTile = GFListTile( + key: listTileKey, + title: titleText, + subTitle: subTitleText, + color: Colors.teal, + avatar: avatar, + icon: icon, + description: description, + enabled: true, + focusColor: Colors.amber, + hoverColor: Colors.blue, + focusNode: focusNode, + autofocus: true, + ); + + final TestApp app = TestApp(listTile); + await tester.pumpWidget(app); + + // find listTile by key + expect(find.byKey(listTileKey), findsOneWidget); + // find title text + expect(find.text('title'), findsOneWidget); + // find sub title text + expect(find.text('sub title'), findsOneWidget); + // find description + expect(find.text('description'), findsOneWidget); + // find icon + expect(find.byIcon(Icons.favorite), findsOneWidget); + // find avatar + expect(find.byType(GFAvatar), findsOneWidget); + + await tester.pumpAndSettle(); + + expect(app.tile.avatar, avatar); + expect(app.tile.title, titleText); + expect(app.tile.subTitle, subTitleText); + expect(app.tile.description, description); + expect(app.tile.icon, icon); + expect(app.tile.avatar, avatar); + expect(app.tile.color, Colors.teal); + expect(app.tile.focusColor, Colors.amber); + expect(app.tile.hoverColor, Colors.blue); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.tile); + + final GFListTile tile; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.tile, + ], + ), + ), + ); +} diff --git a/test/loader_test.dart b/test/loader_test.dart index 218aa74b..50c05e29 100644 --- a/test/loader_test.dart +++ b/test/loader_test.dart @@ -23,22 +23,6 @@ void main() { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - // testWidgets('Asserts.', (tester) async { - // expect( - // () => GFLoader( - // - // ), - // throwsAssertionError, - // ); - // - // expect( - // () => GFLoader( - // - // ), - // throwsAssertionError, - // ); - // }); - testWidgets('GF Loader can be constructed', (tester) async { final GFLoader loader = GFLoader( loaderColorOne: firstColor, @@ -46,7 +30,6 @@ void main() { duration: duration, type: GFLoaderType.ios, loaderIconOne: iconOne, - // androidLoaderColor : Colors.amber, loaderstrokeWidth: stroke, size: GFSize.MEDIUM, child: childWidget, @@ -102,19 +85,6 @@ void main() { expect(app.loader.loaderstrokeWidth, stroke); }); - // testWidgets('Asserts.', (tester) async { - // // when type is null - // - // expect(() => GFLoader( - // type: null, - // loaderIconOne : iconOne, - // loaderIconTwo: iconTwo, - // loaderIconThree: iconThree, - // ), - // throwsAssertionError, - // ); - // }); - testWidgets('GF Loader with square type can be constructed', (tester) async { const customType = GFLoaderType.square; @@ -181,40 +151,6 @@ void main() { expect(app.loader.androidLoaderColor, color); }); - // testWidgets('Asserts.', (tester) async { - // // when type is null - // - // // expect(() => GFLoader( - // // type: null, - // // loaderIconOne : iconOne, - // // loaderIconTwo: iconTwo, - // // loaderIconThree: iconThree, - // // ), - // // throwsAssertionError, - // // - // // ); - // - // const GFLoader loader = GFLoader( - // type: null, - // loaderIconOne : iconOne, - // loaderIconTwo: iconTwo, - // loaderIconThree: iconThree, - // ); - // - // const TestApp app = TestApp(loader); - // - // await tester.pumpWidget(app); - // - // expect( - // tester.takeException(), - // isA().having( - // (error) => error.message, - // 'message', - // 'Type should be custom for icons loader to display', - // ), - // ); - // }); - testWidgets('GF Loader with custom loader can be constructed using child', (tester) async { const customType = GFLoaderType.custom; @@ -232,6 +168,20 @@ void main() { expect(app.loader.child, childWidget); expect(app.loader.type, customType); }); + + testWidgets('Custom GF Loader can be constructed with wrong type', + (tester) async { + const GFLoader loader = GFLoader( + type: GFLoaderType.custom, + loaderIconOne: iconOne, + ); + + const TestApp app = TestApp(loader); + + await tester.pumpWidget(app); + expect(app.loader.type, GFLoaderType.custom, reason: 'custom icon'); + expect(app.loader.loaderIconOne, iconOne); + }); } class TestApp extends StatefulWidget { diff --git a/test/radio_list_tile_test.dart b/test/radio_list_tile_test.dart new file mode 100644 index 00000000..4d7b840a --- /dev/null +++ b/test/radio_list_tile_test.dart @@ -0,0 +1,271 @@ +import 'dart:ui'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +Widget wrap({Widget? child}) => MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: Material(child: child), + ), + ); + +void main() { + final UniqueKey radioKey = UniqueKey(); + const int groupValue = 0; + testWidgets('GFRadioListTile can be constructed Function call Null', + (tester) async { + final GFRadioListTile radioListTile = GFRadioListTile( + key: radioKey, + onChanged: null, + value: 2, + groupValue: groupValue, + toggleable: false, + ); + + final TestApp app = TestApp(radioListTile); + await tester.pumpWidget(app); + await tester.pump(); + expect(find.byKey(radioKey), findsOneWidget); + expect(app.radioListTile.onChanged, null); + expect( + app.radioListTile.value, + 2, + ); + }); + + testWidgets('GFRadioListTile can be constructed Function call Null', + (tester) async { + final GFRadioListTile radioListTile = GFRadioListTile( + key: radioKey, + onChanged: null, + value: 2, + groupValue: groupValue, + toggleable: false, + ); + + final TestApp app = TestApp(radioListTile); + await tester.pumpWidget(app); + await tester.pump(); + expect(find.byKey(radioKey), findsOneWidget); + expect(app.radioListTile.onChanged, null); + expect( + app.radioListTile.value, + 2, + ); + }); + + testWidgets('GFRadioListTile should initialize according to groupValue', + (WidgetTester tester) async { + final List values = [0, 1, 2]; + int groupValue = 0; + final Type radioListTileType = const GFRadioListTile( + value: 0, + groupValue: 0, + onChanged: null, + ).runtimeType; + + List> generatedRadioListTiles; + List> findTiles() => find + .byType(radioListTileType) + .evaluate() + .map((Element element) => element.widget) + .cast>() + .toList(); + + Widget buildFrame() => wrap( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Scaffold( + body: ListView.builder( + itemCount: values.length, + itemBuilder: (BuildContext context, int index) => + GFRadioListTile( + groupValue: groupValue, + onChanged: (value) { + setState(() { + groupValue = value; + }); + }, + value: values[index], + title: Text(values[index].toString()), + ), + ), + ), + ), + ); + + await tester.pumpWidget(buildFrame()); + generatedRadioListTiles = findTiles(); + + // first RadioButton is True then other option false + print(generatedRadioListTiles[0].checked); + print(generatedRadioListTiles[1].checked); + print(generatedRadioListTiles[2].checked); + print('*************'); + expect(generatedRadioListTiles[0].checked, equals(true)); + expect(generatedRadioListTiles[1].checked, equals(false)); + expect(generatedRadioListTiles[2].checked, equals(false)); + + // Second RadioButton is True then other option false + groupValue = 1; + await tester.pumpWidget(buildFrame()); + generatedRadioListTiles = findTiles(); + print(generatedRadioListTiles[0].checked); + print(generatedRadioListTiles[1].checked); + print(generatedRadioListTiles[2].checked); + print('*************'); + + expect(generatedRadioListTiles[0].checked, equals(false)); + expect(generatedRadioListTiles[1].checked, equals(true)); + expect(generatedRadioListTiles[2].checked, equals(false)); + + // third RadioButton is True then other option false + groupValue = 2; + await tester.pumpWidget(buildFrame()); + generatedRadioListTiles = findTiles(); + print(generatedRadioListTiles[0].checked); + print(generatedRadioListTiles[1].checked); + print(generatedRadioListTiles[2].checked); + print('*************'); + + expect(generatedRadioListTiles[0].checked, equals(false)); + expect(generatedRadioListTiles[1].checked, equals(false)); + expect(generatedRadioListTiles[2].checked, equals(true)); + }); + + testWidgets('GFRadioListTile simple control test', + (WidgetTester tester) async { + final Key key = UniqueKey(); + final Key titleKey = UniqueKey(); + final List log = []; + + await tester.pumpWidget( + wrap( + child: GFRadioListTile( + key: key, + value: 1, + groupValue: 2, + onChanged: log.add, + title: Text('Title', key: titleKey), + ), + ), + ); + + await tester.tap(find.byKey(key)); + + expect(log, equals([1])); + log.clear(); + + await tester.pumpWidget( + wrap( + child: GFRadioListTile( + key: key, + value: 1, + groupValue: 1, + onChanged: log.add, + // activeColor: Colors.green[500], + title: Text('Title', key: titleKey), + ), + ), + ); + + await tester.tap(find.byKey(key)); + + expect(log, isEmpty); + + await tester.pumpWidget( + wrap( + child: GFRadioListTile( + key: key, + value: 1, + groupValue: 2, + onChanged: null, + title: Text('Title', key: titleKey), + ), + ), + ); + + await tester.tap(find.byKey(key)); + + expect(log, isEmpty); + + await tester.pumpWidget( + wrap( + child: GFRadioListTile( + key: key, + value: 1, + groupValue: 2, + onChanged: log.add, + title: Text('Title', key: titleKey), + ), + ), + ); + + await tester.tap(find.byKey(titleKey)); + + expect(log, equals([1])); + }); + + testWidgets('GFRadioListTile control tests', (WidgetTester tester) async { + final List values = [0, 1, 2]; + int groupValue = 0; + + final List log = []; + + Widget buildFrame() => wrap( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Scaffold( + body: ListView.builder( + itemCount: values.length, + itemBuilder: (BuildContext context, int index) => + GFRadioListTile( + groupValue: groupValue, + onChanged: (value) { + log.add(value); + setState(() { + groupValue = value; + }); + }, + value: values[index], + title: Text(values[index].toString()), + ), + ), + ), + ), + ); + + // Tests for tapping between [Radio] and [ListTile] + await tester.pumpWidget(buildFrame()); + await tester.tap(find.text('0')); + log.add('-'); + print(log); + await tester.tap(find.text('1')); + await tester.tap(find.text('2')); + print(log); + // expect(log, equals(['-', 1, 2] + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.radioListTile); + + final GFRadioListTile radioListTile; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.radioListTile, + ], + ), + ), + ); +} diff --git a/test/radio_test.dart b/test/radio_test.dart new file mode 100644 index 00000000..02b3bd70 --- /dev/null +++ b/test/radio_test.dart @@ -0,0 +1,221 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + final UniqueKey radioKey = UniqueKey(); + const int groupValue = 0; + testWidgets('Radio button can be constructed Function call Null', + (tester) async { + final GFRadio radio = GFRadio( + key: radioKey, + onChanged: null, + value: 2, + groupValue: groupValue, + toggleable: false, + ); + + final TestApp app = TestApp(radio); + await tester.pumpWidget(app); + await tester.pump(); + expect(find.byKey(radioKey), findsOneWidget); + expect(app.radio.onChanged, null); + expect( + app.radio.value, + 2, + ); + }); + + testWidgets('Radio control test', (tester) async { + final List log = []; + const value = 2; + final radiobutton = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFRadio( + key: radioKey, + size: GFSize.SMALL, + value: value, + groupValue: groupValue, + onChanged: log.add, + // (value) { + // setState(() { + // log.add(value); + // }); + // }, + inactiveIcon: null, + activeBorderColor: GFColors.SUCCESS, + radioColor: GFColors.SUCCESS, + ), + )), + ); + await tester.pumpWidget(radiobutton); + expect(find.byKey(radioKey), findsOneWidget); + + await tester.tap(find.byKey(radioKey)); + expect(groupValue, 0); + print(log); + expect(log, equals([2])); + log.clear(); + + print(log); + expect(log, isEmpty); + + await tester.tap(find.byKey(radioKey)); + await tester.pumpAndSettle(); + print(log); + expect(log, equals([2])); + log.clear(); + }); + + testWidgets('Radio can be toggled when toggleable is set', (tester) async { + final List log = []; + const value = 1; + const groupValue = 2; + final radiobutton = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFRadio( + key: radioKey, + size: GFSize.SMALL, + value: value, + groupValue: groupValue, + onChanged: log.add, + // (value) { + // setState(() { + // log.add(value); + // }); + // }, + toggleable: true, + ), + )), + ); + + await tester.pumpWidget(radiobutton); + expect(find.byKey(radioKey), findsOneWidget); + expect(groupValue, 2); + await tester.tap(find.byKey(radioKey)); + await tester.tap(find.byKey(radioKey)); + print(log[0]); + print(log[1]); + expect(log, equals([1, 1])); + log.clear(); + + await tester.pumpAndSettle(); + log.insert(0, 5); + print(log[0]); + expect(log, equals([5])); + log.clear(); + + expect(log, isEmpty); + }); + + testWidgets('Radio size is configurable by themeData', + (WidgetTester tester) async { + final Key key1 = UniqueKey(); + await tester.pumpWidget( + Theme( + data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded), + child: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: GFRadio( + key: key1, + groupValue: true, + value: true, + onChanged: (bool newValue) {}, + size: 20, + ), + ), + ), + ), + ), + ); + + expect(tester.getSize(find.byKey(key1)), const Size(20, 20)); + + final Key key2 = UniqueKey(); + await tester.pumpWidget( + Theme( + data: + ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), + child: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: GFRadio( + key: key2, + groupValue: true, + value: true, + onChanged: (bool newValue) {}, + size: 40), + ), + ), + ), + ), + ); + + expect(tester.getSize(find.byKey(key2)), const Size(40, 40)); + }); + + testWidgets('Radio changes mouse cursor when hovered Default click', + (WidgetTester tester) async { + const Key key = ValueKey(1); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Align( + alignment: Alignment.topLeft, + child: Material( + child: MouseRegion( + cursor: SystemMouseCursors.forbidden, + child: GFRadio( + key: key, + value: 1, + onChanged: (int? v) {}, + groupValue: 2, + ), + ), + ), + ), + ), + ), + ); + + final TestGesture gesture = + await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1); + await gesture.addPointer(location: tester.getCenter(find.byKey(key))); + addTearDown(gesture.removePointer); + + await tester.pump(); + expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), + SystemMouseCursors.click); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.radio); + + final GFRadio radio; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.radio, + ], + ), + ), + ); +} diff --git a/test/rating_test.dart b/test/rating_test.dart new file mode 100644 index 00000000..b886b274 --- /dev/null +++ b/test/rating_test.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + final Key ratingKey = UniqueKey(); + final Key buttonKey = UniqueKey(); + + const filledIcon = Icon(Icons.favorite); + const defaultIcon = Icon(Icons.favorite_border); + + testWidgets('GFRating can be constructed', (tester) async { + double _rating = 4; + + final rating = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => GFRating( + key: ratingKey, + value: _rating, + onChanged: (value) { + print('value $value'); + setState(() { + _rating = value; + }); + }, + )), + ); + + await tester.pumpWidget(rating); + + // find rating by key + expect(find.byKey(ratingKey), findsOneWidget); + expect(find.byIcon(Icons.star), findsNWidgets(4)); + expect(find.byIcon(Icons.star_border), findsNWidgets(1)); + await tester.tap(find.byIcon(Icons.star_border)); + await tester.pump(); + expect(find.byIcon(Icons.star), findsNWidgets(5)); + expect(find.byIcon(Icons.star_border), findsNothing); + await tester.tap(find.byIcon(Icons.star).first); + await tester.pump(); + expect(find.byIcon(Icons.star), findsNWidgets(1)); + expect(find.byIcon(Icons.star_border), findsNWidgets(4)); + }); + + testWidgets('GFRating with icons.', (WidgetTester tester) async { + double _rating = 1; + + final rating = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => GFRating( + key: ratingKey, + value: _rating, + defaultIcon: defaultIcon, + filledIcon: filledIcon, + onChanged: (value) { + print('value $value'); + setState(() { + _rating = value; + }); + }, + )), + ); + + await tester.pumpWidget(rating); + + // find rating by key + expect(find.byKey(ratingKey), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsNWidgets(1)); + expect(find.byIcon(Icons.favorite_border), findsNWidgets(4)); + await tester.tap(find.byIcon(Icons.favorite_border).last); + await tester.pump(); + expect(find.byIcon(Icons.favorite), findsNWidgets(5)); + expect(find.byIcon(Icons.favorite_border), findsNothing); + await tester.tap(find.byIcon(Icons.favorite).first); + await tester.pump(); + expect(find.byIcon(Icons.favorite), findsNWidgets(1)); + expect(find.byIcon(Icons.favorite_border), findsNWidgets(4)); + }); + + testWidgets('GFRating with half rating property on dragging.', + (WidgetTester tester) async { + double _rating = 3; + + final rating = GFRating( + key: ratingKey, + value: _rating, + allowHalfRating: true, + onChanged: (value) { + _rating = value; + }, + ); + + final TestApp app = TestApp(rating); + await tester.pumpWidget(app); + + // find rating by key + expect(find.byKey(ratingKey), findsOneWidget); + expect(find.byIcon(Icons.star), findsNWidgets(3)); + expect(find.byIcon(Icons.star_border), findsNWidgets(2)); + // Swipe the item to rate 5 star + await tester.drag(find.byKey(ratingKey), const Offset(158.1, 45.1)); + await tester.pump(); + await tester.pump(const Duration(seconds: 2)); + // find rating 5 star + expect(_rating, 5); + }); + + testWidgets('GF Rating using textFormField data input.', + (WidgetTester tester) async { + final _ratingController = TextEditingController(); + double _rating = 3.5; + _ratingController.text = '3.5'; + + final rating = MaterialApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFRating( + key: ratingKey, + value: _rating, + controller: _ratingController, + showTextForm: true, + suffixIcon: GFButton( + key: buttonKey, + type: GFButtonType.transparent, + onPressed: () { + setState(() { + _rating = double.parse(_ratingController.text); + }); + }, + child: const Text('Rate'), + ), + onChanged: (value) {}, + ), + ), + ), + ), + ); + + await tester.pumpWidget(rating); + + // find rating by key + expect(find.byKey(ratingKey), findsOneWidget); + expect(find.byIcon(Icons.star), findsNWidgets(3)); + expect(find.byIcon(Icons.star_half), findsNWidgets(1)); + expect(find.byIcon(Icons.star_border), findsNWidgets(1)); + await tester.tap(find.byType(TextFormField)); + await tester.pump(); + await tester.enterText(find.byWidget(rating), '4.5'); + expect(find.text('4.5'), findsOneWidget); + await tester.tap(find.byType(GFButton)); + await tester.pumpAndSettle(const Duration(seconds: 5)); + expect(find.byIcon(Icons.star), findsNWidgets(4)); + expect(find.byIcon(Icons.star_half), findsNWidgets(1)); + expect(find.byIcon(Icons.star_border), findsNothing); + }); + + testWidgets('GFRating with all properties.', (WidgetTester tester) async { + double _rating = 3; + + final GFRating rating = GFRating( + key: ratingKey, + value: _rating, + itemCount: 6, + spacing: 6, + color: GFColors.ALT, + borderColor: GFColors.DARK, + size: GFSize.LARGE, + allowHalfRating: false, + showTextForm: false, + onChanged: (value) { + _rating = value; + }, + ); + + final TestApp app = TestApp(rating); + await tester.pumpWidget(app); + + expect(app.rating.value, _rating); + expect(app.rating.itemCount, 6); + expect(app.rating.spacing, 6); + expect(app.rating.color, GFColors.ALT); + expect(app.rating.borderColor, GFColors.DARK); + expect(app.rating.size, GFSize.LARGE); + expect(app.rating.allowHalfRating, isFalse); + expect(app.rating.showTextForm, isFalse); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.rating); + final GFRating rating; + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.rating, + ], + ), + ), + ); +} diff --git a/test/searchbar_test.dart b/test/searchbar_test.dart new file mode 100644 index 00000000..8136cc75 --- /dev/null +++ b/test/searchbar_test.dart @@ -0,0 +1,210 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + final Key searchBarKey = UniqueKey(); + const List searchList = [ + 'Flutter', + 'React', + 'Ionic', + 'Xamarin', + 'Flutter2', + 'Angular' + ]; + + const notFound = 'oopsy...no items found'; + + final noItemsFound = Container( + child: const Text(notFound), + ); + + final decoration = InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.teal, + ), + borderRadius: BorderRadius.circular(50)), + ); + + testWidgets('GF SearchBar can be constructed', (tester) async { + final GFSearchBar searchBar = GFSearchBar( + key: searchBarKey, + searchList: searchList, + overlaySearchListItemBuilder: (item) => Container( + padding: const EdgeInsets.all(8), + child: Text( + item, + style: const TextStyle(fontSize: 18), + ), + ), + searchQueryBuilder: (query, list) => list + .where((item) => item.toLowerCase().contains(query.toLowerCase())) + .toList(), + ); + + final TestApp app = TestApp(searchBar); + await tester.pumpWidget(app); + + // find searchBar by key + expect(find.byKey(searchBarKey), findsOneWidget); + // tap the textField to display overlay search list items + await tester.tap(find.byType(TextField)); + // rebuild the widget + await tester.pump(); + // find overlay search list item + expect(find.text('${searchList[1]}'), findsOneWidget); + // enter 'flutter' to the textField + await tester.enterText(find.byWidget(searchBar), 'flutter'); + // find the text 'flutter' in textField + expect(find.text('flutter'), findsOneWidget); + // find the text 'flutter' in overlay search list items + expect(find.widgetWithText(Container, 'flutter'), findsOneWidget); + // tap the close icon to close the overlay search list items + await tester.tap(find.byIcon(Icons.close)); + // find the text 'flutter' in overlay search list items + expect(find.widgetWithText(Container, 'flutter'), findsNothing); + }); + + testWidgets('Can hide searchBox when item selected', (tester) async { + final GFSearchBar searchBar = GFSearchBar( + key: searchBarKey, + searchList: searchList, + overlaySearchListItemBuilder: (item) => Container( + padding: const EdgeInsets.all(8), + child: Text( + item, + style: const TextStyle(fontSize: 18), + ), + ), + hideSearchBoxWhenItemSelected: true, + searchQueryBuilder: (query, list) => list + .where((item) => item.toLowerCase().contains(query.toLowerCase())) + .toList(), + overlaySearchListHeight: 115, + ); + + final TestApp app = TestApp(searchBar); + await tester.pumpWidget(app); + + // find searchBar by key + expect(find.byKey(searchBarKey), findsOneWidget); + // set searchBar.hideSearchBoxWhenItemSelected = true state to hide searchBar when item selected + expect(app.searchBar.hideSearchBoxWhenItemSelected, isTrue); + // tap the textField to display overlay search list items + await tester.tap(find.byType(TextField)); + // rebuild the widget + await tester.pump(); + // find overlay search list item + expect(find.text('${searchList[1]}'), findsOneWidget); + // tap to select item from overlay search list items + await tester.tap(find.text('${searchList[1]}')); + // rebuild the widget + await tester.pump(); + // find searchBar + expect(find.byType(TextField), findsNothing); + + expect(app.searchBar.hideSearchBoxWhenItemSelected, isTrue); + expect(app.searchBar.overlaySearchListHeight, 115); + }); + + testWidgets('On item selected and when item not found in GFSearchBar List', + (tester) async { + final GFSearchBar searchBar = GFSearchBar( + key: searchBarKey, + searchList: searchList, + overlaySearchListItemBuilder: (item) => Container( + padding: const EdgeInsets.all(8), + child: Text( + item, + style: const TextStyle(fontSize: 18), + ), + ), + searchQueryBuilder: (query, list) => list + .where((item) => item.toLowerCase().contains(query.toLowerCase())) + .toList(), + onItemSelected: (item) { + print('selected item $item'); + }, + noItemsFoundWidget: noItemsFound, + ); + + final TestApp app = TestApp(searchBar); + await tester.pumpWidget(app); + + // find searchBar by key + expect(find.byKey(searchBarKey), findsOneWidget); + // tap the textField to display overlay search list items + await tester.tap(find.byType(TextField)); + // rebuild the widget + await tester.pump(); + // find overlay search list item + expect(find.text('${searchList[1]}'), findsOneWidget); + // enter 'flutter' to the textField + await tester.enterText(find.byWidget(searchBar), 'flu'); + // find text 'flutter' in overlay search list item + expect(find.text('flu'), findsOneWidget); + // find text 'flutter' in overlay search list item + expect(find.text('${searchList[1]}'), findsOneWidget); + // tap to select 'flutter' item from overlay search list items + await tester.tap(find.text('${searchList[1]}')); + // rebuild the widget + await tester.pump(); + // // find overlay search list + // expect(find.text('${searchList[1]}'), findsNothing); + // // enter 'dart' to the textField + // await tester.enterText(find.byWidget(searchBar), 'dart'); + // // find text 'oopsy...no items found' in overlay search list item + // // expect(find.text(notFound), findsOneWidget); + + expect(app.searchBar.noItemsFoundWidget, noItemsFound); + }); + + testWidgets('GFSearchBar with search box input decoration', (tester) async { + final GFSearchBar searchBar = GFSearchBar( + key: searchBarKey, + searchList: searchList, + overlaySearchListItemBuilder: (item) => Container( + padding: const EdgeInsets.all(8), + child: Text( + item, + style: const TextStyle(fontSize: 18), + ), + ), + searchQueryBuilder: (query, list) => list + .where((item) => item.toLowerCase().contains(query.toLowerCase())) + .toList(), + onItemSelected: (item) { + print('selected item $item'); + }, + searchBoxInputDecoration: decoration, + ); + + final TestApp app = TestApp(searchBar); + await tester.pumpWidget(app); + + expect(app.searchBar.searchBoxInputDecoration, decoration); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.searchBar); + + final GFSearchBar searchBar; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.searchBar, + ], + ), + ), + ); +} diff --git a/test/segment_tabs_test.dart b/test/segment_tabs_test.dart new file mode 100644 index 00000000..62be8943 --- /dev/null +++ b/test/segment_tabs_test.dart @@ -0,0 +1,253 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + final Key segmentTabsKey = UniqueKey(); + + final List tabList = [ + const Text( + 'Tab1', + ), + const Tab( + icon: Icon(Icons.favorite), + ), + const Text( + 'Tab3', + ), + ]; + + final indicator = BoxDecoration( + color: Colors.teal, + border: Border.all(color: Colors.tealAccent, width: 2)); + + testWidgets('GFSegmentTabs can be constructed', (tester) async { + final TabController tabController = + TabController(length: 3, initialIndex: 0, vsync: tester); + + final GFSegmentTabs segmentTabs = GFSegmentTabs( + key: segmentTabsKey, + tabController: tabController, + length: tabList.length, + tabs: tabList, + ); + + final TestApp app = TestApp(segmentTabs); + await tester.pumpWidget(app); + + // find segmentTabs by key + expect(find.byKey(segmentTabsKey), findsOneWidget); + // find the 'Tab1' text in segmentTabs + expect(find.text('Tab1'), findsOneWidget); + + expect(tabController, isNotNull); + expect(tabController.index, 0); + expect(tabController.previousIndex, 0); + await tester.tap(find.byIcon(Icons.favorite)); + await tester.pump(); + expect(tabController.indexIsChanging, true); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 1); + expect(tabController.previousIndex, 0); + expect(tabController.indexIsChanging, false); + await tester.tap(find.byWidget(tabList[2])); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 2); + expect(tabController.previousIndex, 1); + await tester.tap(find.text('Tab1')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 0); + expect(tabController.previousIndex, 2); + }); + + testWidgets('GFSegmentTabs with on taps selected tab', (tester) async { + final TabController tabController = + TabController(length: 3, initialIndex: 0, vsync: tester); + + final GFSegmentTabs segmentTabs = GFSegmentTabs( + key: segmentTabsKey, + tabController: tabController, + length: tabList.length, + tabs: tabList, + ); + + final TestApp app = TestApp(segmentTabs); + await tester.pumpWidget(app); + + // find segmentTabs by key + expect(find.byKey(segmentTabsKey), findsOneWidget); + // find the 'Tab1' text in segmentTabs + expect(find.text('Tab1'), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + expect(find.byWidget(tabList[2]), findsOneWidget); + expect(tabController.index, 0); + expect(tabController.previousIndex, 0); + await tester.tap(find.byWidget(tabList[2])); + await tester.pumpAndSettle(); + expect(tabController.index, 2); + await tester.tap(find.byIcon(Icons.favorite)); + await tester.pumpAndSettle(); + expect(tabController.index, 1); + await tester.tap(find.text('Tab1')); + await tester.pumpAndSettle(); + expect(tabController.index, 0); + }); + + testWidgets('GFSegmentTabs with on taps center selected tab', (tester) async { + final TabController controller = + TabController(length: 12, initialIndex: 0, vsync: tester); + final List tabs = [ + const Text( + 'Tab_A', + ), + const Text( + 'Tab_B', + ), + const Text( + 'Tab_C', + ), + const Text( + 'Tab_D', + ), + const Text( + 'Tab_E', + ), + const Text( + 'Tab_F', + ), + const Text( + 'Tab_G', + ), + const Text( + 'Tab_H', + ), + const Text( + 'Tab_I', + ), + const Text( + 'Tab_J', + ), + const Text( + 'Tab_K', + ), + const Text( + 'Tab_L', + ), + ]; + + final GFSegmentTabs segmentTabs = GFSegmentTabs( + key: segmentTabsKey, + tabController: controller, + length: tabs.length, + tabs: tabs, + ); + + final TestApp app = TestApp(segmentTabs); + await tester.pumpWidget(app); + + // find segmentTabs by key + expect(find.byKey(segmentTabsKey), findsOneWidget); + // find the 'Tab_A' text in segmentTabs + expect(find.text('Tab_A'), findsOneWidget); + expect(controller, isNotNull); + expect(controller.index, 0); + expect(tester.getSize(find.byKey(segmentTabsKey)).width, equals(800.0)); + // The center of the 'Tab_F' item is to the right of the TabBar's center + expect(tester.getCenter(find.text('Tab_F')).dx, greaterThan(301.0)); + await tester.tap(find.text('Tab_F')); + await tester.pumpAndSettle(); + expect(controller.index, 5); + // The center of the 'Tab_F' item is now at the TabBar's center + expect(tester.getCenter(find.text('Tab_F')).dx, closeTo(366.0, 1.0)); + }); + + testWidgets('GFSegmentTabs can be constructed with properties', + (tester) async { + final TabController tabController = + TabController(length: 3, initialIndex: 0, vsync: tester); + + final GFSegmentTabs segmentTabs = GFSegmentTabs( + key: segmentTabsKey, + tabController: tabController, + length: tabList.length, + tabs: tabList, + tabBarColor: Colors.blueGrey, + height: 66, + width: 244, + border: Border.all(color: Colors.teal), + borderRadius: BorderRadius.circular(5), + indicator: indicator, + indicatorWeight: 5, + indicatorPadding: const EdgeInsets.all(8), + indicatorColor: GFColors.WHITE, + indicatorSize: TabBarIndicatorSize.tab, + labelPadding: const EdgeInsets.all(8), + labelColor: Colors.lightGreen, + unselectedLabelColor: Colors.black, + labelStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + ), + unselectedLabelStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 12, + ), + ); + + final TestApp app = TestApp(segmentTabs); + await tester.pumpWidget(app); + + expect(app.segmentTabs.tabController, tabController); + expect(app.segmentTabs.length, tabList.length); + expect(app.segmentTabs.tabs, tabList); + expect(app.segmentTabs.tabBarColor, Colors.blueGrey); + expect(app.segmentTabs.height, 66); + expect(app.segmentTabs.width, 244); + expect(app.segmentTabs.border, Border.all(color: Colors.teal)); + expect(app.segmentTabs.borderRadius, BorderRadius.circular(5)); + expect(app.segmentTabs.indicator, indicator); + expect(app.segmentTabs.indicatorWeight, 5); + expect(app.segmentTabs.indicatorPadding, const EdgeInsets.all(8)); + expect(app.segmentTabs.indicatorColor, GFColors.WHITE); + expect(app.segmentTabs.indicatorSize, TabBarIndicatorSize.tab); + expect(app.segmentTabs.labelPadding, const EdgeInsets.all(8)); + expect(app.segmentTabs.labelColor, Colors.lightGreen); + expect(app.segmentTabs.unselectedLabelColor, Colors.black); + expect( + app.segmentTabs.labelStyle, + const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + )); + expect( + app.segmentTabs.unselectedLabelStyle, + const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 12, + )); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.segmentTabs); + + final GFSegmentTabs segmentTabs; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Segment Tabs'), + ), + body: const Text('Body'), + bottomNavigationBar: widget.segmentTabs, + ), + ); +} diff --git a/test/shimmer_test.dart b/test/shimmer_test.dart new file mode 100644 index 00000000..ceae39c3 --- /dev/null +++ b/test/shimmer_test.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + final childWidget = Container( + width: 111, + height: 222, + ); + const firstColor = Colors.teal; + const secondColor = Colors.tealAccent; + + const count = 5; + const duration = Duration(milliseconds: 3000); + + final gradient = LinearGradient( + begin: Alignment.bottomRight, + end: Alignment.centerLeft, + stops: const [0, 0.3, 0.6, 0.9, 1], + colors: [ + Colors.teal[100]!, + Colors.teal[200]!, + Colors.teal[300]!, + Colors.teal[400]!, + Colors.teal[500]!, + ], + ); + + testWidgets('GF Shimmer can be constructed', (tester) async { + // final childWidget = Icon(Icons.directions_bike_sharp, size: 45,); + + final GFShimmer shimmer = GFShimmer( + child: childWidget, + mainColor: firstColor, + secondaryColor: secondColor, + shimmerEffectCount: count, + duration: duration, + ); + + final TestApp app = TestApp(shimmer); + + await tester.pumpWidget(app); + await tester.pump(duration); + + await tester.pumpWidget(Container(child: childWidget)); + expect(find.byWidget(childWidget), findsOneWidget); + }); + + testWidgets('GF Shimmer can be constructed with icon widget', (tester) async { + const GFShimmer shimmer = GFShimmer( + child: Icon( + Icons.directions_bike_sharp, + size: 45, + ), + mainColor: firstColor, + secondaryColor: secondColor, + shimmerEffectCount: count, + ); + + const TestApp app = TestApp(shimmer); + + await tester.pumpWidget(app); + + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + }); + + test('should emit the right values for shimmerEffectCount', () async { + final GFShimmer shimmer = GFShimmer( + child: childWidget, + mainColor: firstColor, + secondaryColor: secondColor, + shimmerEffectCount: count, + ); + expect(shimmer.shimmerEffectCount, count); + }); + + test('GF Shimmer with gradient', () async { + final GFShimmer shimmer = GFShimmer( + child: childWidget, + mainColor: firstColor, + secondaryColor: secondColor, + shimmerEffectCount: count, + showGradient: true, + gradient: gradient, + ); + expect(shimmer.showGradient, isTrue); + expect(shimmer.gradient, gradient); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.shimmer); + + final GFShimmer shimmer; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.shimmer, + ], + ), + ), + ); +} diff --git a/test/social_button_test.dart b/test/social_button_test.dart new file mode 100644 index 00000000..6832f087 --- /dev/null +++ b/test/social_button_test.dart @@ -0,0 +1,380 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + const text = 'click me'; + const textStyle = TextStyle(fontSize: 14, color: Colors.black); + const size = GFSize.SMALL; + const childWidget = Text('tap me'); + + testWidgets('Disabled GF Button can be constructed', (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: null, + text: text, + size: size, + disabledColor: Colors.teal.shade300, + disabledElevation: 3, + disabledTextColor: GFColors.LIGHT, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.onPressed, null); + expect(app.button.disabledColor, Colors.teal.shade300); + expect(app.button.disabledElevation, 3); + expect(app.button.disabledTextColor, GFColors.LIGHT); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets('GF Button can be constructed', (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + color: GFColors.ALT, + textStyle: textStyle, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.textStyle, textStyle); + expect(app.button.size, size); + expect(app.button.color, GFColors.ALT); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets('GF Button can be constructed with child for custom text', + (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + child: childWidget, + color: Colors.amber, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(app.button.child, childWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.color, Colors.amber); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + + await tester.pumpWidget(app); + }); + + testWidgets( + 'GF Button can be constructed with standard type and shape i.e default', + (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + textColor: GFColors.DARK, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.textColor, GFColors.DARK); + expect(app.button.size, size); + + await tester.tap(find.byKey(buttonKey)); + await tester.pump(); + await tester.press(find.byKey(buttonKey)); + await tester.pump(); + await tester.longPress(find.byKey(buttonKey)); + await tester.pump(); + + // await expectLater(() => tester.tap(find.byKey(buttonKey)), prints('onHighlight changed')); + + await tester.pumpWidget(app); + }); + + testWidgets( + 'GF Button with icon and icon position' + 'and with Block Button', (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + child: childWidget, + icon: const Icon( + Icons.directions_bike_sharp, + ), + position: GFPosition.start, + blockButton: true, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.position, GFPosition.start); + expect(app.button.blockButton, isTrue); + }); + + testWidgets( + 'GF Button with solid type and pills shape ' + 'and Full Width Button', (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + child: childWidget, + icon: const Icon( + Icons.directions_bike_sharp, + ), + position: GFPosition.start, + type: GFButtonType.solid, + shape: GFButtonShape.pills, + fullWidthButton: true, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.position, GFPosition.start); + expect(app.button.type, GFButtonType.solid); + expect(app.button.shape, GFButtonShape.pills); + expect(app.button.fullWidthButton, isTrue); + }); + + testWidgets('GF Button with type outline and square shape ', (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + child: childWidget, + icon: const Icon( + Icons.directions_bike_sharp, + ), + position: GFPosition.start, + type: GFButtonType.outline, + shape: GFButtonShape.square, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + expect(find.byIcon(Icons.directions_bike_sharp), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.position, GFPosition.start); + expect(app.button.type, GFButtonType.outline); + expect(app.button.shape, GFButtonShape.square); + }); + + testWidgets('GF Button with custom border and shape ', (tester) async { + const buttonKey = Key('header'); + + const border = BorderSide( + color: Colors.tealAccent, width: 1, style: BorderStyle.solid); + const shape = RoundedRectangleBorder( + side: + BorderSide(color: Colors.pink, width: 2, style: BorderStyle.solid), + borderRadius: BorderRadius.zero); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + borderShape: shape, + borderSide: border, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.borderShape, shape); + expect(app.button.borderSide, border); + }); + + testWidgets('GF Button with boxshadow ', (tester) async { + const buttonKey = Key('header'); + + const boxshadow = BoxShadow( + color: Colors.pink, + blurRadius: 2, + spreadRadius: 1, + offset: Offset.zero, + ); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + buttonBoxShadow: true, + boxShadow: boxshadow, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.buttonBoxShadow, isTrue); + expect(app.button.boxShadow, boxshadow); + }); + + testWidgets( + 'GF Button with hover, focus, highlight and splash color. Works only in web', + (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + hoverColor: Colors.tealAccent, + focusColor: Colors.teal, + highlightColor: Colors.amber, + splashColor: Colors.red, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.hoverColor, Colors.tealAccent); + expect(app.button.focusColor, Colors.teal); + expect(app.button.highlightColor, Colors.amber); + expect(app.button.splashColor, Colors.red); + }); + + testWidgets( + 'GF Button with hover, focus, highlight and splash color. Works only in web', + (tester) async { + const buttonKey = Key('header'); + + final GFSocialButton button = GFSocialButton( + key: buttonKey, + onPressed: () {}, + text: text, + size: size, + elevation: 3, + focusElevation: 2, + hoverElevation: 2, + highlightElevation: 4, + ); + + final TestApp app = TestApp(button); + + await tester.pumpWidget(app); + + expect(find.text('click me'), findsOneWidget); + + expect(app.button.text, text); + expect(app.button.size, size); + expect(app.button.elevation, 3); + expect(app.button.focusElevation, 2); + expect(app.button.hoverElevation, 2); + expect(app.button.highlightElevation, 4); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.button); + + final GFSocialButton button; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.button, + ], + ), + ), + ); +} diff --git a/test/sticky_header_test.dart b/test/sticky_header_test.dart new file mode 100644 index 00000000..d4924bae --- /dev/null +++ b/test/sticky_header_test.dart @@ -0,0 +1,301 @@ +import 'dart:core'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + testWidgets('GFStickyHeader can be constructed', (tester) async { + const itemCount = 12; + + final stickyHeader = Directionality( + textDirection: TextDirection.ltr, + child: ListView.builder( + itemCount: itemCount, + itemBuilder: (BuildContext context, int index) => GFStickyHeader( + stickyContent: Container( + alignment: AlignmentDirectional.center, + height: 50, + color: Colors.teal.shade200, + child: Text( + 'Header tile $index', + ), + ), + content: Container( + color: Colors.blueGrey.shade200, + height: 111, + child: Text('List tile $index'))), + ), + ); + + final TestApp app = TestApp(stickyHeader); + await tester.pumpWidget(app); + + // displays first 3 index + + expect(find.text('Header tile 1'), findsOneWidget); + expect(find.text('List tile 1'), findsOneWidget); + + expect(find.text('Header tile 2'), findsOneWidget); + expect(find.text('List tile 2'), findsOneWidget); + + expect(find.text('Header tile 3'), findsOneWidget); + expect(find.text('List tile 3'), findsOneWidget); + + expect(find.text('Header tile 4'), findsNothing); + expect(find.text('List tile 4'), findsNothing); + + // scroll ups first index and display index 4 + + TestGesture gesture = + await tester.startGesture(tester.getCenter(find.text('List tile 1'))); + await gesture.moveBy(const Offset(600, -330)); + await tester.pump(); + + expect(find.text('Header tile 1'), findsNothing); + expect(find.text('List tile 1'), findsNothing); + + expect(find.text('Header tile 4'), findsOneWidget); + expect(find.text('List tile 4'), findsOneWidget); + + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + await tester.pumpWidget(app); + + expect(find.text('Header tile 5'), findsOneWidget); + expect(find.text('List tile 5'), findsOneWidget); + + // scroll ups third index and display index 6 + + gesture = + await tester.startGesture(tester.getCenter(find.text('List tile 5'))); + await gesture.moveBy(const Offset(600, -330)); + await tester.pump(); + + expect(find.text('Header tile 3'), findsNothing); + expect(find.text('List tile 3'), findsNothing); + + expect(find.text('Header tile 6'), findsOneWidget); + expect(find.text('List tile 6'), findsOneWidget); + + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + await tester.pumpWidget(app); + + expect(find.text('Header tile 7'), findsOneWidget); + expect(find.text('List tile 7'), findsOneWidget); + + expect(find.text('Header tile 8'), findsNothing); + expect(find.text('List tile 8'), findsNothing); + }); + + testWidgets('GFStickyHeader horizontal can be constructed', (tester) async { + const itemCount = 12; + + final stickyHeader = Directionality( + textDirection: TextDirection.ltr, + child: ListView.builder( + itemCount: itemCount, + itemBuilder: (BuildContext context, int index) => GFStickyHeader( + enableHeaderOverlap: true, + direction: Axis.horizontal, + // stickyContentPosition: GFPosition.end, + stickyContent: Container( + alignment: AlignmentDirectional.center, + height: 50, + color: Colors.teal.shade200, + child: Text( + 'Header tile $index', + ), + ), + content: Container( + alignment: AlignmentDirectional.center, + height: 111, + color: Colors.blueGrey.shade200, + child: Text('List tile $index'))), + ), + ); + + final TestApp app = TestApp(stickyHeader); + await tester.pumpWidget(app); + + // displays first 6 index + + expect(find.text('Header tile 1'), findsOneWidget); + expect(find.text('List tile 1'), findsOneWidget); + + expect(find.text('Header tile 2'), findsOneWidget); + expect(find.text('List tile 2'), findsOneWidget); + + expect(find.text('Header tile 3'), findsOneWidget); + expect(find.text('List tile 3'), findsOneWidget); + + expect(find.text('Header tile 6'), findsNothing); + expect(find.text('List tile 6'), findsNothing); + + // scroll ups first index and display index 4 + + TestGesture gesture = + await tester.startGesture(tester.getCenter(find.text('List tile 3'))); + await gesture.moveBy(const Offset(600, -330)); + await tester.pump(); + + expect(find.text('Header tile 1'), findsNothing); + expect(find.text('List tile 1'), findsNothing); + + expect(find.text('Header tile 6'), findsOneWidget); + expect(find.text('List tile 6'), findsOneWidget); + + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + await tester.pumpWidget(app); + + // scroll ups seventh index and display index 11 + + gesture = + await tester.startGesture(tester.getCenter(find.text('List tile 7'))); + await gesture.moveBy(const Offset(600, -330)); + await tester.pump(); + + expect(find.text('Header tile 3'), findsNothing); + expect(find.text('List tile 3'), findsNothing); + + expect(find.text('Header tile 10'), findsOneWidget); + expect(find.text('List tile 10'), findsOneWidget); + + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + await tester.pumpWidget(app); + + expect(find.text('Header tile 11'), findsOneWidget); + expect(find.text('List tile 11'), findsOneWidget); + }); + + testWidgets('GFStickyHeader can be constructed with properties', + (tester) async { + final GFStickyHeader stickyHeader = GFStickyHeader( + enableHeaderOverlap: true, + direction: Axis.horizontal, + stickyContentPosition: GFPosition.end, + stickyContent: Container( + alignment: AlignmentDirectional.center, + height: 50, + color: Colors.teal.shade200, + child: const Text( + 'Header tile', + ), + ), + content: Container( + alignment: AlignmentDirectional.center, + height: 111, + color: Colors.blueGrey.shade200, + child: const Text('List tile'))); + + final Tester app = Tester(stickyHeader); + await tester.pumpWidget(app); + + expect(find.text('Header tile'), findsOneWidget); + expect(find.text('List tile'), findsOneWidget); + + expect(stickyHeader.enableHeaderOverlap, isTrue); + expect(stickyHeader.direction, Axis.horizontal); + expect(stickyHeader.stickyContentPosition, GFPosition.end); + }); + + testWidgets('GFStickyHeaderBuilder can be constructed with properties', + (tester) async { + final stickyHeader = Directionality( + textDirection: TextDirection.ltr, + child: ListView.builder( + shrinkWrap: true, + physics: const ScrollPhysics(), + itemCount: 12, + itemBuilder: (context, index) => GFStickyHeaderBuilder( + // direction: Axis.horizontal, + // enableHeaderOverlap: true, + // stickyContentPosition: GFPosition.end, + stickyContentBuilder: (BuildContext context, double stuckValue) { + stuckValue = 1.0 - stuckValue.clamp(0.0, 1.0); + return Container( + height: 50, + width: MediaQuery.of(context).size.width * 0.5, + color: Color.lerp(Colors.teal[100], Colors.teal[600], stuckValue), + padding: const EdgeInsets.symmetric(horizontal: 16), + alignment: Alignment.centerLeft, + child: Row( + children: [ + Expanded( + child: Text( + 'Image #$index', + style: const TextStyle(color: Colors.white), + ), + ), + Offstage( + offstage: stuckValue <= 0.0, + child: Opacity( + opacity: stuckValue, + child: IconButton( + icon: const Icon(Icons.image, color: Colors.white), + // ignore: deprecated_member_use + onPressed: () => Scaffold.of(context).showSnackBar( + SnackBar(content: Text('Favorite #$index'))), + ), + ), + ), + ], + ), + ); + }, + content: Container( + height: 200, + width: MediaQuery.of(context).size.width * 0.5, + color: Colors.teal, + child: const Text('Content tile'), + ), + ), + ), + ); + + final TestApp app = TestApp(stickyHeader); + await tester.pumpWidget(app); + }); +} + +class Tester extends StatefulWidget { + const Tester(this.stickyHeader); + + final Widget stickyHeader; + + @override + _TesterState createState() => _TesterState(); +} + +class _TesterState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold(body: SingleChildScrollView(child: widget.stickyHeader)), + ); +} + +class TestApp extends StatefulWidget { + const TestApp(this.stickyHeader); + + final Widget stickyHeader; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold(body: widget.stickyHeader), + ); +} diff --git a/test/tabbar_test.dart b/test/tabbar_test.dart new file mode 100644 index 00000000..9595f775 --- /dev/null +++ b/test/tabbar_test.dart @@ -0,0 +1,333 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + final Key tabBarKey = UniqueKey(); + + final List tabList = [ + const Text( + 'Tab1', + ), + const Tab( + icon: Icon(Icons.favorite), + ), + const Tab( + icon: Icon(Icons.directions_railway), + child: Text( + 'Tab3', + ), + ), + ]; + + final indicator = BoxDecoration( + color: Colors.teal, + border: Border.all(color: Colors.tealAccent, width: 2)); + const tabBarShape = RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24), topRight: Radius.circular(24))); + + testWidgets('GFTabBar can be constructed', (tester) async { + final TabController tabController = + TabController(length: 3, initialIndex: 0, vsync: tester); + + final GFTabBar tabBar = GFTabBar( + key: tabBarKey, + controller: tabController, + length: tabList.length, + tabs: tabList, + ); + + final TestApp app = TestApp(tabBar); + await tester.pumpWidget(app); + + // find tabBar by key + expect(find.byKey(tabBarKey), findsOneWidget); + // find the 'Tab1' text in tabBar + expect(find.text('Tab1'), findsOneWidget); + + expect(tabController, isNotNull); + expect(tabController.index, 0); + expect(tabController.previousIndex, 0); + await tester.tap(find.byIcon(Icons.favorite)); + await tester.pump(); + expect(tabController.indexIsChanging, true); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 1); + expect(tabController.previousIndex, 0); + expect(tabController.indexIsChanging, false); + await tester.tap(find.byWidget(tabList[2])); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 2); + expect(tabController.previousIndex, 1); + await tester.tap(find.text('Tab1')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 0); + expect(tabController.previousIndex, 2); + }); + + testWidgets('GFTabBar with scrollable taps selected tab', (tester) async { + final TabController tabController = + TabController(length: 3, initialIndex: 0, vsync: tester); + + final GFTabBar tabBar = GFTabBar( + key: tabBarKey, + controller: tabController, + length: tabList.length, + tabs: tabList, + isScrollable: true, + ); + + final TestApp app = TestApp(tabBar); + await tester.pumpWidget(app); + + // find tabBar by key + expect(find.byKey(tabBarKey), findsOneWidget); + // find the 'Tab1' text in tabBar + expect(app.tabBar.isScrollable, isTrue); + expect(find.text('Tab1'), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + expect(find.byWidget(tabList[2]), findsOneWidget); + expect(tabController.index, 0); + expect(tabController.previousIndex, 0); + await tester.tap(find.byWidget(tabList[2])); + await tester.pumpAndSettle(); + expect(tabController.index, 2); + await tester.tap(find.byIcon(Icons.favorite)); + await tester.pumpAndSettle(); + expect(tabController.index, 1); + await tester.tap(find.text('Tab1')); + await tester.pumpAndSettle(); + expect(tabController.index, 0); + }); + + testWidgets('GFTabBar with scrollable taps center selected tab', + (tester) async { + final TabController controller = + TabController(length: 12, initialIndex: 0, vsync: tester); + final List tabs = [ + const Text( + 'Tab_A', + ), + const Text( + 'Tab_B', + ), + const Text( + 'Tab_C', + ), + const Text( + 'Tab_D', + ), + const Text( + 'Tab_E', + ), + const Text( + 'Tab_F', + ), + const Text( + 'Tab_G', + ), + const Text( + 'Tab_H', + ), + const Text( + 'Tab_I', + ), + const Text( + 'Tab_J', + ), + const Text( + 'Tab_K', + ), + const Text( + 'Tab_L', + ), + ]; + + final GFTabBar tabBar = GFTabBar( + key: tabBarKey, + controller: controller, + length: tabs.length, + tabs: tabs, + isScrollable: true, + ); + + final TestApp app = TestApp(tabBar); + await tester.pumpWidget(app); + + // find tabBar by key + expect(find.byKey(tabBarKey), findsOneWidget); + // find the 'Tab_A' text in tabBar + expect(app.tabBar.isScrollable, isTrue); + expect(find.text('Tab_A'), findsOneWidget); + expect(controller, isNotNull); + expect(controller.index, 0); + expect(tester.getSize(find.byKey(tabBarKey)).width, equals(800.0)); + // The center of the 'Tab_F' item is to the right of the TabBar's center + expect(tester.getCenter(find.text('Tab_F')).dx, greaterThan(401.0)); + await tester.tap(find.text('Tab_F')); + await tester.pumpAndSettle(); + expect(controller.index, 5); + // The center of the 'Tab_F' item is now at the TabBar's center + expect(tester.getCenter(find.text('Tab_F')).dx, closeTo(400.0, 1.0)); + }); + + testWidgets('GFTabBar can be scrolled independent of the selection', + (tester) async { + final TabController controller = + TabController(length: 12, initialIndex: 0, vsync: tester); + final List tabs = [ + const Text( + 'TabA', + ), + const Text( + 'TabB', + ), + const Text( + 'TabC', + ), + const Text( + 'TabD', + ), + const Text( + 'TabE', + ), + const Text( + 'TabF', + ), + const Text( + 'TabG', + ), + const Text( + 'TabH', + ), + const Text( + 'TabI', + ), + const Text( + 'TabJ', + ), + const Text( + 'TabK', + ), + const Text( + 'TabL', + ), + ]; + + final GFTabBar tabBar = GFTabBar( + key: tabBarKey, + controller: controller, + length: tabs.length, + tabs: tabs, + isScrollable: true, + ); + + final TestApp app = TestApp(tabBar); + await tester.pumpWidget(app); + + // find tabBar by key + expect(find.byKey(tabBarKey), findsOneWidget); + // find the 'Tab_A' text in tabBar + expect(app.tabBar.isScrollable, isTrue); + expect(find.text('TabA'), findsOneWidget); + expect(controller, isNotNull); + expect(controller.index, 0); + // Fling-scroll the TabBar to the left + expect(tester.getCenter(find.text('TabH')).dx, lessThan(700.0)); + await tester.fling(find.byKey(tabBarKey), const Offset(-200, 0), 10000); + await tester.pump(); + await tester + .pump(const Duration(seconds: 1)); // finish the scroll animation + expect(tester.getCenter(find.text('TabH')).dx, lessThan(500.0)); + // Scrolling the TabBar doesn't change the selection + expect(controller.index, 0); + }); + + testWidgets('GFTabBar can be constructed with properties', (tester) async { + final TabController tabController = + TabController(length: 3, initialIndex: 0, vsync: tester); + + final GFTabBar tabBar = GFTabBar( + key: tabBarKey, + controller: tabController, + length: tabList.length, + tabs: tabList, + tabBarColor: Colors.blueGrey, + tabBarHeight: 66, + isScrollable: false, + shape: tabBarShape, + indicator: indicator, + indicatorWeight: 5, + indicatorPadding: const EdgeInsets.all(8), + indicatorColor: GFColors.WHITE, + indicatorSize: TabBarIndicatorSize.tab, + labelPadding: const EdgeInsets.all(8), + labelColor: Colors.lightGreen, + unselectedLabelColor: Colors.black, + labelStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + ), + unselectedLabelStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 12, + ), + ); + + final TestApp app = TestApp(tabBar); + await tester.pumpWidget(app); + + expect(app.tabBar.controller, tabController); + expect(app.tabBar.length, tabList.length); + expect(app.tabBar.tabs, tabList); + expect(app.tabBar.tabBarColor, Colors.blueGrey); + expect(app.tabBar.tabBarHeight, 66); + expect(app.tabBar.isScrollable, isFalse); + expect(app.tabBar.shape, tabBarShape); + expect(app.tabBar.indicator, indicator); + expect(app.tabBar.indicatorWeight, 5); + expect(app.tabBar.indicatorPadding, const EdgeInsets.all(8)); + expect(app.tabBar.indicatorColor, GFColors.WHITE); + expect(app.tabBar.indicatorSize, TabBarIndicatorSize.tab); + expect(app.tabBar.labelPadding, const EdgeInsets.all(8)); + expect(app.tabBar.labelColor, Colors.lightGreen); + expect(app.tabBar.unselectedLabelColor, Colors.black); + expect( + app.tabBar.labelStyle, + const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + )); + expect( + app.tabBar.unselectedLabelStyle, + const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 12, + )); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.tabBar); + + final GFTabBar tabBar; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('TabBar'), + ), + body: const Text('Body'), + bottomNavigationBar: widget.tabBar, + ), + ); +} diff --git a/test/tabbarview_test.dart b/test/tabbarview_test.dart new file mode 100644 index 00000000..cbfeb5a9 --- /dev/null +++ b/test/tabbarview_test.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +void main() { + final Key tabBarViewKey = UniqueKey(); + final List tabs = [ + 'AAAAAA', + 'BBBBBB', + 'CCCCCC', + 'DDDDDD', + 'EEEEEE' + ]; + final List tabList = [ + Text(tabs[0]), + Text(tabs[1]), + Text(tabs[2]), + Text(tabs[3]), + Text(tabs[4]) + ]; + + testWidgets('GFTabBarView can be constructed', (tester) async { + final TabController tabController = + TabController(length: 3, initialIndex: 0, vsync: tester); + + final GFTabBarView tabBarView = + GFTabBarView(controller: tabController, children: [ + Container( + child: const Text('aaa'), + ), + Container( + child: const Text('bbb'), + ), + Container( + child: const Text('ccc'), + ), + ]); + + final TestApp app = TestApp(tabBarView); + await tester.pumpWidget(app); + + expect(find.text('aaa'), findsOneWidget); + expect(find.text('bbb'), findsNothing); + + Offset point = tester.getCenter(find.text('aaa')); + await tester.dragFrom(point, const Offset(-600, 0)); + await tester.pump(); + expect(find.text('bbb'), findsOneWidget); + + point = tester.getCenter(find.text('bbb')); + await tester.dragFrom(point, const Offset(-600, 0)); + await tester.pump(); + expect(find.text('ccc'), findsOneWidget); + + point = tester.getCenter(find.text('ccc')); + await tester.dragFrom(point, const Offset(600, 0)); + await tester.pump(); + expect(find.text('bbb'), findsOneWidget); + }); + + testWidgets('GFTabBarView can be constructed with properties', + (tester) async { + final TabController tabController = + TabController(length: tabList.length, initialIndex: 0, vsync: tester); + + final GFTabBarView tabBarView = GFTabBarView( + key: tabBarViewKey, + controller: tabController, + children: tabList, + height: 345, + ); + + final TestApp app = TestApp(tabBarView); + await tester.pumpWidget(app); + + expect(app.tabBarView.controller, tabController); + expect(app.tabBarView.children, tabList); + expect(app.tabBarView.height, 345); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.tabBarView); + + final GFTabBarView tabBarView; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('TabBarView'), + ), + body: widget.tabBarView, + ), + ); +} diff --git a/test/tabs_test.dart b/test/tabs_test.dart new file mode 100644 index 00000000..0daa0f1c --- /dev/null +++ b/test/tabs_test.dart @@ -0,0 +1,560 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; + +class StateMarker extends StatefulWidget { + const StateMarker({Key? key, this.child}) : super(key: key); + final Widget? child; + @override + StateMarkerState createState() => StateMarkerState(); +} + +class StateMarkerState extends State { + String? marker; + @override + Widget build(BuildContext context) { + if (widget.child != null) { + return widget.child!; + } + return Container(); + } +} + +void main() { + final Key tabsKey = UniqueKey(); + final Key tabBarViewKey = UniqueKey(); + + final indicator = BoxDecoration( + color: Colors.teal, + border: Border.all(color: Colors.tealAccent, width: 2)); + const tabsShape = RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24), topRight: Radius.circular(24))); + + testWidgets('GFTabs can be constructed', (tester) async { + final List tabList = [ + const Text( + 'Tab1', + ), + const Tab( + icon: Icon(Icons.favorite), + ), + const Tab( + icon: Icon(Icons.directions_railway), + child: Text( + 'Tab3', + ), + ), + ]; + final List bodyText = ['AAAAAA', 'BBBBBB', 'CCCCCC']; + final List tabViewList = [ + Text(bodyText[0]), + Text(bodyText[1]), + Text(bodyText[2]) + ]; + final TabController tabController = + TabController(length: tabList.length, vsync: tester); + + final GFTabs tabs = GFTabs( + key: tabsKey, + initialIndex: 0, + controller: tabController, + length: tabList.length, + tabs: tabList, + tabBarView: GFTabBarView( + controller: tabController, + children: + tabViewList.map((text) => StateMarker(child: text)).toList()), + ); + + final TestApp app = TestApp(tabs); + await tester.pumpWidget(app); + + // find tabs by key + expect(find.byKey(tabsKey), findsOneWidget); + // find the 'Tab1' text in tabs + expect(find.text('Tab1'), findsOneWidget); + + expect(tabController, isNotNull); + expect(tabController.index, 0); + expect(tabController.previousIndex, 0); + expect(bodyText[tabController.index], 'AAAAAA'); + await tester.tap(find.byIcon(Icons.favorite)); + await tester.pump(); + expect(tabController.indexIsChanging, true); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 1); + expect(bodyText[tabController.index], 'BBBBBB'); + expect(tabController.previousIndex, 0); + expect(tabController.indexIsChanging, false); + await tester.tap(find.byWidget(tabList[2])); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 2); + expect(bodyText[tabController.index], 'CCCCCC'); + expect(tabController.previousIndex, 1); + await tester.tap(find.text('Tab1')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + expect(tabController.index, 0); + expect(tabController.previousIndex, 2); + }); + + testWidgets('GFTabs with scrollable taps selected tab', (tester) async { + final List tabList = [ + const Text( + 'Tab1', + ), + const Tab( + icon: Icon(Icons.favorite), + ), + const Tab( + icon: Icon(Icons.directions_railway), + child: Text( + 'Tab3', + ), + ), + ]; + final List bodyText = ['AAAAAA', 'BBBBBB', 'CCCCCC']; + final List tabViewList = [ + Text(bodyText[0]), + Text(bodyText[1]), + Text(bodyText[2]) + ]; + final TabController tabController = + TabController(length: tabList.length, initialIndex: 0, vsync: tester); + + final GFTabs tabs = GFTabs( + key: tabsKey, + controller: tabController, + length: tabList.length, + tabs: tabList, + isScrollable: true, + tabBarView: GFTabBarView( + controller: tabController, + children: + tabViewList.map((text) => StateMarker(child: text)).toList()), + ); + + final TestApp app = TestApp(tabs); + await tester.pumpWidget(app); + + // find tabs by key + expect(find.byKey(tabsKey), findsOneWidget); + // find the 'Tab1' text in tabs + expect(app.tabs.isScrollable, isTrue); + expect(find.text('Tab1'), findsOneWidget); + expect(find.byIcon(Icons.favorite), findsOneWidget); + expect(find.byWidget(tabList[2]), findsOneWidget); + expect(tabController.index, 0); + expect(bodyText[tabController.index], 'AAAAAA'); + expect(tabController.previousIndex, 0); + await tester.tap(find.byWidget(tabList[2])); + await tester.pumpAndSettle(); + expect(tabController.index, 2); + expect(bodyText[tabController.index], 'CCCCCC'); + await tester.tap(find.byIcon(Icons.favorite)); + await tester.pumpAndSettle(); + expect(tabController.index, 1); + expect(bodyText[tabController.index], 'BBBBBB'); + await tester.tap(find.text('Tab1')); + await tester.pumpAndSettle(); + expect(tabController.index, 0); + }); + + testWidgets('GFTabs with scrollable taps center selected tab', + (tester) async { + final List tabList = [ + const Text( + 'Tab_A', + ), + const Text( + 'Tab_B', + ), + const Text( + 'Tab_C', + ), + const Text( + 'Tab_D', + ), + const Text( + 'Tab_E', + ), + const Text( + 'Tab_F', + ), + const Text( + 'Tab_G', + ), + const Text( + 'Tab_H', + ), + const Text( + 'Tab_I', + ), + const Text( + 'Tab_J', + ), + const Text( + 'Tab_K', + ), + const Text( + 'Tab_L', + ), + ]; + final List bodyText = [ + 'AAAAAA', + 'BBBBBB', + 'CCCCCC', + 'DDDDDD', + 'EEEEEE', + 'FFFFFF', + 'GGGGGG', + 'HHHHHH', + 'IIIIII', + 'JJJJJJ', + 'KKKKKK', + 'LLLLLL' + ]; + final List tabViewList = [ + Text(bodyText[0]), + Text(bodyText[1]), + Text(bodyText[2]), + Text(bodyText[3]), + Text(bodyText[4]), + Text(bodyText[5]), + Text(bodyText[6]), + Text(bodyText[7]), + Text(bodyText[8]), + Text(bodyText[9]), + Text(bodyText[10]), + Text(bodyText[11]) + ]; + final TabController tabController = + TabController(length: tabList.length, initialIndex: 0, vsync: tester); + + final GFTabs tabs = GFTabs( + key: tabsKey, + controller: tabController, + length: tabList.length, + tabs: tabList, + isScrollable: true, + tabBarView: GFTabBarView( + controller: tabController, + children: + tabViewList.map((text) => StateMarker(child: text)).toList()), + ); + + final TestApp app = TestApp(tabs); + await tester.pumpWidget(app); + + // find tabs by key + expect(find.byKey(tabsKey), findsOneWidget); + // find the 'Tab_A' text in tabs + expect(app.tabs.isScrollable, isTrue); + expect(find.text('Tab_A'), findsOneWidget); + expect(tabController, isNotNull); + expect(tabController.index, 0); + expect(bodyText[tabController.index], 'AAAAAA'); + expect(tester.getSize(find.byKey(tabsKey)).width, equals(800.0)); + // The center of the 'Tab_F' item is to the right of the TabBar's center + expect(tester.getCenter(find.text('Tab_F')).dx, greaterThan(401.0)); + await tester.tap(find.text('Tab_F')); + await tester.pumpAndSettle(); + expect(tabController.index, 5); + expect(bodyText[tabController.index], 'FFFFFF'); + // The center of the 'Tab_F' item is now at the TabBar's center + expect(tester.getCenter(find.text('Tab_F')).dx, closeTo(400.0, 1.0)); + }); + + testWidgets('GFTabs can be scrolled independent of the selection', + (tester) async { + final List tabList = [ + const Text( + 'TabA', + ), + const Text( + 'TabB', + ), + const Text( + 'TabC', + ), + const Text( + 'TabD', + ), + const Text( + 'TabE', + ), + const Text( + 'TabF', + ), + const Text( + 'TabG', + ), + const Text( + 'TabH', + ), + const Text( + 'TabI', + ), + const Text( + 'TabJ', + ), + const Text( + 'TabK', + ), + const Text( + 'TabL', + ), + ]; + final List bodyText = [ + 'AAAAAA', + 'BBBBBB', + 'CCCCCC', + 'DDDDDD', + 'EEEEEE', + 'FFFFFF', + 'GGGGGG', + 'HHHHHH', + 'IIIIII', + 'JJJJJJ', + 'KKKKKK', + 'LLLLLL' + ]; + final List tabViewList = [ + Text(bodyText[0]), + Text(bodyText[1]), + Text(bodyText[2]), + Text(bodyText[3]), + Text(bodyText[4]), + Text(bodyText[5]), + Text(bodyText[6]), + Text(bodyText[7]), + Text(bodyText[8]), + Text(bodyText[9]), + Text(bodyText[10]), + Text(bodyText[11]) + ]; + final TabController tabController = + TabController(length: tabList.length, initialIndex: 0, vsync: tester); + + final GFTabs tabs = GFTabs( + key: tabsKey, + isScrollable: true, + controller: tabController, + length: tabList.length, + tabs: tabList, + tabBarView: GFTabBarView( + controller: tabController, + children: + tabViewList.map((text) => StateMarker(child: text)).toList()), + ); + + final TestApp app = TestApp(tabs); + await tester.pumpWidget(app); + + // find tabs by key + expect(find.byKey(tabsKey), findsOneWidget); + // find the 'Tab_A' text in tabs + expect(app.tabs.isScrollable, isTrue); + expect(find.text('TabA'), findsOneWidget); + expect(tabController, isNotNull); + expect(tabController.index, 0); + expect(bodyText[tabController.index], 'AAAAAA'); + // Fling-scroll the TabBar to the left + expect(tester.getCenter(find.text('TabH')).dx, lessThan(700.0)); + await tester.fling(find.byType(TabBar), const Offset(-200, 0), 10000); + await tester.pump(); + await tester + .pump(const Duration(seconds: 1)); // finish the scroll animation + expect(tester.getCenter(find.text('TabH')).dx, lessThan(500.0)); + // Scrolling the TabBar doesn't change the selection + expect(tabController.index, 0); + expect(bodyText[tabController.index], 'AAAAAA'); + }); + + testWidgets('GFTabs can be constructed', (tester) async { + final List tabBarList = [ + const Text('TabA'), + const Text('TabB'), + const Text('TabC'), + const Text('TabD'), + const Text('TabE') + ]; + final List tabs = [ + 'AAAAAA', + 'BBBBBB', + 'CCCCCC', + 'DDDDDD', + 'EEEEEE' + ]; + final List tabList = [ + Text(tabs[0]), + Text(tabs[1]), + Text(tabs[2]), + Text(tabs[3]), + Text(tabs[4]) + ]; + final TabController tabController = + TabController(length: tabList.length, initialIndex: 0, vsync: tester); + + final GFTabs tab = GFTabs( + key: tabsKey, + height: 960, + controller: tabController, + length: tabs.length, + tabs: tabBarList, + tabBarView: GFTabBarView( + controller: tabController, + key: tabBarViewKey, + children: tabList.map((text) => Container(child: text)).toList()), + ); + + final TestApp app = TestApp(tab); + await tester.pumpWidget(app); + + expect(find.text('AAAAAA'), findsOneWidget); + expect(find.text('BBBBBB'), findsNothing); + + Offset point = tester.getCenter(find.text('AAAAAA')); + await tester.dragFrom(point, const Offset(-600, 0)); + await tester.pump(); + expect(find.text('BBBBBB'), findsOneWidget); + + point = tester.getCenter(find.text('BBBBBB')); + await tester.dragFrom(point, const Offset(-600, 0)); + await tester.pump(); + expect(find.text('CCCCCC'), findsOneWidget); + + point = tester.getCenter(find.text('CCCCCC')); + await tester.dragFrom(point, const Offset(600, 0)); + await tester.pump(); + expect(find.text('BBBBBB'), findsOneWidget); + }); + + testWidgets('GFTabs can be constructed with properties', (tester) async { + final List tabList = [ + const Text( + 'TabA', + ), + const Text( + 'TabB', + ), + const Text( + 'TabC', + ), + const Text( + 'TabD', + ), + const Text( + 'TabE', + ), + ]; + final List tabText = [ + 'AAAAAA', + 'BBBBBB', + 'CCCCCC', + 'DDDDDD', + 'EEEEEE' + ]; + final List tabViewList = [ + Text(tabText[0]), + Text(tabText[1]), + Text(tabText[2]), + Text(tabText[3]), + Text(tabText[4]) + ]; + final TabController tabController = + TabController(length: tabList.length, initialIndex: 0, vsync: tester); + + final GFTabs tabs = GFTabs( + key: tabsKey, + controller: tabController, + length: tabList.length, + tabs: tabList, + tabBarView: GFTabBarView( + controller: tabController, + children: + tabViewList.map((text) => StateMarker(child: text)).toList()), + tabBarColor: Colors.blueGrey, + tabBarHeight: 66, + height: 345, + isScrollable: false, + shape: tabsShape, + indicator: indicator, + indicatorWeight: 5, + indicatorPadding: const EdgeInsets.all(8), + indicatorColor: GFColors.WHITE, + indicatorSize: TabBarIndicatorSize.tab, + labelPadding: const EdgeInsets.all(8), + labelColor: Colors.lightGreen, + unselectedLabelColor: Colors.black, + labelStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + ), + unselectedLabelStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 12, + ), + ); + + final TestApp app = TestApp(tabs); + await tester.pumpWidget(app); + + expect(app.tabs.controller, tabController); + expect(app.tabs.length, tabList.length); + expect(app.tabs.tabs, tabList); + expect(app.tabs.controller, tabController); + // expect(app.tabs.tabBarView, GFTabBarView( + // children: tabViewList.map((text) => StateMarker( + // child: text + // )).toList() + // ),); + expect(app.tabs.height, 345); + expect(app.tabs.tabBarColor, Colors.blueGrey); + expect(app.tabs.tabBarHeight, 66); + expect(app.tabs.isScrollable, isFalse); + expect(app.tabs.shape, tabsShape); + expect(app.tabs.indicator, indicator); + expect(app.tabs.indicatorWeight, 5); + expect(app.tabs.indicatorPadding, const EdgeInsets.all(8)); + expect(app.tabs.indicatorColor, GFColors.WHITE); + expect(app.tabs.indicatorSize, TabBarIndicatorSize.tab); + expect(app.tabs.labelPadding, const EdgeInsets.all(8)); + expect(app.tabs.labelColor, Colors.lightGreen); + expect(app.tabs.unselectedLabelColor, Colors.black); + expect( + app.tabs.labelStyle, + const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + )); + expect( + app.tabs.unselectedLabelStyle, + const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 12, + )); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.tabs); + + final GFTabs tabs; + + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('TabBar'), + ), + body: widget.tabs), + ); +} diff --git a/test/toggle_test.dart b/test/toggle_test.dart new file mode 100644 index 00000000..d952ca7d --- /dev/null +++ b/test/toggle_test.dart @@ -0,0 +1,520 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + final Key toggleKey = UniqueKey(); + + const enabledTextStyle = TextStyle(color: Colors.green); + const disabledTextStyle = TextStyle(color: Colors.red); + + testWidgets('GFToggle can be constructed', (WidgetTester tester) async { + bool isSelected = false; + + final toggle = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: Center( + child: GFToggle( + key: toggleKey, + value: isSelected, + onChanged: (value) { + setState(() { + isSelected = value!; + }); + }, + ), + ), + ), + ), + ); + + await tester.pumpWidget(toggle); + + expect(isSelected, isFalse); + await tester.tap(find.byKey(toggleKey)); + await tester.pump(); + expect(isSelected, isTrue); + }); + + testWidgets('GFToggle can be constructed with enabled and disabled Text', + (tester) async { + bool isOn = false; + + final toggle = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFToggle( + key: toggleKey, + value: isOn, + enabledText: 'ON', + disabledText: 'OFF', + onChanged: (value) { + setState(() { + isOn = value!; + }); + }, + ), + )), + ); + + // final TestApp app = TestApp(toggle); + await tester.pumpWidget(toggle); + + // find toggle by key + expect(find.byKey(toggleKey), findsOneWidget); + + // find text 'OFF' + expect(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.data!.contains('OFF'); + } + return toggleWidget.textSpan!.toPlainText().contains('OFF'); + } + return false; + }), findsOneWidget); + + // tap on text 'OFF; + await tester.tap(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.data!.contains('OFF'); + } + return toggleWidget.textSpan!.toPlainText().contains('OFF'); + } + return false; + })); + + // Rebuild the widget after the state has changed. + await tester.pump(); + + // find text 'ON' + expect(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.data!.contains('ON'); + } + return toggleWidget.textSpan!.toPlainText().contains('ON'); + } + return false; + }), findsOneWidget); + + // tap on text 'ON' + await tester.tap(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.data!.contains('ON'); + } + return toggleWidget.textSpan!.toPlainText().contains('ON'); + } + return false; + })); + + // Rebuild the widget after the state has changed. + await tester.pump(const Duration(milliseconds: 500)); + + // find text 'OFF' + expect(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.data!.contains('OFF'); + } + return toggleWidget.textSpan!.toPlainText().contains('OFF'); + } + return false; + }), findsOneWidget); + }); + + testWidgets('GFToggle with enabled and disabled color', + (WidgetTester tester) async { + bool isOn = false; + + final toggle = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFToggle( + key: toggleKey, + value: isOn, + enabledText: 'ON', + disabledText: 'OFF', + disabledThumbColor: Colors.amber, + disabledTrackColor: Colors.amberAccent, + enabledThumbColor: Colors.teal, + enabledTrackColor: Colors.tealAccent, + boxShape: BoxShape.rectangle, + borderRadius: const BorderRadius.all(Radius.circular(4)), + onChanged: (value) { + setState(() { + isOn = value!; + }); + }, + ), + )), + ); + + // find toggle key + await tester.pumpWidget(toggle); + + // finds value false + expect(isOn, isFalse); + + // find disabled color, finds one widget + expect(find.byWidgetPredicate((widget) { + if (widget is DecoratedBox) { + final DecoratedBox toggleWidget = widget; + // ignore: avoid_as + final BoxDecoration toggle = toggleWidget.decoration as BoxDecoration; + return toggle.color == Colors.amber; + } + return false; + }), findsOneWidget); + + // finds enabled color, finds nothing + expect(find.byWidgetPredicate((widget) { + if (widget is DecoratedBox) { + final DecoratedBox toggleWidget = widget; + // ignore: avoid_as + final BoxDecoration toggle = toggleWidget.decoration as BoxDecoration; + return toggle.color == Colors.teal; + } + return false; + }), findsNothing); + + // tap on text 'OFF' + await tester.tap(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.data!.contains('OFF'); + } + return toggleWidget.textSpan!.toPlainText().contains('OFF'); + } + return false; + })); + + // Rebuild the widget after the state has changed. + await tester.pump(); + + // finds value true + expect(isOn, isTrue); + + // finds enabled color, finds one widget + expect(find.byWidgetPredicate((widget) { + if (widget is DecoratedBox) { + final DecoratedBox toggleWidget = widget; + // ignore: avoid_as + final BoxDecoration toggle = toggleWidget.decoration as BoxDecoration; + return toggle.color == Colors.teal; + } + return false; + }), findsOneWidget); + + // finds disabled color, finds nothing + expect(find.byWidgetPredicate((widget) { + if (widget is DecoratedBox) { + final DecoratedBox toggleWidget = widget; + // ignore: avoid_as + final BoxDecoration toggle = toggleWidget.decoration as BoxDecoration; + return toggle.color == Colors.amber; + } + return false; + }), findsNothing); + }); + + testWidgets('GFToggle with borderRadius and boxShape', + (WidgetTester tester) async { + bool isOn = false; + + final toggle = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFToggle( + key: toggleKey, + value: isOn, + enabledText: 'ON', + disabledText: 'OFF', + disabledThumbColor: Colors.amber, + disabledTrackColor: Colors.amberAccent, + enabledThumbColor: Colors.teal, + enabledTrackColor: Colors.tealAccent, + boxShape: BoxShape.rectangle, + borderRadius: const BorderRadius.all(Radius.circular(4)), + onChanged: (value) { + setState(() { + isOn = value!; + }); + }, + ), + )), + ); + + // find toggle key + await tester.pumpWidget(toggle); + + // finds value false + expect(isOn, isFalse); + + // find border radius, finds one widget + expect(find.byWidgetPredicate((widget) { + if (widget is DecoratedBox) { + final DecoratedBox toggleWidget = widget; + // ignore: avoid_as + final BoxDecoration toggle = toggleWidget.decoration as BoxDecoration; + return toggle.borderRadius == + const BorderRadius.all(Radius.circular(4)); + } + return false; + }), findsOneWidget); + + // find boxShape, rectangle + expect(find.byWidgetPredicate((widget) { + if (widget is PhysicalModel) { + final PhysicalModel toggleWidget = widget; + final PhysicalModel toggle = toggleWidget; + return toggle.shape == BoxShape.rectangle; + } + return false; + }), findsOneWidget); + }); + + testWidgets('GFToggle with enabled and disabled textStyle', + (WidgetTester tester) async { + bool isOn = false; + + final toggle = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFToggle( + key: toggleKey, + value: isOn, + enabledText: 'ON', + disabledText: 'OFF', + enabledTextStyle: enabledTextStyle, + disabledTextStyle: disabledTextStyle, + disabledThumbColor: Colors.amber, + disabledTrackColor: Colors.amberAccent, + enabledThumbColor: Colors.teal, + enabledTrackColor: Colors.tealAccent, + boxShape: BoxShape.rectangle, + borderRadius: const BorderRadius.all(Radius.circular(4)), + onChanged: (value) { + setState(() { + isOn = value!; + }); + }, + ), + )), + ); + + // find toggle key + await tester.pumpWidget(toggle); + + // finds value false + expect(isOn, isFalse); + + // find disabled text color + expect(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.style!.color == disabledTextStyle.color; + } + } + return false; + }), findsOneWidget); + + // find enabled text color + expect(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.style!.color == enabledTextStyle.color; + } + } + return false; + }), findsNothing); + + // tap on text 'OFF' + await tester.tap(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.data!.contains('OFF'); + } + return toggleWidget.textSpan!.toPlainText().contains('OFF'); + } + return false; + })); + + // Rebuild the widget after the state has changed. + await tester.pump(); + + // find disabled text color + expect(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.style!.color == disabledTextStyle.color; + } + } + return false; + }), findsNothing); + + // find enabled text color + expect(find.byWidgetPredicate((widget) { + if (widget is Text) { + final Text toggleWidget = widget; + if (toggleWidget.data != null) { + return toggleWidget.style!.color == enabledTextStyle.color; + } + } + return false; + }), findsOneWidget); + }); + + testWidgets('GFToggle with type ios', (WidgetTester tester) async { + bool isOn = false; + + final toggle = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: GFToggle( + key: toggleKey, + value: isOn, + enabledText: 'ON', + disabledText: 'OFF', + type: GFToggleType.ios, + onChanged: (value) { + setState(() { + isOn = value!; + }); + }, + ), + )), + ); + + // find toggle key + await tester.pumpWidget(toggle); + + // finds value false + expect(isOn, isFalse); + + // find toggle type, ios + expect(find.byWidgetPredicate((widget) { + if (widget is DecoratedBox) { + final DecoratedBox toggleWidget = widget; + // ignore: avoid_as + final BoxDecoration toggle = toggleWidget.decoration as BoxDecoration; + return toggle.shape == BoxShape.circle; + } + return false; + }), findsOneWidget); + }); + + testWidgets('GFToggle can be constructed with long duration', + (WidgetTester tester) async { + bool isSelected = false; + + final toggle = Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => Material( + child: Center( + child: GFToggle( + key: toggleKey, + value: isSelected, + onChanged: (value) { + setState(() { + isSelected = value!; + }); + }, + duration: const Duration(milliseconds: 800), + ), + ), + ), + ), + ); + + await tester.pumpWidget(toggle); + + expect(isSelected, isFalse); + await tester.tap(find.byKey(toggleKey)); + await tester.pump(); + expect(isSelected, isTrue); + }); + + testWidgets('GFToggle with all properties.', (WidgetTester tester) async { + bool isOn = false; + + final GFToggle toggle = GFToggle( + key: toggleKey, + value: isOn, + enabledText: 'ON', + disabledText: 'OFF', + enabledTextStyle: enabledTextStyle, + disabledTextStyle: disabledTextStyle, + disabledThumbColor: Colors.amber, + disabledTrackColor: Colors.amberAccent, + enabledThumbColor: Colors.teal, + enabledTrackColor: Colors.tealAccent, + boxShape: BoxShape.rectangle, + borderRadius: const BorderRadius.all(Radius.circular(4)), + duration: const Duration(milliseconds: 400), + type: GFToggleType.ios, + onChanged: (value) { + isOn = value!; + }, + ); + + final TestApp app = TestApp(toggle); + await tester.pumpWidget(app); + + expect(app.toggle.value, isOn); + expect(app.toggle.enabledText, 'ON'); + expect(app.toggle.disabledText, 'OFF'); + expect(app.toggle.enabledTextStyle, enabledTextStyle); + expect(app.toggle.disabledTextStyle, disabledTextStyle); + expect(app.toggle.enabledThumbColor, Colors.teal); + expect(app.toggle.enabledTrackColor, Colors.tealAccent); + expect(app.toggle.disabledThumbColor, Colors.amber); + expect(app.toggle.disabledTrackColor, Colors.amberAccent); + expect(app.toggle.boxShape, BoxShape.rectangle); + expect(app.toggle.borderRadius, const BorderRadius.all(Radius.circular(4))); + expect(app.toggle.duration, const Duration(milliseconds: 400)); + expect(app.toggle.type, GFToggleType.ios); + }); +} + +class TestApp extends StatefulWidget { + const TestApp(this.toggle); + final GFToggle toggle; + @override + _TestAppState createState() => _TestAppState(); +} + +class _TestAppState extends State { + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + body: Column( + children: [ + widget.toggle, + ], + ), + ), + ); +} diff --git a/test/typography_test.dart b/test/typography_test.dart index 5ba4d11e..33c894d5 100644 --- a/test/typography_test.dart +++ b/test/typography_test.dart @@ -44,6 +44,7 @@ void main() { const bool divider = true; const GFTypography typography = GFTypography( + text: 'type 1', showDivider: divider, ); @@ -56,6 +57,7 @@ void main() { final textopacity = Colors.black.withOpacity(0.56); final GFTypography typography = GFTypography( + text: 'type1', textColor: textopacity, ); @@ -79,6 +81,7 @@ void main() { icon: icon, backgroundImage: bgImage, backgroundImagecolorFilter: colorfilter, + text: 'type1', ); final TestApp app = TestApp(typography);