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 createState() => _SCSvgaAssetWidgetState(); } class _SCSvgaAssetWidgetState extends State with SingleTickerProviderStateMixin { static final Map _cache = {}; static final Map> _loadingTasks = >{}; 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 _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 _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(), ); } }