wonders/lib/ui/wonder_illustrations/common/animated_clouds.dart
2024-01-18 10:43:46 -07:00

155 lines
4.9 KiB
Dart

import 'dart:async';
import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/utils/context_utils.dart';
// Shows a set of clouds that animated onto stage.
// When value-key is changed, a new set of clouds will animate into place and the old ones will animate out.
// Uses a random seed system, to make sure we get the same set of clouds for each wonder, without actually having to hand-position them.
class AnimatedClouds extends StatefulWidget with GetItStatefulWidgetMixin {
AnimatedClouds(
{Key? key, this.enableAnimations = true, required this.wonderType, required this.opacity, this.cloudSize = 500})
: super(key: key);
final WonderType wonderType;
final bool enableAnimations;
final double opacity;
final double cloudSize;
@override
State<AnimatedClouds> createState() => _AnimatedCloudsState();
}
class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProviderStateMixin, GetItStateMixin {
late List<_Cloud> _clouds = [];
List<_Cloud> _oldClouds = [];
late final AnimationController _anim = AnimationController(vsync: this, duration: 1500.ms);
@override
void initState() {
super.initState();
scheduleMicrotask(() {
setState(() => _clouds = _getClouds());
});
_showClouds();
}
@override
void dispose() {
_anim.dispose();
super.dispose();
}
@override
void didUpdateWidget(covariant AnimatedClouds oldWidget) {
if (oldWidget.wonderType != widget.wonderType) {
_oldClouds = _clouds;
_clouds = _getClouds();
_showClouds();
}
super.didUpdateWidget(oldWidget);
}
int _getCloudSeed(WonderType type) {
switch (type) {
case WonderType.chichenItza:
return 2;
case WonderType.christRedeemer:
return 78;
case WonderType.colosseum:
return 1;
case WonderType.greatWall:
return 500;
case WonderType.machuPicchu:
return 37;
case WonderType.petra:
return 111;
case WonderType.pyramidsGiza:
return 15;
case WonderType.tajMahal:
return 2;
}
}
/// Starts playing the clouds animation, or jumps right to the end, based on [AnimatedClouds.enableAnimations]
void _showClouds() {
widget.enableAnimations ? _anim.forward(from: 0) : _anim.value = 1;
}
@override
Widget build(BuildContext context) {
// Old clouds animate from 0 to startOffset, new clouds do the opposite.
Widget buildCloud(_Cloud c, {required bool isOld, required int startOffset}) {
// Use a positive, or negative start offset, based on index
final stOffset = _clouds.indexOf(c) % 2 == 0 ? -startOffset : startOffset;
// If old, we will end at the stOffset and start at 0, if new, start at stOffset, and end at 0
double curvedValue = Curves.easeOut.transform(_anim.value);
return Positioned(
top: c.pos.dy,
left: isOld ? c.pos.dx - stOffset * curvedValue : c.pos.dx + stOffset * (1 - curvedValue),
child: Opacity(opacity: isOld ? 1 - _anim.value : _anim.value, child: c),
);
}
return RepaintBoundary(
child: ClipRect(
child: OverflowBox(
child: AnimatedBuilder(
animation: _anim,
builder: (_, __) {
// A stack with 2 sets of clouds, one set is moving out of view while the other moves in.
return Stack(
clipBehavior: Clip.hardEdge,
key: ValueKey(widget.wonderType),
children: [
if (_anim.value != 1) ...[
..._oldClouds.map((c) => buildCloud(c, isOld: true, startOffset: 1000)),
],
..._clouds.map((c) => buildCloud(c, isOld: false, startOffset: 1000)),
],
);
},
),
),
),
);
}
List<_Cloud> _getClouds() {
Size size = ContextUtils.getSize(context) ?? Size(context.widthPx, 400);
rndSeed = _getCloudSeed(widget.wonderType);
return List<_Cloud>.generate(3, (index) {
return _Cloud(
Offset(rnd.getDouble(-200, size.width - 100), rnd.getDouble(50, size.height - 50)),
scale: rnd.getDouble(.7, 1),
flipX: rnd.getBool(),
flipY: rnd.getBool(),
opacity: widget.opacity,
size: widget.cloudSize,
);
}).toList();
}
}
class _Cloud extends StatelessWidget {
const _Cloud(this.pos,
{this.scale = 1, this.flipX = false, this.flipY = false, required this.opacity, required this.size});
final Offset pos;
final double scale;
final bool flipX;
final bool flipY;
final double opacity;
final double size;
@override
Widget build(BuildContext context) => Transform.scale(
scaleX: scale * (flipX ? -1 : 1),
scaleY: scale * (flipY ? -1 : 1),
child: Image.asset(
ImagePaths.cloud,
opacity: AlwaysStoppedAnimation(.4 * opacity),
width: size * scale,
fit: BoxFit.fitWidth,
),
);
}