mnemo_cards/lib/features/card_flipper/flip_card.dart

143 lines
4.1 KiB
Dart
Raw Normal View History

2024-05-22 20:48:44 +00:00
import 'dart:async';
import 'package:flutter/material.dart';
import 'flip_side.dart';
import 'flip_card_controllers.dart';
import 'dart:math';
/// [FlipCard] A component that provides a flip card animation
class FlipCard extends StatefulWidget {
/// [controller] used to ccontrol the flip
final FlipCardController controller;
///[frontWidget] The Front side widget of the card
final Widget frontWidget;
///[backWidget] The Back side widget of the card
final Widget backWidget;
/// [onTapFlipping] When enabled, the card will flip automatically when touched.
final bool onTapFlipping;
/// [axis] The flip axis [Horizontal] and [Vertical]
final FlipAxis axis;
/// [rotateSide] The card rotate side
final RotateSide rotateSide;
/// [animationDuration] The amount of milliseconds a turn animation will take.
final Duration animationDuration;
/// [disableSplashEffect] The option for disable Inkwell widget's splash effect.
final bool disableSplashEffect;
/// [splashColor] The option for Inkwell widget's splashColor.
final Color? splashColor;
/// [focusColor] The option for Inkwell widget's focusColor.
final Color? focusColor;
const FlipCard({
Key? key,
this.focusColor,
this.splashColor,
this.onTapFlipping = false,
this.disableSplashEffect = false,
required this.frontWidget,
required this.backWidget,
required this.controller,
this.axis = FlipAxis.vertical,
required this.rotateSide,
this.animationDuration = const Duration(milliseconds: 800),
}) : super(key: key);
@override
FlipCardState createState() => FlipCardState();
}
class FlipCardState extends State<FlipCard> with TickerProviderStateMixin {
late AnimationController animationController;
double anglePlus = 0;
late bool _isFront;
StreamSubscription? _sub;
@override
void initState() {
super.initState();
_isFront = widget.controller.isFront;
animationController =
AnimationController(duration: widget.animationDuration, vsync: this);
if (!_isFront) {
animationController.value = 1.0;
anglePlus = pi;
}
_sub ??= widget.controller.asStream.distinct().listen((isFront) async {
if (this._isFront != isFront) {
if (animationController.isAnimating) return;
this._isFront = isFront;
await animationController
.forward(from: 0)
.then((value) => anglePlus = pi);
}
});
}
@override
void dispose() {
animationController.dispose();
_sub?.cancel();
_sub = null;
super.dispose();
}
@override
Widget build(BuildContext context) => AnimatedBuilder(
animation: animationController,
builder: (context, child) {
double piValue = 0.0;
if (widget.rotateSide == RotateSide.top ||
widget.rotateSide == RotateSide.left) {
piValue = pi;
} else {
piValue = -pi;
}
print('animation controller ${animationController.value}');
double angle = animationController.value * piValue;
late Matrix4 transform;
late Matrix4 transformForBack;
if (_isFront) angle += anglePlus;
if (widget.axis == FlipAxis.horizontal) {
transform = Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(angle);
transformForBack = Matrix4.identity()..rotateX(pi);
} else {
transform = Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(angle);
transformForBack = Matrix4.identity()..rotateY(pi);
}
final isFrontWidget = _isFrontWidget(angle.abs());
return Transform(
alignment: Alignment.center,
transform: transform,
child: isFrontWidget
? widget.frontWidget
: Transform(
transform: transformForBack,
alignment: Alignment.center,
child: widget.backWidget,
),
);
});
bool _isFrontWidget(double angle) {
const degrees90 = pi / 2;
const degrees270 = 3 * pi / 2;
return angle <= degrees90 || angle >= degrees270;
}
}