Files
holobooth/lib/share/widgets/portal_animation.dart
Alejandro Santiago cb6741cc8e chore: bump Flutter and Dart SDK to latest versions (#438)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

Changes:
- Bump min dart SDK to 2.19.0
- Bump Flutter version to 3.7.12
- Used Very Good Analysis 4.0.0
- Corrected new analyzer issues
- Dart format

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [X] 🗑️ Chore
2023-06-27 15:57:39 -07:00

258 lines
5.9 KiB
Dart

import 'dart:async';
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui';
import 'package:flame/cache.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart' hide Image;
import 'package:holobooth/assets/assets.dart';
enum PortalMode {
portrait,
landscape,
mobile;
PortalModeData get data {
switch (this) {
case PortalMode.portrait:
return PortalModeData(
texturePath: Assets.animations.mobilePortalSpritesheet.path,
textureSize: Vector2(650, 850),
thumbSize: Vector2(322, 378),
thumbOffset: Vector2(168, 104),
frameAmout: 90,
amountPerRow: 10,
);
case PortalMode.landscape:
return PortalModeData(
texturePath: Assets.animations.desktopPortalSpritesheet.path,
textureSize: Vector2(710, 750),
thumbSize: Vector2(498, 280),
thumbOffset: Vector2(100, 96),
frameAmout: 90,
amountPerRow: 10,
);
case PortalMode.mobile:
return PortalModeData(
texturePath: Assets.animations.smallPortalAnimation.path,
textureSize: Vector2(325, 425),
thumbSize: Vector2(162, 190),
thumbOffset: Vector2(84, 52),
frameAmout: 72,
amountPerRow: 12,
);
}
}
}
class PortalModeData {
PortalModeData({
required this.texturePath,
required this.textureSize,
required this.thumbSize,
required this.thumbOffset,
required this.frameAmout,
required this.amountPerRow,
});
final String texturePath;
final Vector2 textureSize;
final Vector2 thumbSize;
final Vector2 thumbOffset;
final int frameAmout;
final int amountPerRow;
}
class PortalAnimation extends StatefulWidget {
const PortalAnimation({
required this.mode,
required this.imageBytes,
required this.onComplete,
super.key,
});
final PortalMode mode;
final Uint8List imageBytes;
final VoidCallback onComplete;
@override
State<PortalAnimation> createState() => _PortalAnimationState();
}
class _PortalAnimationState extends State<PortalAnimation> {
late final _game = PortalGame(
mode: widget.mode,
imageBytes: widget.imageBytes,
onComplete: widget.onComplete,
);
@override
Widget build(BuildContext context) {
return GameWidget(game: _game);
}
}
@visibleForTesting
class PortalGame extends FlameGame {
PortalGame({
required this.mode,
required this.imageBytes,
required this.onComplete,
Future<Image> Function(Uint8List)? decodeImage,
Future<SpriteAnimation> Function(String, SpriteAnimationData)?
loadAnimation,
}) {
_decodeImageFromList = decodeImage ?? decodeImageFromList;
_loadAnimation = loadAnimation ?? loadSpriteAnimation;
}
final PortalMode mode;
final Uint8List imageBytes;
final VoidCallback onComplete;
late final Future<Image> Function(Uint8List) _decodeImageFromList;
late final Future<SpriteAnimation> Function(String, SpriteAnimationData)
_loadAnimation;
@override
Future<void> onLoad() async {
images = Images(prefix: '');
final data = mode.data;
final image = await _decodeImageFromList(imageBytes);
final thumb = Sprite(image);
final animation = await _loadAnimation(
data.texturePath,
SpriteAnimationData.sequenced(
amount: data.frameAmout,
amountPerRow: data.amountPerRow,
textureSize: data.textureSize,
stepTime: .05,
loop: false,
),
);
final frameComponent = SpriteAnimationComponent(
animation: animation,
size: data.textureSize,
position: -data.textureSize / 2,
);
add(frameComponent);
/// Play
final platImageSprite = await loadSprite(Assets.icons.playIcon.path);
animation.onComplete = () {
onComplete();
frameComponent.add(
ThumbComponent(
sprite: thumb,
data: data,
children: [
PlayComponent(sprite: platImageSprite),
],
),
);
};
final scaleX = size.x / data.textureSize.x;
final scaleY = size.x / data.textureSize.y;
camera
..zoom = math.min(scaleX, scaleY)
..followVector2(Vector2.zero());
}
@override
Color backgroundColor() => Colors.transparent;
}
@visibleForTesting
class ThumbComponent extends PositionComponent with HasPaint {
ThumbComponent({
required this.sprite,
required this.data,
super.children,
});
final Sprite sprite;
final PortalModeData data;
late final Rect clipRect;
late final Vector2 renderSize;
@override
Future<void> onLoad() async {
size = data.thumbSize.clone();
final imageSize = Vector2(
sprite.image.width.toDouble(),
sprite.image.height.toDouble(),
);
position = data.thumbOffset;
paint.color = Colors.white.withOpacity(0);
add(OpacityEffect.fadeIn(LinearEffectController(.5)));
clipRect = Rect.fromLTWH(
0,
0,
data.thumbSize.x,
data.thumbSize.y,
);
final rateX = data.thumbSize.x / imageSize.x;
final rateY = data.thumbSize.y / imageSize.y;
final rate = math.max(rateX, rateY);
renderSize = imageSize * rate;
}
@override
void render(Canvas canvas) {
canvas
..save()
..clipRect(clipRect);
sprite.render(
canvas,
size: renderSize,
position: data.thumbSize / 2,
anchor: Anchor.center,
overridePaint: paint,
);
canvas.restore();
}
}
@visibleForTesting
class PlayComponent extends SpriteComponent with ParentIsA<PositionComponent> {
PlayComponent({
required super.sprite,
});
@override
Future<void> onLoad() async {
final dimension = math.max(
parent.size.x,
parent.size.y,
);
size = Vector2.all(dimension * .22);
anchor = Anchor.center;
position = parent.size / 2;
}
}