Files
Qlyra/lib/frontend/widgets/composer_morph_icon.dart
sevenhill f74057ce9b
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
Rebrand application as Qlyra
2026-08-30 13:16:17 +03:00

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),
],
),
),
);
}
}