178 lines
4.2 KiB
Dart
178 lines
4.2 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;
|
|
}
|
|
_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(),
|
|
);
|
|
}
|
|
}
|