Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skimmer effect while fetching featured circuits #57

Closed
wants to merge 11 commits into from
228 changes: 228 additions & 0 deletions lib/ui/components/cv_shimmer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

enum CVShimmerDirection { LeftToRight, RightToLeft, TopToBottom, BottomToTop }

@immutable
class CVShimmer extends StatefulWidget {
final Widget child;
final Duration period;
final CVShimmerDirection direction;
final Gradient gradient;
final int loop;
final bool enabled;

const CVShimmer({
Key key,
@required this.child,
@required this.gradient,
this.direction = CVShimmerDirection.LeftToRight,
this.period = const Duration(milliseconds: 1500),
this.loop = 0,
this.enabled = true,
}) : super(key: key);

CVShimmer.fromColors({
Key key,
@required this.child,
@required Color baseColor,
@required Color highlightColor,
this.period = const Duration(milliseconds: 1500),
this.direction = CVShimmerDirection.LeftToRight,
this.loop = 0,
this.enabled = true,
}) : gradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.centerRight,
colors: <Color>[
baseColor,
baseColor,
highlightColor,
baseColor,
baseColor
],
stops: const <double>[
0.0,
0.35,
0.5,
0.65,
1.0
]),
super(key: key);

@override
_CVShimmerState createState() => _CVShimmerState();

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Gradient>('gradient', gradient,
defaultValue: null));
properties.add(EnumProperty<CVShimmerDirection>('direction', direction));
properties.add(
DiagnosticsProperty<Duration>('period', period, defaultValue: null));
properties
.add(DiagnosticsProperty<bool>('enabled', enabled, defaultValue: null));
}
}

class _CVShimmerState extends State<CVShimmer>
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(CVShimmer 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) => _CVShimmer(
child: child,
direction: widget.direction,
gradient: widget.gradient,
percent: _controller.value,
),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

@immutable
class _CVShimmer extends SingleChildRenderObjectWidget {
final double percent;
final CVShimmerDirection direction;
final Gradient gradient;

const _CVShimmer({
Widget child,
@required this.percent,
@required this.direction,
@required this.gradient,
}) : super(child: child);

@override
_CVShimmerFilter createRenderObject(BuildContext context) {
return _CVShimmerFilter(percent, direction, gradient);
}

@override
void updateRenderObject(BuildContext context, _CVShimmerFilter CVShimmer) {
CVShimmer.percent = percent;
CVShimmer.gradient = gradient;
CVShimmer.direction = direction;
}
}

class _CVShimmerFilter extends RenderProxyBox {
CVShimmerDirection _direction;
Gradient _gradient;
double _percent;

_CVShimmerFilter(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(CVShimmerDirection 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 == CVShimmerDirection.RightToLeft) {
dx = _offset(width, -width, _percent);
dy = 0.0;
rect = Rect.fromLTWH(dx - width, dy, 3 * width, height);
} 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 == CVShimmerDirection.BottomToTop) {
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;
}
}
36 changes: 36 additions & 0 deletions lib/ui/views/projects/featured_projects_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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_shimmer.dart';

class FeaturedProjectsView extends StatefulWidget {
static const String id = 'featured_projects_view';
final bool showAppBar;
Expand Down Expand Up @@ -41,6 +43,40 @@ class _FeaturedProjectsViewState extends State<FeaturedProjectsView> {
),
);
});
} else if (model.isBusy(model.FETCH_FEATURED_PROJECTS)) {
_items.add(Padding(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can reduce code by making a single widget and adding it to the list 3 times.

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(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()),
),
));
}

if (!widget.embed &&
Expand Down