Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
129 lines
3.3 KiB
Dart
129 lines
3.3 KiB
Dart
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),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|