bring flutter asset loading inline with cpp

basically does what it says, i'm also removing an example i used to check on things being garbage collected

Diffs=
c0411df0a bring flutter asset loading inline with cpp (#6135)

Co-authored-by: Gordon Hayes <pggordonhayes@gmail.com>
Co-authored-by: Maxwell Talbot <talbot.maxwell@gmail.com>
This commit is contained in:
mjtalbot
2023-10-24 14:37:30 +00:00
parent 50f9624195
commit 312a8b0577
23 changed files with 212 additions and 478 deletions

View File

@ -1 +1 @@
db984dfd3dd7d0ebf6b4e9cfc9fed28269d8c8ed
c0411df0a74b2aafa18526375cda19f6779a2354

View File

@ -1,6 +1,7 @@
## Upcoming
## 0.12.0
- Increase HTTP dependency range for Rive and Rive Common
- Update api for loading rive files, simplifying loading external images & fonts.
## 0.11.17

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,296 +0,0 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:rive/math.dart';
import 'package:rive/rive.dart';
class BasicTextCustomized extends StatefulWidget {
const BasicTextCustomized({Key? key}) : super(key: key);
@override
State<BasicTextCustomized> createState() => _BasicTextCustomizedState();
}
/// Basic example playing a Rive animation from a packaged asset.
class _BasicTextCustomizedState extends State<BasicTextCustomized>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 10));
RiveArtboardRenderer? _artboardRenderer;
List<Artboard> artboards = [];
late RiveFile file;
FontAsset? fontAsset;
randomFont() async {
final urls = [
'https://cdn.rive.app/runtime/flutter/IndieFlower-Regular.ttf',
'https://cdn.rive.app/runtime/flutter/comic-neue.ttf',
'https://cdn.rive.app/runtime/flutter/inter.ttf',
'https://cdn.rive.app/runtime/flutter/inter-tight.ttf',
'https://cdn.rive.app/runtime/flutter/josefin-sans.ttf',
'https://cdn.rive.app/runtime/flutter/send-flowers.ttf',
];
final res = await http.get(
// pick a random url from the list of fonts
Uri.parse(urls[Random().nextInt(urls.length)]),
);
await fontAsset?.decode(Uint8List.view(res.bodyBytes.buffer));
}
addArtboard() async {
unawaited(randomFont());
setState(() {
final artboard = file.mainArtboard.instance();
final controller = StateMachineController.fromArtboard(
artboard,
'State Machine 1',
)!;
artboard.addController(controller);
artboards.add(artboard);
while (artboards.length > 5) {
artboards.removeAt(0);
}
_artboardRenderer = RiveArtboardRenderer(
fit: BoxFit.cover,
alignment: Alignment.center,
artboards: artboards,
);
});
}
Future<void> _load() async {
// You need to manage adding the controller to the artboard yourself,
// unlike with the RiveAnimation widget that handles a lot of this logic
// for you by simply providing the state machine (or animation) name.
file = await RiveFile.asset(
'assets/trans_text.riv',
loadEmbeddedAssets: false,
assetLoader: CallbackAssetLoader(
(asset) async {
if (asset is FontAsset) {
setState(() {
fontAsset = asset;
});
return true;
}
return false;
},
),
);
final artboard = file.mainArtboard.instance();
final controller = StateMachineController.fromArtboard(
artboard,
'State Machine 1',
)!;
artboard.addController(controller);
artboards.add(artboard);
unawaited(randomFont());
setState(
() => _artboardRenderer = RiveArtboardRenderer(
fit: BoxFit.cover,
alignment: Alignment.center,
artboards: [artboard],
),
);
}
@override
void initState() {
super.initState();
_animationController.repeat();
_load();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
addArtboard();
},
child: Center(
child: _artboardRenderer == null
? const SizedBox()
: CustomPaint(
painter: RiveCustomPainter(
_artboardRenderer!,
repaint: _animationController,
),
child: const SizedBox.expand(), // use all the size available
),
),
),
);
}
}
class RiveCustomPainter extends CustomPainter {
final RiveArtboardRenderer artboardRenderer;
RiveCustomPainter(this.artboardRenderer, {super.repaint}) {
_lastTickTime = DateTime.now();
_elapsedTime = Duration.zero;
}
late DateTime _lastTickTime;
late Duration _elapsedTime;
void _calculateElapsedTime() {
final currentTime = DateTime.now();
_elapsedTime = currentTime.difference(_lastTickTime);
_lastTickTime = currentTime;
}
@override
void paint(Canvas canvas, Size size) {
_calculateElapsedTime(); // Calculate elapsed time since last tick.
// Advance the artboard by the elapsed time.
artboardRenderer.advance(_elapsedTime.inMicroseconds / 1000000);
final width = size.width / 3;
final height = size.height / 2;
final artboardSize = Size(width, height);
// First row
canvas.save();
artboardRenderer.render(canvas, artboardSize);
canvas.restore();
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
/// Keeps an `Artboard` instance and renders it to a `Canvas`.
///
/// This is a simplified version of the `RiveAnimation` widget and its
/// RenderObject
///
/// This accounts for the `fit` and `alignment` properties, similar to how
/// `RiveAnimation` works.
class RiveArtboardRenderer {
final List<Artboard> artboards;
final BoxFit fit;
final Alignment alignment;
RiveArtboardRenderer({
required this.fit,
required this.alignment,
required this.artboards,
});
void advance(double dt) {
for (var artboard in artboards) {
artboard.advance(dt, nested: true);
}
}
late final aabb =
AABB.fromValues(0, 0, artboards.first.width, artboards.first.height);
void render(Canvas canvas, Size size) {
_paint(canvas, aabb, size);
}
final _transform = Mat2D();
final _center = Mat2D();
void _paint(Canvas canvas, AABB bounds, Size size) {
for (var artboard in artboards) {
_paintArtboard(artboard, canvas, bounds, size);
bounds = AABB.fromValues(bounds.left - 100, bounds.top - 100,
bounds.right - 100, bounds.bottom - 100);
}
}
void _paintArtboard(
Artboard artboard, Canvas canvas, AABB bounds, Size size) {
final contentWidth = bounds[2] - bounds[0];
final contentHeight = bounds[3] - bounds[1];
if (contentWidth == 0 || contentHeight == 0) {
return;
}
final x = -1 * bounds[0] -
contentWidth / 2.0 -
(alignment.x * contentWidth / 2.0);
final y = -1 * bounds[1] -
contentHeight / 2.0 -
(alignment.y * contentHeight / 2.0);
var scaleX = 1.0;
var scaleY = 1.0;
canvas.save();
switch (fit) {
case BoxFit.fill:
scaleX = size.width / contentWidth;
scaleY = size.height / contentHeight;
break;
case BoxFit.contain:
final minScale =
min(size.width / contentWidth, size.height / contentHeight);
scaleX = scaleY = minScale;
break;
case BoxFit.cover:
final maxScale =
max(size.width / contentWidth, size.height / contentHeight);
scaleX = scaleY = maxScale;
break;
case BoxFit.fitHeight:
final minScale = size.height / contentHeight;
scaleX = scaleY = minScale;
break;
case BoxFit.fitWidth:
final minScale = size.width / contentWidth;
scaleX = scaleY = minScale;
break;
case BoxFit.none:
scaleX = scaleY = 1.0;
break;
case BoxFit.scaleDown:
final minScale =
min(size.width / contentWidth, size.height / contentHeight);
scaleX = scaleY = minScale < 1.0 ? minScale : 1.0;
break;
}
Mat2D.setIdentity(_transform);
_transform[4] = size.width / 2.0 + (alignment.x * size.width / 2.0);
_transform[5] = size.height / 2.0 + (alignment.y * size.height / 2.0);
Mat2D.scale(_transform, _transform, Vec2D.fromValues(scaleX, scaleY));
Mat2D.setIdentity(_center);
_center[4] = x;
_center[5] = y;
Mat2D.multiply(_transform, _transform, _center);
canvas.translate(
size.width / 2.0 + (alignment.x * size.width / 2.0),
size.height / 2.0 + (alignment.y * size.height / 2.0),
);
canvas.scale(scaleX, scaleY);
canvas.translate(x, y);
artboard.draw(canvas);
canvas.restore();
}
}

View File

@ -14,6 +14,8 @@ import 'package:http/http.dart' as http;
/// and provide them instantly.
///
/// See `custom_cached_asset_loading.dart` for an example of this.
///
/// See: https://help.rive.app/runtimes/loading-assets
class CustomAssetLoading extends StatefulWidget {
const CustomAssetLoading({Key? key}) : super(key: key);
@ -79,14 +81,18 @@ class _RiveRandomImageState extends State<_RiveRandomImage> {
Future<void> _loadFiles() async {
final imageFile = await RiveFile.asset(
'assets/asset.riv',
loadEmbeddedAssets: false,
'assets/image_out_of_band.riv',
assetLoader: CallbackAssetLoader(
(asset) async {
final res =
await http.get(Uri.parse('https://picsum.photos/1000/1000'));
await asset.decode(Uint8List.view(res.bodyBytes.buffer));
return true;
(asset, bytes) async {
// Replace image assets that are not embedded in the rive file
if (asset is ImageAsset && bytes == null) {
final res =
await http.get(Uri.parse('https://picsum.photos/500/500'));
await asset.decode(Uint8List.view(res.bodyBytes.buffer));
return true;
} else {
return false; // use default asset loading
}
},
),
);
@ -102,9 +108,23 @@ class _RiveRandomImageState extends State<_RiveRandomImage> {
return const Center(child: CircularProgressIndicator());
}
return RiveAnimation.direct(
_riveImageSampleFile!,
fit: BoxFit.cover,
return Stack(
children: [
RiveAnimation.direct(
_riveImageSampleFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
),
const Positioned(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'This example loads a random image dynamically and asynchronously.\n\nHover to zoom.',
style: TextStyle(color: Colors.black),
),
),
)
],
);
}
}
@ -128,25 +148,29 @@ class _RiveRandomFontState extends State<_RiveRandomFont> {
Future<void> _loadFiles() async {
final fontFile = await RiveFile.asset(
'assets/sampletext.riv',
loadEmbeddedAssets: false, // disable loading embedded assets.
'assets/acqua_text_out_of_band.riv',
assetLoader: CallbackAssetLoader(
(asset) async {
final urls = [
'https://cdn.rive.app/runtime/flutter/IndieFlower-Regular.ttf',
'https://cdn.rive.app/runtime/flutter/comic-neue.ttf',
'https://cdn.rive.app/runtime/flutter/inter.ttf',
'https://cdn.rive.app/runtime/flutter/inter-tight.ttf',
'https://cdn.rive.app/runtime/flutter/josefin-sans.ttf',
'https://cdn.rive.app/runtime/flutter/send-flowers.ttf',
];
(asset, bytes) async {
// Replace font assets that are not embedded in the rive file
if (asset is FontAsset && bytes == null) {
final urls = [
'https://cdn.rive.app/runtime/flutter/IndieFlower-Regular.ttf',
'https://cdn.rive.app/runtime/flutter/comic-neue.ttf',
'https://cdn.rive.app/runtime/flutter/inter.ttf',
'https://cdn.rive.app/runtime/flutter/inter-tight.ttf',
'https://cdn.rive.app/runtime/flutter/josefin-sans.ttf',
'https://cdn.rive.app/runtime/flutter/send-flowers.ttf',
];
final res = await http.get(
// pick a random url from the list of fonts
Uri.parse(urls[Random().nextInt(urls.length)]),
);
await asset.decode(Uint8List.view(res.bodyBytes.buffer));
return true;
final res = await http.get(
// pick a random url from the list of fonts
Uri.parse(urls[Random().nextInt(urls.length)]),
);
await asset.decode(Uint8List.view(res.bodyBytes.buffer));
return true;
} else {
return false; // use default asset loading
}
},
),
);
@ -162,9 +186,23 @@ class _RiveRandomFontState extends State<_RiveRandomFont> {
return const Center(child: CircularProgressIndicator());
}
return RiveAnimation.direct(
_riveFontSampleFile!,
fit: BoxFit.cover,
return Stack(
children: [
RiveAnimation.direct(
_riveFontSampleFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
),
const Positioned(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'This example loads a random font dynamically and asynchronously.\n\nClick to change drink.',
style: TextStyle(color: Colors.black),
),
),
)
],
);
}
}

View File

@ -14,6 +14,8 @@ import 'package:rive/rive.dart';
///
/// The example also shows how to swap out the assets multiple times by
/// keeping a reference to the asset and swapping it out.
///
/// See: https://help.rive.app/runtimes/loading-assets
class CustomCachedAssetLoading extends StatefulWidget {
const CustomCachedAssetLoading({Key? key}) : super(key: key);
@ -38,7 +40,7 @@ class _CustomCachedAssetLoadingState extends State<CustomCachedAssetLoading> {
Future<void> _warmUpCache() async {
final futures = <Future>[];
loadImage() async {
final res = await http.get(Uri.parse('https://picsum.photos/1000/1000'));
final res = await http.get(Uri.parse('https://picsum.photos/500/500'));
final body = Uint8List.view(res.bodyBytes.buffer);
final image = await ImageAsset.parseBytes(body);
if (image != null) {
@ -160,10 +162,9 @@ class __RiveRandomCachedImageState extends State<_RiveRandomCachedImage> {
Future<void> _loadRiveFile() async {
final imageFile = await RiveFile.asset(
'assets/asset.riv',
loadEmbeddedAssets: false,
'assets/image_out_of_band.riv',
assetLoader: CallbackAssetLoader(
(asset) async {
(asset, bytes) async {
if (asset is ImageAsset) {
asset.image = _imageCache[Random().nextInt(_imageCache.length)];
// Maintain a reference to the image asset
@ -188,9 +189,23 @@ class __RiveRandomCachedImageState extends State<_RiveRandomCachedImage> {
return Column(
children: [
Expanded(
child: RiveAnimation.direct(
_riveImageSampleFile!,
fit: BoxFit.cover,
child: Stack(
children: [
RiveAnimation.direct(
_riveImageSampleFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
),
const Positioned(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'This example caches images and swaps them out instantly.\n\nHover to zoom.',
style: TextStyle(color: Colors.black),
),
),
),
],
),
),
Padding(
@ -200,7 +215,7 @@ class __RiveRandomCachedImageState extends State<_RiveRandomCachedImage> {
_imageAsset?.image =
_imageCache[Random().nextInt(_imageCache.length)];
},
child: const Text('Random image asset'),
child: const Text('Random image'),
),
),
],
@ -234,10 +249,9 @@ class __RiveRandomCachedFontState extends State<_RiveRandomCachedFont> {
Future<void> _loadRiveFile() async {
final fontFile = await RiveFile.asset(
'assets/sampletext.riv',
loadEmbeddedAssets: false,
'assets/acqua_text_out_of_band.riv',
assetLoader: CallbackAssetLoader(
(asset) async {
(asset, bytes) async {
if (asset is FontAsset) {
asset.font = _fontCache[Random().nextInt(_fontCache.length)];
_fontAssets.add(asset);
@ -262,9 +276,23 @@ class __RiveRandomCachedFontState extends State<_RiveRandomCachedFont> {
return Column(
children: [
Expanded(
child: RiveAnimation.direct(
_riveFontSampleFile!,
fit: BoxFit.cover,
child: Stack(
children: [
RiveAnimation.direct(
_riveFontSampleFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
),
const Positioned(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'This example caches fonts and swaps them out instantly.\n\nClick to change drink.',
style: TextStyle(color: Colors.black),
),
),
),
],
),
),
Padding(
@ -275,7 +303,7 @@ class __RiveRandomCachedFontState extends State<_RiveRandomCachedFont> {
element?.font = _fontCache[Random().nextInt(_fontCache.length)];
}
},
child: const Text('Random font asset'),
child: const Text('Random font'),
),
),
],

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:rive_example/basic_text_customized.dart';
import 'package:rive_example/custom_asset_loading.dart';
import 'package:rive_example/custom_cached_asset_loading.dart';
@ -46,7 +45,6 @@ class RiveExampleApp extends StatefulWidget {
class _RiveExampleAppState extends State<RiveExampleApp> {
// Example animations
final _pages = [
const _Page('Basic Text Customized', BasicTextCustomized()),
const _Page('Simple Animation - Asset', SimpleAssetAnimation()),
const _Page('Simple Animation - Network', SimpleNetworkAnimation()),
const _Page('Play/Pause Animation', PlayPauseAnimation()),
@ -61,8 +59,8 @@ class _RiveExampleAppState extends State<RiveExampleApp> {
const _Page('Skinning Demo', SkinningDemo()),
const _Page('Animation Carousel', AnimationCarousel()),
const _Page('Basic Text', BasicText()),
const _Page('Custom Asset Loading', CustomAssetLoading()),
const _Page('Custom Cached Asset Loading', CustomCachedAssetLoading()),
const _Page('Asset Loading', CustomAssetLoading()),
const _Page('Cached Asset Loading', CustomCachedAssetLoading()),
const _Page('Event Open URL Button', EventOpenUrlButton()),
const _Page('Event Sounds', EventSounds()),
const _Page('Event Star Rating', EventStarRating()),

View File

@ -9,9 +9,9 @@ import 'package:rive/src/utilities/utilities.dart';
///
/// See [CallbackAssetLoader] and [LocalAssetLoader] for an example of how to
/// use this.
// ignore: one_member_abstracts
abstract class FileAssetLoader {
Future<bool> load(FileAsset asset);
bool isCompatible(FileAsset asset) => true;
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes);
}
/// Loads assets from Rive's CDN.
@ -23,10 +23,12 @@ class CDNAssetLoader extends FileAssetLoader {
CDNAssetLoader();
@override
bool isCompatible(FileAsset asset) => asset.cdnUuid.isNotEmpty;
@override
Future<bool> load(FileAsset asset) async {
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes) async {
// if the asset is embedded, or does not have a cdn uuid, do not attempt
// to load it
if (embeddedBytes != null || asset.cdnUuid.isEmpty) {
return false;
}
// TODO (Max): Do we have a URL builder?
// TODO (Max): We should aim to get loading errors exposed where
// possible, this includes failed network requests but also
@ -81,7 +83,11 @@ class LocalAssetLoader extends FileAssetLoader {
}) : _assetBundle = assetBundle ?? rootBundle;
@override
Future<bool> load(FileAsset asset) async {
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes) async {
// do not load embedded assets.
if (embeddedBytes != null) {
return false;
}
String? assetPath;
switch (asset.type) {
case Type.unknown:
@ -109,7 +115,6 @@ class LocalAssetLoader extends FileAssetLoader {
///
/// This callback will be triggered for any **referenced** assets.
///
/// Set [loadEmbeddedAssets] to false to disable loading embedded assets
///
/// Set [loadCdnAssets] to false to disable loading
/// assets from the Rive CDN.
@ -120,10 +125,9 @@ class LocalAssetLoader extends FileAssetLoader {
/// ```dart
/// final riveFile = await RiveFile.asset(
/// 'assets/asset.riv',
/// loadEmbeddedAssets: true,
/// loadCdnAssets: true,
/// assetLoader: CallbackAssetLoader(
/// (asset) async {
/// (asset, bytes) async {
/// final res =
/// await http.get(Uri.parse('https://picsum.photos/1000/1000'));
/// await asset.decode(Uint8List.view(res.bodyBytes.buffer));
@ -133,13 +137,13 @@ class LocalAssetLoader extends FileAssetLoader {
/// );
/// ```
class CallbackAssetLoader extends FileAssetLoader {
Future<bool> Function(FileAsset) callback;
Future<bool> Function(FileAsset asset, Uint8List? embeddedBytes) callback;
CallbackAssetLoader(this.callback);
@override
Future<bool> load(FileAsset asset) async {
return callback(asset);
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes) async {
return callback(asset, embeddedBytes);
}
}
@ -157,13 +161,10 @@ class FallbackAssetLoader extends FileAssetLoader {
FallbackAssetLoader(this.fileAssetLoaders);
@override
Future<bool> load(FileAsset asset) async {
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes) async {
for (var i = 0; i < fileAssetLoaders.length; i++) {
final resolver = fileAssetLoaders[i];
if (!resolver.isCompatible(asset)) {
continue;
}
final success = await resolver.load(asset);
final success = await resolver.load(asset, embeddedBytes);
if (success) {
return true;
}

View File

@ -13,38 +13,32 @@ abstract class FileAssetResolver {
class FileAssetImporter extends ImportStackObject {
final FileAssetLoader? assetLoader;
final FileAsset fileAsset;
final bool loadEmbeddedAssets;
Uint8List? embeddedBytes;
FileAssetImporter(
this.fileAsset,
this.assetLoader, {
this.loadEmbeddedAssets = true,
});
bool _contentsResolved = false;
this.assetLoader,
);
void resolveContents(FileAssetContents contents) {
if (loadEmbeddedAssets) {
_contentsResolved = true;
fileAsset.decode(contents.bytes);
}
embeddedBytes = contents.bytes;
}
@override
bool resolve() {
if (!_contentsResolved) {
// try to get them out of band
assetLoader?.load(fileAsset).then((loaded) {
// allow our loader to load the file asset.
assetLoader?.load(fileAsset, embeddedBytes).then((loaded) {
if (!loaded && embeddedBytes != null) {
fileAsset.decode(embeddedBytes!);
} else if (!loaded) {
// TODO: improve error logging
if (!loaded) {
printDebugMessage(
'''Rive asset (${fileAsset.name}) was not able to load:
printDebugMessage(
'''Rive asset (${fileAsset.name}) was not able to load:
- Unique file name: ${fileAsset.uniqueFilename}
- Asset id: ${fileAsset.id}''',
);
}
});
}
);
}
});
return super.resolve();
}
}

View File

@ -5,9 +5,6 @@ class StateMachineBool extends StateMachineBoolBase {
@override
void valueChanged(bool from, bool to) {}
@override
void publicChanged(bool from, bool to) {}
@override
bool isValidType<T>() => T == bool;
}

View File

@ -18,7 +18,4 @@ abstract class StateMachineInput extends StateMachineInputBase {
bool isValidType<T>() => false;
}
class _StateMachineUnknownInput extends StateMachineInput {
@override
void publicChanged(bool from, bool to) {}
}
class _StateMachineUnknownInput extends StateMachineInput {}

View File

@ -4,7 +4,4 @@ export 'package:rive/src/generated/animation/state_machine_number_base.dart';
class StateMachineNumber extends StateMachineNumberBase {
@override
void valueChanged(double from, double to) {}
@override
void publicChanged(bool from, bool to) {}
}

View File

@ -4,9 +4,6 @@ export 'package:rive/src/generated/animation/state_machine_trigger_base.dart';
class StateMachineTrigger extends StateMachineTriggerBase {
void fire() {}
@override
void publicChanged(bool from, bool to) {}
@override
bool isValidType<T>() => T == bool;
}

View File

@ -113,6 +113,11 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
return null;
}
/// Find all components of a specific type.
Iterable<T> components<T>() {
return _components.whereType<T>();
}
@override
Artboard get artboard => this;

View File

@ -117,7 +117,7 @@ class NestedArtboard extends NestedArtboardBase {
}
bool advance(double elapsedSeconds) {
if (mountedArtboard == null) {
if (mountedArtboard == null || isCollapsed) {
return false;
}

View File

@ -30,7 +30,6 @@ import 'package:rive/src/rive_core/animation/state_machine_listener.dart';
import 'package:rive/src/rive_core/animation/state_transition.dart';
import 'package:rive/src/rive_core/artboard.dart';
import 'package:rive/src/rive_core/assets/file_asset.dart';
import 'package:rive/src/rive_core/assets/file_asset_contents.dart';
import 'package:rive/src/rive_core/assets/image_asset.dart';
import 'package:rive/src/rive_core/backboard.dart';
import 'package:rive/src/rive_core/component.dart';
@ -154,9 +153,8 @@ class RiveFile {
RiveFile._(
BinaryReader reader,
this.header,
this._assetLoader, {
bool loadEmbeddedAssets = true,
}) {
this._assetLoader,
) {
/// Property fields table of contents
final propertyToField = _propertyToFieldLookup(header);
@ -175,12 +173,7 @@ class RiveFile {
}
continue;
}
// TODO: Question (Max): two options, either tell the fileAssetImporter,
// or simply skip the object. I think we should skip the object.
if (!loadEmbeddedAssets && object is FileAssetContentsBase) {
// suppress importing embedded assets
continue;
}
ImportStackObject? stackObject;
var stackType = object.coreType;
switch (object.coreType) {
@ -247,7 +240,6 @@ class RiveFile {
stackObject = FileAssetImporter(
object as FileAsset,
_assetLoader,
loadEmbeddedAssets: loadEmbeddedAssets,
);
stackType = FileAssetBase.typeKey;
break;
@ -322,10 +314,10 @@ class RiveFile {
///
/// Set [loadCdnAssets] to `false` to disable loading assets from the CDN.
///
/// Set [loadEmbeddedAssets] to `false` to disable loading embedded assets.
///
/// Whether an assets is embedded/cdn/referenced is determined by the Rive
/// file - as set in the editor.
///
/// Loading assets documentation: https://help.rive.app/runtimes/loading-assets
/// {@endtemplate}
///
/// Will throw [RiveFormatErrorException] if data is malformed. Will throw
@ -335,7 +327,6 @@ class RiveFile {
@Deprecated('Use `assetLoader` instead.') FileAssetResolver? assetResolver,
FileAssetLoader? assetLoader,
bool loadCdnAssets = true,
bool loadEmbeddedAssets = true,
}) {
var reader = BinaryReader(bytes);
return RiveFile._(
@ -347,7 +338,6 @@ class RiveFile {
if (loadCdnAssets) CDNAssetLoader(),
],
),
loadEmbeddedAssets: loadEmbeddedAssets,
);
}
@ -365,7 +355,6 @@ class RiveFile {
ByteData bytes, {
FileAssetLoader? assetLoader,
bool loadCdnAssets = true,
bool loadEmbeddedAssets = true,
}) async {
if (!_initializedText) {
/// If the file looks like needs the text runtime, let's load it.
@ -378,7 +367,6 @@ class RiveFile {
bytes,
assetLoader: assetLoader,
loadCdnAssets: loadCdnAssets,
loadEmbeddedAssets: loadEmbeddedAssets,
);
}
@ -396,7 +384,6 @@ class RiveFile {
AssetBundle? bundle,
FileAssetLoader? assetLoader,
bool loadCdnAssets = true,
bool loadEmbeddedAssets = true,
}) async {
final bytes = await (bundle ?? rootBundle).load(
bundleKey,
@ -406,7 +393,6 @@ class RiveFile {
bytes,
assetLoader: assetLoader,
loadCdnAssets: loadCdnAssets,
loadEmbeddedAssets: loadEmbeddedAssets,
);
}
@ -424,7 +410,6 @@ class RiveFile {
@Deprecated('Use `assetLoader` instead.') FileAssetResolver? assetResolver,
FileAssetLoader? assetLoader,
bool loadCdnAssets = true,
bool loadEmbeddedAssets = true,
}) async {
final res = await http.get(Uri.parse(url), headers: headers);
final bytes = ByteData.view(res.bodyBytes.buffer);
@ -432,7 +417,6 @@ class RiveFile {
bytes,
assetLoader: assetLoader,
loadCdnAssets: loadCdnAssets,
loadEmbeddedAssets: loadEmbeddedAssets,
);
}
@ -446,13 +430,11 @@ class RiveFile {
String path, {
FileAssetLoader? assetLoader,
bool loadCdnAssets = true,
bool loadEmbeddedAssets = true,
}) async {
final bytes = await localFileBytes(path);
return _initTextAndImport(
ByteData.view(bytes!.buffer),
assetLoader: assetLoader,
loadEmbeddedAssets: loadEmbeddedAssets,
loadCdnAssets: loadCdnAssets,
);
}

View File

@ -1,5 +1,5 @@
name: rive
version: 0.11.17
version: 0.12.0
homepage: https://rive.app
description: Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.
repository: https://github.com/rive-app/rive-flutter

View File

@ -46,25 +46,26 @@ void main() {
);
// each artboard adds file asset referencer.
var count = 20;
var count = 19;
while (count-- > 0) {
riveFile.artboards.first.instance();
}
final image = riveFile.artboards.first.component<Image>(assetName);
final asset = image!.asset!;
expect(asset.fileAssetReferencers.length, 21);
await Future<void>.delayed(const Duration(milliseconds: 10));
expect(asset.fileAssetReferencers.length, 20);
await Future<void>.delayed(const Duration(milliseconds: 100));
// ok, kinda lame, but the above allows garbage collection to kick in
// which will remove referencers, its not really deterministic though
expect(
asset.fileAssetReferencers.length < 5,
true,
reason: "Expected ${asset.fileAssetReferencers.length} < 5",
);
},
// skipping because it only works when we run this test directly,
// not when running it as part of other tests.
// skipping because it does not work, you can see things get
// finalized but this does not consistently happen in tests.
skip: true,
);
});

View File

@ -35,22 +35,6 @@ void main() {
verifyNever(() => mockHttpClient.openUrl(any(), any()));
});
test('Disabling embedded assets also does not hit a url', () async {
final mockHttpClient = getMockHttpClient();
await HttpOverrides.runZoned(() async {
final riveBytes = loadFile('assets/sample_image.riv');
RiveFile.import(
riveBytes,
loadEmbeddedAssets: false,
);
}, createHttpClient: (_) => mockHttpClient);
// by default we try to make a network request
verifyNever(() => mockHttpClient.openUrl(any(), any()));
});
test('Disabling cdn also does not hit a url', () async {
final mockHttpClient = getMockHttpClient();
@ -59,7 +43,6 @@ void main() {
runZonedGuarded(() {
RiveFile.import(
riveBytes,
loadEmbeddedAssets: false,
loadCdnAssets: false,
);
}, (error, stack) {
@ -76,16 +59,18 @@ void main() {
verifyNever(() => mockHttpClient.openUrl(any(), any()));
// by default we try to check for assets
});
test('test importing rive file, make sure we get a good callback',
() async {
// lets just return an image
final riveBytes = loadFile('assets/sample_image.riv');
final imageBytes = loadFile('assets/file.png');
final parameters = [];
RiveFile.import(riveBytes, loadEmbeddedAssets: false,
assetLoader: CallbackAssetLoader(
(asset) async {
parameters.add(asset);
final assets = [];
final byteList = [];
RiveFile.import(riveBytes, assetLoader: CallbackAssetLoader(
(asset, bytes) async {
assets.add(asset);
byteList.add(bytes);
await asset.decode(Uint8List.sublistView(
imageBytes,
));
@ -93,7 +78,7 @@ void main() {
},
));
final asset = parameters.first;
final asset = assets.first;
expect(asset is ImageAsset, true);
final fileAsset = asset as ImageAsset;
@ -101,7 +86,38 @@ void main() {
expect(fileAsset.type, Type.image);
expect(fileAsset.name, assetName);
expect(fileAsset.assetId, 42981);
expect(fileAsset.id, -1);
expect(byteList.first.length, 202385);
});
test('test we load embedded assets if loaders are not provided', () async {
// lets just return an image
final riveBytes = loadFile('assets/sample_image.riv');
final assets = [];
RiveFile.import(riveBytes, assetLoader: CallbackAssetLoader(
(asset, bytes) async {
assets.add(asset);
return false;
},
));
final asset = assets.first;
expect(asset is ImageAsset, true);
final fileAsset = asset as ImageAsset;
expect(fileAsset.extension, Extension.png);
expect(fileAsset.type, Type.image);
expect(fileAsset.name, assetName);
expect(fileAsset.assetId, 42981);
// file asset will not be loaded
expect(fileAsset.image, null);
expect(fileAsset.id, -1);
await Future<void>.delayed(const Duration(milliseconds: 100));
expect(fileAsset.image != null, true);
});
test('Make sure the image gets the dimensions once the image is loaded',
@ -113,9 +129,8 @@ void main() {
final file = RiveFile.import(
riveBytes,
loadEmbeddedAssets: false,
assetLoader: CallbackAssetLoader(
(asset) async {
(asset, bytes) async {
await asset.decode(Uint8List.sublistView(
imageBytes,
));
@ -159,30 +174,12 @@ void main() {
)).called(1);
});
test('Disabling embedded assets also hits a url', () async {
await HttpOverrides.runZoned(() async {
final riveBytes = loadFile('assets/cdn_image.riv');
runZonedGuarded(() {
RiveFile.import(
riveBytes,
loadEmbeddedAssets: false,
);
}, (error, stack) {
print('what?');
});
}, createHttpClient: (_) => mockHttpClient);
// by default we try to make a network request
verify(() => mockHttpClient.openUrl(any(), any())).called(1);
});
test('Disabling cdn will mean no url hit', () async {
await HttpOverrides.runZoned(() async {
final riveBytes = loadFile('assets/cdn_image.riv');
RiveFile.import(
riveBytes,
loadEmbeddedAssets: false,
loadCdnAssets: false,
);
}, createHttpClient: (_) => mockHttpClient);
@ -199,10 +196,8 @@ void main() {
final parameters = [];
await HttpOverrides.runZoned(() async {
final riveBytes = loadFile('assets/cdn_image.riv');
RiveFile.import(riveBytes, loadEmbeddedAssets: false,
assetLoader: CallbackAssetLoader(
(asset) async {
RiveFile.import(riveBytes, assetLoader: CallbackAssetLoader(
(asset, bytes) async {
parameters.add(asset);
await asset.decode(Uint8List.sublistView(
imageBytes,
@ -226,9 +221,8 @@ void main() {
await HttpOverrides.runZoned(() async {
final riveBytes = loadFile('assets/cdn_image.riv');
RiveFile.import(riveBytes, loadEmbeddedAssets: false,
assetLoader: CallbackAssetLoader(
(asset) async {
RiveFile.import(riveBytes, assetLoader: CallbackAssetLoader(
(asset, bytes) async {
parameters.add(asset);
return false;
},