chatapp3-flutter/lib/ui_kit/widgets/svga/sc_svga_asset_widget.dart
NIGGER SLAYER 3b6ba4804e bug
2026-04-16 16:32:54 +08:00

180 lines
4.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_svga/flutter_svga.dart';
class SCSvgaAssetWidget extends StatefulWidget {
const SCSvgaAssetWidget({
super.key,
required this.assetPath,
this.width,
this.height,
this.active = true,
this.loop = false,
this.fit = BoxFit.contain,
this.filterQuality = FilterQuality.low,
this.allowDrawingOverflow = false,
this.fallback,
});
final String assetPath;
final double? width;
final double? height;
final bool active;
final bool loop;
final BoxFit fit;
final FilterQuality filterQuality;
final bool allowDrawingOverflow;
final Widget? fallback;
@override
State<SCSvgaAssetWidget> createState() => _SCSvgaAssetWidgetState();
}
class _SCSvgaAssetWidgetState extends State<SCSvgaAssetWidget>
with SingleTickerProviderStateMixin {
static final Map<String, MovieEntity> _cache = <String, MovieEntity>{};
static final Map<String, Future<MovieEntity>> _loadingTasks =
<String, Future<MovieEntity>>{};
late final SVGAAnimationController _controller;
String? _loadedAssetPath;
bool _hasError = false;
@override
void initState() {
super.initState();
_controller = SVGAAnimationController(vsync: this);
_loadAsset();
}
@override
void didUpdateWidget(covariant SCSvgaAssetWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.assetPath != widget.assetPath) {
_loadAsset();
return;
}
if (oldWidget.active != widget.active || oldWidget.loop != widget.loop) {
_syncPlayback(
restartIfActive: widget.active && oldWidget.active != widget.active,
);
}
}
Future<void> _loadAsset() async {
final assetPath = widget.assetPath;
setState(() {
_hasError = false;
});
try {
final movieEntity = await _obtainMovieEntity(assetPath);
if (!mounted || widget.assetPath != assetPath) {
return;
}
setState(() {
_loadedAssetPath = assetPath;
_controller.videoItem = movieEntity;
});
_syncPlayback(restartIfActive: true);
} catch (error) {
debugPrint('[SCSVGA] load failed asset=$assetPath error=$error');
if (!mounted || widget.assetPath != assetPath) {
return;
}
setState(() {
_hasError = true;
});
}
}
Future<MovieEntity> _obtainMovieEntity(String assetPath) async {
final cached = _cache[assetPath];
if (cached != null) {
return cached;
}
final loadingTask = _loadingTasks[assetPath];
if (loadingTask != null) {
return loadingTask;
}
final future = () async {
final entity = await SVGAParser.shared.decodeFromAssets(assetPath);
entity.autorelease = false;
_cache[assetPath] = entity;
return entity;
}();
_loadingTasks[assetPath] = future;
try {
return await future;
} finally {
_loadingTasks.remove(assetPath);
}
}
void _syncPlayback({bool restartIfActive = false}) {
if (_controller.videoItem == null) {
return;
}
if (!widget.active) {
_controller.stop();
_controller.reset();
return;
}
if (widget.loop) {
if (restartIfActive || !_controller.isAnimating) {
_controller.reset();
_controller.repeat();
}
return;
}
if (restartIfActive || !_controller.isAnimating) {
_controller.reset();
_controller.repeat(count: 1);
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_hasError) {
return _buildFallback();
}
final videoItem = _controller.videoItem;
if (videoItem == null || _loadedAssetPath != widget.assetPath) {
return _buildFallback();
}
return SizedBox(
width: widget.width,
height: widget.height,
child: IgnorePointer(
child: SVGAImage(
_controller,
fit: widget.fit,
clearsAfterStop: false,
filterQuality: widget.filterQuality,
allowDrawingOverflow: widget.allowDrawingOverflow,
),
),
);
}
Widget _buildFallback() {
return SizedBox(
width: widget.width,
height: widget.height,
child: widget.fallback ?? const SizedBox.shrink(),
);
}
}