From 6c8004ab2ea775ebd1a3baf8944ef6516eec3e14 Mon Sep 17 00:00:00 2001 From: iCoder-007 Date: Tue, 19 Jan 2021 01:38:27 -0500 Subject: [PATCH 1/5] added skimmer effect while fetching circuits --- lib/ui/components/cv_skimmer.dart | 227 ++++++++++++++++++ .../projects/featured_projects_view.dart | 36 +++ 2 files changed, 263 insertions(+) create mode 100644 lib/ui/components/cv_skimmer.dart diff --git a/lib/ui/components/cv_skimmer.dart b/lib/ui/components/cv_skimmer.dart new file mode 100644 index 00000000..e8591abc --- /dev/null +++ b/lib/ui/components/cv_skimmer.dart @@ -0,0 +1,227 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +enum ShimmerDirection { ltr, rtl, ttb, btt } + +@immutable +class Shimmer extends StatefulWidget { + final Widget child; + final Duration period; + final ShimmerDirection direction; + final Gradient gradient; + final int loop; + final bool enabled; + + const Shimmer({ + Key key, + @required this.child, + @required this.gradient, + this.direction = ShimmerDirection.ltr, + this.period = const Duration(milliseconds: 1500), + this.loop = 0, + this.enabled = true, + }) : super(key: key); + + Shimmer.fromColors({ + Key key, + @required this.child, + @required Color baseColor, + @required Color highlightColor, + this.period = const Duration(milliseconds: 1500), + this.direction = ShimmerDirection.ltr, + this.loop = 0, + this.enabled = true, + }) : gradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.centerRight, + colors: [ + baseColor, + baseColor, + highlightColor, + baseColor, + baseColor + ], + stops: const [ + 0.0, + 0.35, + 0.5, + 0.65, + 1.0 + ]), + super(key: key); + + @override + _ShimmerState createState() => _ShimmerState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('gradient', gradient, + defaultValue: null)); + properties.add(EnumProperty('direction', direction)); + properties.add( + DiagnosticsProperty('period', period, defaultValue: null)); + properties + .add(DiagnosticsProperty('enabled', enabled, defaultValue: null)); + } +} + +class _ShimmerState extends State with SingleTickerProviderStateMixin { + AnimationController _controller; + int _count = 0; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this, duration: widget.period) + ..addStatusListener((AnimationStatus status) { + if (status != AnimationStatus.completed) { + return; + } + _count++; + if (widget.loop <= 0) { + _controller.repeat(); + } else if (_count < widget.loop) { + _controller.forward(from: 0.0); + } + }); + if (widget.enabled) { + _controller.forward(); + } + } + + @override + void didUpdateWidget(Shimmer oldWidget) { + if (widget.enabled) { + _controller.forward(); + } else { + _controller.stop(); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + child: widget.child, + builder: (BuildContext context, Widget child) => _Shimmer( + child: child, + direction: widget.direction, + gradient: widget.gradient, + percent: _controller.value, + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} + +@immutable +class _Shimmer extends SingleChildRenderObjectWidget { + final double percent; + final ShimmerDirection direction; + final Gradient gradient; + + const _Shimmer({ + Widget child, + @required this.percent, + @required this.direction, + @required this.gradient, + }) : super(child: child); + + @override + _ShimmerFilter createRenderObject(BuildContext context) { + return _ShimmerFilter(percent, direction, gradient); + } + + @override + void updateRenderObject(BuildContext context, _ShimmerFilter shimmer) { + shimmer.percent = percent; + shimmer.gradient = gradient; + shimmer.direction = direction; + } +} + +class _ShimmerFilter extends RenderProxyBox { + ShimmerDirection _direction; + Gradient _gradient; + double _percent; + + _ShimmerFilter(this._percent, this._direction, this._gradient); + + @override + ShaderMaskLayer get layer => super.layer as ShaderMaskLayer; + + @override + bool get alwaysNeedsCompositing => child != null; + + set percent(double newValue) { + if (newValue == _percent) { + return; + } + _percent = newValue; + markNeedsPaint(); + } + + set gradient(Gradient newValue) { + if (newValue == _gradient) { + return; + } + _gradient = newValue; + markNeedsPaint(); + } + + set direction(ShimmerDirection newDirection) { + if (newDirection == _direction) { + return; + } + _direction = newDirection; + markNeedsLayout(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null) { + assert(needsCompositing); + + final width = child.size.width; + final height = child.size.height; + Rect rect; + double dx, dy; + if (_direction == ShimmerDirection.rtl) { + dx = _offset(width, -width, _percent); + dy = 0.0; + rect = Rect.fromLTWH(dx - width, dy, 3 * width, height); + } else if (_direction == ShimmerDirection.ttb) { + dx = 0.0; + dy = _offset(-height, height, _percent); + rect = Rect.fromLTWH(dx, dy - height, width, 3 * height); + } else if (_direction == ShimmerDirection.btt) { + dx = 0.0; + dy = _offset(height, -height, _percent); + rect = Rect.fromLTWH(dx, dy - height, width, 3 * height); + } else { + dx = _offset(-width, width, _percent); + dy = 0.0; + rect = Rect.fromLTWH(dx - width, dy, 3 * width, height); + } + layer ??= ShaderMaskLayer(); + layer + ..shader = _gradient.createShader(rect) + ..maskRect = offset & size + ..blendMode = BlendMode.srcIn; + context.pushLayer(layer, super.paint, offset); + } else { + layer = null; + } + } + + double _offset(double start, double end, double percent) { + return start + (end - start) * percent; + } +} diff --git a/lib/ui/views/projects/featured_projects_view.dart b/lib/ui/views/projects/featured_projects_view.dart index c827be72..051d9cc6 100644 --- a/lib/ui/views/projects/featured_projects_view.dart +++ b/lib/ui/views/projects/featured_projects_view.dart @@ -7,6 +7,8 @@ import 'package:mobile_app/ui/views/projects/components/featured_project_card.da import 'package:mobile_app/ui/views/projects/project_details_view.dart'; import 'package:mobile_app/viewmodels/projects/featured_projects_viewmodel.dart'; +import '../../components/cv_skimmer.dart'; + class FeaturedProjectsView extends StatefulWidget { static const String id = 'featured_projects_view'; final bool showAppBar; @@ -41,6 +43,40 @@ class _FeaturedProjectsViewState extends State { ), ); }); + } else if (model.isBusy(model.FETCH_FEATURED_PROJECTS)) { + _items.add(Padding( + padding: EdgeInsets.symmetric(vertical: 15), + child: SizedBox( + width: MediaQuery.of(context).size.width * .9, + height: 200.0, + child: Shimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Card()), + ), + )); + _items.add(Padding( + padding: EdgeInsets.symmetric(vertical: 15), + child: SizedBox( + width: MediaQuery.of(context).size.width * .9, + height: 200.0, + child: Shimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Card()), + ), + )); + _items.add(Padding( + padding: EdgeInsets.symmetric(vertical: 15), + child: SizedBox( + width: MediaQuery.of(context).size.width * .9, + height: 200.0, + child: Shimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Card()), + ), + )); } if (!widget.embed && From bd0c0f96768a3774c56670e4ece96288fa42e508 Mon Sep 17 00:00:00 2001 From: iCoder-007 Date: Tue, 19 Jan 2021 23:21:02 -0500 Subject: [PATCH 2/5] fixed typo --- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../{cv_skimmer.dart => cv_shimmer.dart} | 57 ++++++++++--------- .../projects/featured_projects_view.dart | 8 +-- 3 files changed, 34 insertions(+), 33 deletions(-) rename lib/ui/components/{cv_skimmer.dart => cv_shimmer.dart} (75%) diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 296b146b..2640865c 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip diff --git a/lib/ui/components/cv_skimmer.dart b/lib/ui/components/cv_shimmer.dart similarity index 75% rename from lib/ui/components/cv_skimmer.dart rename to lib/ui/components/cv_shimmer.dart index e8591abc..0a366443 100644 --- a/lib/ui/components/cv_skimmer.dart +++ b/lib/ui/components/cv_shimmer.dart @@ -1,34 +1,34 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -enum ShimmerDirection { ltr, rtl, ttb, btt } +enum CVShimmerDirection { LeftToRight, RightToLeft, TopToBottom, BottomToTop } @immutable -class Shimmer extends StatefulWidget { +class CVShimmer extends StatefulWidget { final Widget child; final Duration period; - final ShimmerDirection direction; + final CVShimmerDirection direction; final Gradient gradient; final int loop; final bool enabled; - const Shimmer({ + const CVShimmer({ Key key, @required this.child, @required this.gradient, - this.direction = ShimmerDirection.ltr, + this.direction = CVShimmerDirection.LeftToRight, this.period = const Duration(milliseconds: 1500), this.loop = 0, this.enabled = true, }) : super(key: key); - Shimmer.fromColors({ + CVShimmer.fromColors({ Key key, @required this.child, @required Color baseColor, @required Color highlightColor, this.period = const Duration(milliseconds: 1500), - this.direction = ShimmerDirection.ltr, + this.direction = CVShimmerDirection.LeftToRight, this.loop = 0, this.enabled = true, }) : gradient = LinearGradient( @@ -51,14 +51,14 @@ class Shimmer extends StatefulWidget { super(key: key); @override - _ShimmerState createState() => _ShimmerState(); + _CVShimmerState createState() => _CVShimmerState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('gradient', gradient, defaultValue: null)); - properties.add(EnumProperty('direction', direction)); + properties.add(EnumProperty('direction', direction)); properties.add( DiagnosticsProperty('period', period, defaultValue: null)); properties @@ -66,7 +66,8 @@ class Shimmer extends StatefulWidget { } } -class _ShimmerState extends State with SingleTickerProviderStateMixin { +class _CVShimmerState extends State + with SingleTickerProviderStateMixin { AnimationController _controller; int _count = 0; @@ -91,7 +92,7 @@ class _ShimmerState extends State with SingleTickerProviderStateMixin { } @override - void didUpdateWidget(Shimmer oldWidget) { + void didUpdateWidget(CVShimmer oldWidget) { if (widget.enabled) { _controller.forward(); } else { @@ -105,7 +106,7 @@ class _ShimmerState extends State with SingleTickerProviderStateMixin { return AnimatedBuilder( animation: _controller, child: widget.child, - builder: (BuildContext context, Widget child) => _Shimmer( + builder: (BuildContext context, Widget child) => _CVShimmer( child: child, direction: widget.direction, gradient: widget.gradient, @@ -122,12 +123,12 @@ class _ShimmerState extends State with SingleTickerProviderStateMixin { } @immutable -class _Shimmer extends SingleChildRenderObjectWidget { +class _CVShimmer extends SingleChildRenderObjectWidget { final double percent; - final ShimmerDirection direction; + final CVShimmerDirection direction; final Gradient gradient; - const _Shimmer({ + const _CVShimmer({ Widget child, @required this.percent, @required this.direction, @@ -135,24 +136,24 @@ class _Shimmer extends SingleChildRenderObjectWidget { }) : super(child: child); @override - _ShimmerFilter createRenderObject(BuildContext context) { - return _ShimmerFilter(percent, direction, gradient); + _CVShimmerFilter createRenderObject(BuildContext context) { + return _CVShimmerFilter(percent, direction, gradient); } @override - void updateRenderObject(BuildContext context, _ShimmerFilter shimmer) { - shimmer.percent = percent; - shimmer.gradient = gradient; - shimmer.direction = direction; + void updateRenderObject(BuildContext context, _CVShimmerFilter CVShimmer) { + CVShimmer.percent = percent; + CVShimmer.gradient = gradient; + CVShimmer.direction = direction; } } -class _ShimmerFilter extends RenderProxyBox { - ShimmerDirection _direction; +class _CVShimmerFilter extends RenderProxyBox { + CVShimmerDirection _direction; Gradient _gradient; double _percent; - _ShimmerFilter(this._percent, this._direction, this._gradient); + _CVShimmerFilter(this._percent, this._direction, this._gradient); @override ShaderMaskLayer get layer => super.layer as ShaderMaskLayer; @@ -176,7 +177,7 @@ class _ShimmerFilter extends RenderProxyBox { markNeedsPaint(); } - set direction(ShimmerDirection newDirection) { + set direction(CVShimmerDirection newDirection) { if (newDirection == _direction) { return; } @@ -193,15 +194,15 @@ class _ShimmerFilter extends RenderProxyBox { final height = child.size.height; Rect rect; double dx, dy; - if (_direction == ShimmerDirection.rtl) { + if (_direction == CVShimmerDirection.RightToLeft) { dx = _offset(width, -width, _percent); dy = 0.0; rect = Rect.fromLTWH(dx - width, dy, 3 * width, height); - } else if (_direction == ShimmerDirection.ttb) { + } else if (_direction == CVShimmerDirection.TopToBottom) { dx = 0.0; dy = _offset(-height, height, _percent); rect = Rect.fromLTWH(dx, dy - height, width, 3 * height); - } else if (_direction == ShimmerDirection.btt) { + } else if (_direction == CVShimmerDirection.BottomToTop) { dx = 0.0; dy = _offset(height, -height, _percent); rect = Rect.fromLTWH(dx, dy - height, width, 3 * height); diff --git a/lib/ui/views/projects/featured_projects_view.dart b/lib/ui/views/projects/featured_projects_view.dart index 051d9cc6..8aba7e2a 100644 --- a/lib/ui/views/projects/featured_projects_view.dart +++ b/lib/ui/views/projects/featured_projects_view.dart @@ -7,7 +7,7 @@ import 'package:mobile_app/ui/views/projects/components/featured_project_card.da import 'package:mobile_app/ui/views/projects/project_details_view.dart'; import 'package:mobile_app/viewmodels/projects/featured_projects_viewmodel.dart'; -import '../../components/cv_skimmer.dart'; +import '../../components/cv_shimmer.dart'; class FeaturedProjectsView extends StatefulWidget { static const String id = 'featured_projects_view'; @@ -49,7 +49,7 @@ class _FeaturedProjectsViewState extends State { child: SizedBox( width: MediaQuery.of(context).size.width * .9, height: 200.0, - child: Shimmer.fromColors( + child: CVShimmer.fromColors( baseColor: Colors.grey, highlightColor: Colors.white, child: Card()), @@ -60,7 +60,7 @@ class _FeaturedProjectsViewState extends State { child: SizedBox( width: MediaQuery.of(context).size.width * .9, height: 200.0, - child: Shimmer.fromColors( + child: CVShimmer.fromColors( baseColor: Colors.grey, highlightColor: Colors.white, child: Card()), @@ -71,7 +71,7 @@ class _FeaturedProjectsViewState extends State { child: SizedBox( width: MediaQuery.of(context).size.width * .9, height: 200.0, - child: Shimmer.fromColors( + child: CVShimmer.fromColors( baseColor: Colors.grey, highlightColor: Colors.white, child: Card()), From 53ce04a4e2936d96cff8c1a9d77149182da3cc1c Mon Sep 17 00:00:00 2001 From: iCoder-007 Date: Tue, 19 Jan 2021 23:24:27 -0500 Subject: [PATCH 3/5] fixed typo --- android/gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2640865c..296b146b 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip From 082b2b2e18af2b7669f6df9cd4ed73ba84489204 Mon Sep 17 00:00:00 2001 From: iCoder-007 Date: Thu, 21 Jan 2021 00:01:42 -0500 Subject: [PATCH 4/5] removed unnecessary code --- .../projects/featured_projects_view.dart | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/lib/ui/views/projects/featured_projects_view.dart b/lib/ui/views/projects/featured_projects_view.dart index 8aba7e2a..e569809c 100644 --- a/lib/ui/views/projects/featured_projects_view.dart +++ b/lib/ui/views/projects/featured_projects_view.dart @@ -44,7 +44,7 @@ class _FeaturedProjectsViewState extends State { ); }); } else if (model.isBusy(model.FETCH_FEATURED_PROJECTS)) { - _items.add(Padding( + var _shimmerWidget = Padding( padding: EdgeInsets.symmetric(vertical: 15), child: SizedBox( width: MediaQuery.of(context).size.width * .9, @@ -54,29 +54,10 @@ class _FeaturedProjectsViewState extends State { highlightColor: Colors.white, child: Card()), ), - )); - _items.add(Padding( - padding: EdgeInsets.symmetric(vertical: 15), - child: SizedBox( - width: MediaQuery.of(context).size.width * .9, - height: 200.0, - child: CVShimmer.fromColors( - baseColor: Colors.grey, - highlightColor: Colors.white, - child: Card()), - ), - )); - _items.add(Padding( - padding: EdgeInsets.symmetric(vertical: 15), - child: SizedBox( - width: MediaQuery.of(context).size.width * .9, - height: 200.0, - child: CVShimmer.fromColors( - baseColor: Colors.grey, - highlightColor: Colors.white, - child: Card()), - ), - )); + ); + _items.add(_shimmerWidget); + _items.add(_shimmerWidget); + _items.add(_shimmerWidget); } if (!widget.embed && From 96dc4de4ff091b37e7763fd30242561728a7eb09 Mon Sep 17 00:00:00 2001 From: iCoder-007 Date: Fri, 29 Jan 2021 23:01:36 -0500 Subject: [PATCH 5/5] skimmer effect while fetching circuit --- lib/ui/views/groups/my_groups_view.dart | 28 +++++++++++++++++++ .../views/profile/user_favourites_view.dart | 15 ++++++++++ lib/ui/views/profile/user_projects_view.dart | 16 +++++++++++ 3 files changed, 59 insertions(+) diff --git a/lib/ui/views/groups/my_groups_view.dart b/lib/ui/views/groups/my_groups_view.dart index 0730a71a..cd5e8b30 100644 --- a/lib/ui/views/groups/my_groups_view.dart +++ b/lib/ui/views/groups/my_groups_view.dart @@ -6,6 +6,7 @@ import 'package:mobile_app/models/groups.dart'; import 'package:mobile_app/services/dialog_service.dart'; import 'package:mobile_app/ui/components/cv_add_icon_button.dart'; import 'package:mobile_app/ui/components/cv_primary_button.dart'; +import 'package:mobile_app/ui/components/cv_shimmer.dart'; import 'package:mobile_app/ui/views/base_view.dart'; import 'package:mobile_app/ui/views/groups/components/group_member_card.dart'; import 'package:mobile_app/ui/views/groups/components/group_mentor_card.dart'; @@ -128,6 +129,20 @@ class _MyGroupsViewState extends State { CVAddIconButton(onPressed: _model.fetchMentoredGroups), ); } + } else if (model.isBusy(model.FETCH_MENTORED_GROUPS)) { + var _shimmerWidget = Padding( + padding: EdgeInsets.symmetric(vertical: 15), + child: SizedBox( + width: MediaQuery.of(context).size.width * .9, + height: 150.0, + child: CVShimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Card()), + ), + ); + _items.add(_shimmerWidget); + _items.add(_shimmerWidget); } _items.add(SizedBox(height: 24)); @@ -146,6 +161,19 @@ class _MyGroupsViewState extends State { CVAddIconButton(onPressed: _model.fetchMemberGroups), ); } + } else if (model.isBusy(model.FETCH_MEMBER_GROUPS)) { + var _shimmerWidget = Padding( + padding: EdgeInsets.symmetric(vertical: 15), + child: SizedBox( + width: MediaQuery.of(context).size.width * .9, + height: 100.0, + child: CVShimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Card()), + ), + ); + _items.add(_shimmerWidget); } return ListView( diff --git a/lib/ui/views/profile/user_favourites_view.dart b/lib/ui/views/profile/user_favourites_view.dart index fb33990f..6044f4a2 100644 --- a/lib/ui/views/profile/user_favourites_view.dart +++ b/lib/ui/views/profile/user_favourites_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:mobile_app/models/projects.dart'; import 'package:mobile_app/ui/components/cv_add_icon_button.dart'; +import 'package:mobile_app/ui/components/cv_shimmer.dart'; import 'package:mobile_app/ui/views/base_view.dart'; import 'package:mobile_app/ui/views/projects/components/project_card.dart'; import 'package:mobile_app/ui/views/projects/project_details_view.dart'; @@ -45,6 +46,20 @@ class _UserFavouritesViewState extends State ), ); }); + } else if (model.isBusy(model.FETCH_USER_FAVOURITES)) { + var _shimmerWidget = Padding( + padding: EdgeInsets.symmetric(vertical: 15), + child: SizedBox( + width: MediaQuery.of(context).size.width * .9, + height: 200.0, + child: CVShimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Card()), + ), + ); + _items.add(_shimmerWidget); + _items.add(_shimmerWidget); } if (model?.previousUserFavouritesBatch?.links?.next != null) { diff --git a/lib/ui/views/profile/user_projects_view.dart b/lib/ui/views/profile/user_projects_view.dart index ecf3c7d9..a576062d 100644 --- a/lib/ui/views/profile/user_projects_view.dart +++ b/lib/ui/views/profile/user_projects_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:mobile_app/ui/components/cv_add_icon_button.dart'; +import 'package:mobile_app/ui/components/cv_shimmer.dart'; import 'package:mobile_app/ui/views/base_view.dart'; import 'package:mobile_app/ui/views/projects/components/project_card.dart'; import 'package:mobile_app/ui/views/projects/project_details_view.dart'; @@ -42,6 +43,21 @@ class _UserProjectsViewState extends State ), ); }); + } else if (model.isBusy(model.FETCH_USER_PROJECTS) != null && + model.isBusy(model.FETCH_USER_PROJECTS)) { + var _shimmerWidget = Padding( + padding: EdgeInsets.symmetric(vertical: 15), + child: SizedBox( + width: MediaQuery.of(context).size.width * .9, + height: 200.0, + child: CVShimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Card()), + ), + ); + _items.add(_shimmerWidget); + _items.add(_shimmerWidget); } if (model?.previousUserProjectsBatch?.links?.next != null) {