feat: анимации с реанимации

This commit is contained in:
Jganenokk
2026-08-06 19:21:45 +07:00
parent 81159bac60
commit 79c743e86a
50 changed files with 3247 additions and 925 deletions
@@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:material_symbols_icons/symbols.dart';
enum ComposerAction { mic, videocam, send }
const Map<(ComposerAction, ComposerAction), String> _morphs = {
(ComposerAction.mic, ComposerAction.videocam):
'assets/lottie/ic_mic_to_videocam.json',
(ComposerAction.videocam, ComposerAction.mic):
'assets/lottie/ic_videocam_to_mic.json',
(ComposerAction.mic, ComposerAction.send):
'assets/lottie/ic_mic_to_send.json',
(ComposerAction.videocam, ComposerAction.send):
'assets/lottie/ic_videocam_to_send.json',
(ComposerAction.send, ComposerAction.mic):
'assets/lottie/ic_send_to_mic.json',
(ComposerAction.send, ComposerAction.videocam):
'assets/lottie/ic_send_to_videocam.json',
};
IconData composerActionIcon(ComposerAction action) => switch (action) {
ComposerAction.mic => Symbols.mic,
ComposerAction.videocam => Symbols.videocam,
ComposerAction.send => Symbols.send,
};
class ComposerMorphIcon extends StatefulWidget {
const ComposerMorphIcon({
super.key,
required this.action,
required this.color,
this.size = 24,
this.duration = const Duration(milliseconds: 400),
});
final ComposerAction action;
final Color color;
final double size;
final Duration duration;
@override
State<ComposerMorphIcon> createState() => _ComposerMorphIconState();
}
class _ComposerMorphIconState extends State<ComposerMorphIcon>
with SingleTickerProviderStateMixin {
static bool _warmed = false;
late final AnimationController _controller = AnimationController(
vsync: this,
duration: widget.duration,
);
String? _playing;
@override
void initState() {
super.initState();
_controller.addStatusListener(_onStatus);
_warmUp();
}
void _warmUp() {
if (_warmed) return;
_warmed = true;
for (final asset in _morphs.values.toSet()) {
AssetLottie(asset).load();
}
}
void _onStatus(AnimationStatus status) {
if (status != AnimationStatus.completed) return;
if (_playing == null || !mounted) return;
setState(() => _playing = null);
}
@override
void didUpdateWidget(ComposerMorphIcon oldWidget) {
super.didUpdateWidget(oldWidget);
_controller.duration = widget.duration;
if (widget.action == oldWidget.action) return;
final asset = _morphs[(oldWidget.action, widget.action)];
if (asset == null) {
if (_playing != null) setState(() => _playing = null);
return;
}
_controller.stop();
_playing = null;
_controller.value = 0;
setState(() => _playing = asset);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final asset = _playing;
if (asset == null) {
return Icon(
composerActionIcon(widget.action),
color: widget.color,
size: widget.size,
weight: 400,
);
}
return SizedBox.square(
dimension: widget.size,
child: Lottie.asset(
asset,
controller: _controller,
fit: BoxFit.contain,
delegates: LottieDelegates(
values: [ValueDelegate.color(const ['**'], value: widget.color)],
),
),
);
}
}