From b96d123573ee9dc6b0ef8bbd7980e596fbc5b1be Mon Sep 17 00:00:00 2001 From: Carl de Billy Date: Sun, 29 Nov 2020 22:52:46 -0500 Subject: [PATCH] feat(lottie): Added the ability of LottieVisualSource to download its content and assign it locally. This is required for a feature in a coming commit, adding theming support to lottie files. --- .../LottieVisualSource.Android.cs | 101 +++--- .../Uno.UI.Lottie/LottieVisualSource.cs | 228 +------------ .../LottieVisualSource.iOSmacOS.cs | 2 +- .../Uno.UI.Lottie/LottieVisualSource.wasm.cs | 27 +- .../Uno.UI.Lottie/LottieVisualSourceBase.cs | 305 ++++++++++++++++++ .../LottieVisualSourceBase.net.cs | 12 + src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj | 1 + .../Lottie/SampleLottieAnimation.xaml.cs | 1 + 8 files changed, 405 insertions(+), 272 deletions(-) create mode 100644 src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.cs create mode 100644 src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.net.cs diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.Android.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.Android.cs index e2aee7cc90e6..1fae36954773 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.Android.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.Android.cs @@ -1,64 +1,57 @@ using System; +using System.Threading; using Android.Animation; using Android.Widget; using Com.Airbnb.Lottie; using Windows.Foundation; using Windows.UI.Xaml.Controls; using Android.Views; +using Uno.Disposables; using Uno.UI; using ViewHelper = Uno.UI.ViewHelper; +using System.Threading.Tasks; namespace Microsoft.Toolkit.Uwp.UI.Lottie { - partial class LottieVisualSource + partial class LottieVisualSourceBase { - private LottieAnimationView _animation; + private LottieAnimationView? _animation; - private LottieListener _listener; + private LottieListener? _listener; private class LottieListener : AnimatorListenerAdapter { - private readonly LottieVisualSource _lottieVisualSource; + private readonly LottieVisualSourceBase _lottieVisualSource; - public LottieListener(LottieVisualSource lottieVisualSource) + public LottieListener(LottieVisualSourceBase lottieVisualSource) { _lottieVisualSource = lottieVisualSource; } - public override void OnAnimationCancel(Animator animation) - { - _lottieVisualSource.SetIsPlaying(false); - } + public override void OnAnimationCancel(Animator? animation) => _lottieVisualSource.SetIsPlaying(false); - public override void OnAnimationEnd(Animator animation) - { - _lottieVisualSource.SetIsPlaying(false); - } + public override void OnAnimationEnd(Animator? animation) => _lottieVisualSource.SetIsPlaying(false); - public override void OnAnimationPause(Animator animation) - { - _lottieVisualSource.SetIsPlaying(false); - } + public override void OnAnimationPause(Animator? animation) => _lottieVisualSource.SetIsPlaying(false); - public override void OnAnimationResume(Animator animation) - { - _lottieVisualSource.SetIsPlaying(true); - } + public override void OnAnimationResume(Animator? animation) => _lottieVisualSource.SetIsPlaying(true); - public override void OnAnimationStart(Animator animation) - { - _lottieVisualSource.SetIsPlaying(true); - } + public override void OnAnimationStart(Animator? animation) => _lottieVisualSource.SetIsPlaying(true); } public bool UseHardwareAcceleration { get; set; } = true; - private Uri _lastSource; + private Uri? _lastSource; private (double fromProgress, double toProgress, bool looped)? _playState; - partial void InnerUpdate() + private readonly SerialDisposable _animationDataSubscription = new SerialDisposable(); + + async Task InnerUpdate(CancellationToken ct) { - var player = _player; + if (!(_player is { } player)) + { + return; + } if (_animation == null) { @@ -69,29 +62,38 @@ partial void InnerUpdate() _animation.AddAnimatorListener(_listener); - SetProperties(); + await SetProperties(); + // Add the player after player.AddView(_animation); } else { - SetProperties(); + await SetProperties(); } - void SetProperties() + async Task SetProperties() { - var source = UriSource; - if(_lastSource == null || !_lastSource.Equals(source)) + var sourceUri = UriSource; + if(_lastSource == null || !_lastSource.Equals(sourceUri)) { - _lastSource = source; + _lastSource = sourceUri; - if (TryLoadEmbeddedJson(source, out var json)) + if ((await TryLoadDownloadJson(sourceUri, ct)) is { } jsonStream) { - _animation.SetAnimationFromJson(json, source.OriginalString); + var cacheKey = sourceUri.OriginalString; + _animationDataSubscription.Disposable = null; + _animationDataSubscription.Disposable = + LoadAndObserveAnimationData(jsonStream, cacheKey, OnJsonChanged); + + void OnJsonChanged(string updatedJson, string updatedCacheKey) + { + _animation.SetAnimationFromJson(updatedJson, updatedCacheKey); + } } else { - var path = source?.PathAndQuery ?? ""; + var path = sourceUri?.PathAndQuery ?? ""; if (path.StartsWith("/")) { path = path.Substring(1); @@ -137,7 +139,7 @@ partial void InnerMeasure(Size size) { var physicalSize = size.LogicalToPhysicalPixels(); - _animation.Measure( + _animation?.Measure( ViewHelper.MakeMeasureSpec((int)physicalSize.Width, MeasureSpecMode.AtMost), ViewHelper.MakeMeasureSpec((int)physicalSize.Height, MeasureSpecMode.AtMost) ); @@ -145,9 +147,12 @@ partial void InnerMeasure(Size size) private void SetDuration() { - var duration = TimeSpan.FromMilliseconds(_animation.Duration); - _player?.SetValue(AnimatedVisualPlayer.DurationProperty, duration); - _player?.SetValue(AnimatedVisualPlayer.IsAnimatedVisualLoadedProperty, duration > TimeSpan.Zero); + if (_animation is { } animation) + { + var duration = TimeSpan.FromMilliseconds(animation.Duration); + _player?.SetValue(AnimatedVisualPlayer.DurationProperty, duration); + _player?.SetValue(AnimatedVisualPlayer.IsAnimatedVisualLoadedProperty, duration > TimeSpan.Zero); + } } public void Play(double fromProgress, double toProgress, bool looped) @@ -158,14 +163,18 @@ public void Play(double fromProgress, double toProgress, bool looped) return; } SetIsPlaying(true); + if (_animation is { } animation) + { #if __ANDROID_26__ - _animation.RepeatCount = looped ? ValueAnimator.Infinite : 0; // Repeat count doesn't include first time. + animation.RepeatCount = + looped ? ValueAnimator.Infinite : 0; // Repeat count doesn't include first time. #else - _animation.Loop(looped); + animation.Loop(looped); #endif - _animation.SetMinProgress((float)fromProgress); - _animation.SetMaxProgress((float)toProgress); - _animation.PlayAnimation(); + animation.SetMinProgress((float)fromProgress); + animation.SetMaxProgress((float)toProgress); + animation.PlayAnimation(); + } } public void Stop() diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.cs index 8e7259edf5a5..c9f14549cbe0 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.cs @@ -1,226 +1,20 @@ using System; -using System.Buffers.Text; -using System.Linq; -using System.Reflection; -using System.Text; +using System.IO; +using System.Threading; using System.Threading.Tasks; -using Uno; -using Windows.Foundation; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media; -using Microsoft.Extensions.Logging; -using Uno.Extensions; -using Uno.Extensions.Specialized; -using Uno.Logging; -using Uno.UI; -using Windows.UI.Composition; +using Windows.Storage.Streams; +using Windows.UI.Xaml.Data; +using Uno.Disposables; namespace Microsoft.Toolkit.Uwp.UI.Lottie { - public partial class LottieVisualSource : DependencyObject, IAnimatedVisualSource + [Bindable] + public partial class LottieVisualSource : LottieVisualSourceBase { - private AnimatedVisualPlayer _player; - - public static DependencyProperty UriSourceProperty { get ; } = DependencyProperty.Register( - "UriSource", - typeof(Uri), - typeof(LottieVisualSource), - new FrameworkPropertyMetadata( - default(Uri), - FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, - OnUriSourceChanged)); - - public Uri UriSource - { - get => (Uri)GetValue(UriSourceProperty); - set => SetValue(UriSourceProperty, value); - } - - public static DependencyProperty OptionsProperty { get ; } = DependencyProperty.Register( - "Options", typeof(LottieVisualOptions), typeof(LottieVisualSource), new FrameworkPropertyMetadata(LottieVisualOptions.None)); - - [NotImplemented] - public LottieVisualOptions Options - { - get => (LottieVisualOptions)GetValue(OptionsProperty); - set => SetValue(OptionsProperty, value); - } - - [NotImplemented] - public static LottieVisualSource CreateFromString(string uri) - { - throw new NotImplementedException(); - } - -#if HAS_UNO_WINUI - [NotImplemented] - public IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor, out object diagnostics) - { - throw new NotImplementedException(); - } -#endif - - private static void OnUriSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - if (sender is LottieVisualSource source) - { - source.Update(source._player); - } - } - - public Task SetSourceAsync(Uri sourceUri) - { - UriSource = sourceUri; - - // TODO: this method should not return before the animation is ready. - - return Task.CompletedTask; - } - - -#if !(__WASM__ || __ANDROID__ || __IOS__ || __MACOS__) - - public void Play(double fromProgress, double toProgress, bool looped) - { - throw new NotImplementedException(); - } - - public void Stop() - { - throw new NotImplementedException(); - } - - public void Pause() - { - throw new NotImplementedException(); - } - - public void Resume() - { - throw new NotImplementedException(); - } - - public void SetProgress(double progress) - { - throw new NotImplementedException(); - } - - public void Load() - { - throw new NotImplementedException(); - } - - public void Unload() - { - throw new NotImplementedException(); - } - - public Size Measure(Size availableSize) - { - throw new NotImplementedException(); - } - - private readonly Size CompositionSize = default; +#if !DEBUG + protected override bool IsPayloadNeedsToBeUpdated => false; // load the animation using url if possible +#else + protected override bool IsPayloadNeedsToBeUpdated => false; #endif - - public void Update(AnimatedVisualPlayer player) - { - _player = player; - if (_player != null) - { - InnerUpdate(); - } - } - - partial void InnerUpdate(); - - private void SetIsPlaying(bool isPlaying) => _player?.SetValue(AnimatedVisualPlayer.IsPlayingProperty, isPlaying); - - Size IAnimatedVisualSource.Measure(Size availableSize) - { - var compositionSize = CompositionSize; - if (compositionSize == default) - { - return default; - } - - var stretch = _player.Stretch; - - if (stretch == Stretch.None) - { - return compositionSize; - } - - var availableWidth = availableSize.Width; - var availableHeight = availableSize.Height; - - var resultSize = availableSize; - - if (double.IsInfinity(availableWidth)) - { - if (double.IsInfinity(availableHeight)) - { - return compositionSize; - } - - resultSize = new Size(availableHeight * compositionSize.Width / compositionSize.Height, availableHeight); - } - - if (double.IsInfinity(availableHeight)) - { - resultSize = new Size(availableWidth, availableWidth * compositionSize.Height / compositionSize.Width); - } - - InnerMeasure(resultSize); - - return resultSize; - } - - partial void InnerMeasure(Size size); - - private bool TryLoadEmbeddedJson(Uri uri, out string json) - { - if (uri.Scheme != "embedded") - { - json = null; - return false; - } - - var assemblyName = uri.Host; - - Assembly assembly; - if (assemblyName == ".") - { - assembly = Application.Current.GetType().Assembly; - } - else - { - assembly = Assembly.Load(assemblyName); - } - - if (assembly == null) - { - json = null; - return false; - } - - var resourceName = uri.AbsolutePath.Substring(1).Replace("(assembly)", assembly.GetName().Name); - using var stream = assembly.GetManifestResourceStream(resourceName); - if (stream == null) - { - if (this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn($"Unable to find embedded resource named '{resourceName}' to load."); - } - json = null; - return false; - } - - var bytes = new byte[(int)stream.Length]; - stream.Read(bytes, 0, bytes.Length); - json = Encoding.UTF8.GetString(bytes); - return true; - } } } diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.iOSmacOS.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.iOSmacOS.cs index ea79b0c12224..c87d01cc7e3d 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.iOSmacOS.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.iOSmacOS.cs @@ -22,7 +22,7 @@ partial class LottieVisualSource public bool UseHardwareAcceleration { get; set; } = true; - private Uri _lastSource; + private Uri? _lastSource; private (double fromProgress, double toProgress, bool looped)? _playState; partial void InnerUpdate() diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.wasm.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.wasm.cs index fc2cb8e68203..a36f793b3ed2 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.wasm.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.wasm.cs @@ -15,10 +15,10 @@ public partial class LottieVisualSource { private static readonly string UNO_BOOTSTRAP_APP_BASE = global::System.Environment.GetEnvironmentVariable(nameof(UNO_BOOTSTRAP_APP_BASE)); - private AnimatedVisualPlayer _initializedPlayer; + private AnimatedVisualPlayer? _initializedPlayer; private bool _isPlaying; private Size _compositionSize = new Size(0, 0); - private Uri _loadedEmbeddedUri; + private Uri? _loadedEmbeddedUri; private (double fromProgress, double toProgress, bool looped)? _playState; private bool _isUpdating; @@ -28,8 +28,13 @@ partial void InnerUpdate() var player = _player; if(_initializedPlayer != player) { - player.RegisterHtmlCustomEventHandler("lottie_state", OnStateChanged, isDetailJson: false); _initializedPlayer = player; + player?.RegisterHtmlCustomEventHandler("lottie_state", OnStateChanged, isDetailJson: false); + } + + if (player == null) + { + return; } string[] js; @@ -112,6 +117,11 @@ private void ApplyPlayState() private void ParseStateString(string stateString) { + if (_player == null) + { + return; + } + var parts = stateString.Split('|'); double.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var w); double.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var h); @@ -145,6 +155,7 @@ public void Play(double fromProgress, double toProgress, bool looped) { return; } + var js = new[] { "Uno.UI.Lottie.play(", @@ -164,10 +175,12 @@ public void Play(double fromProgress, double toProgress, bool looped) void IAnimatedVisualSource.Stop() { _playState = null; + if (_player == null) { return; } + var js = new[] { "Uno.UI.Lottie.stop(", @@ -184,6 +197,7 @@ void IAnimatedVisualSource.Pause() { return; } + var js = new[] { "Uno.UI.Lottie.pause(", @@ -217,6 +231,7 @@ public void SetProgress(double progress) { return; } + var js = new[] { "Uno.UI.Lottie.setProgress(", @@ -254,11 +269,7 @@ void IAnimatedVisualSource.Load() void IAnimatedVisualSource.Unload() { - if (_player == null) - { - return; - } - if (!_isPlaying) + if (_player == null || !_isPlaying) { return; } diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.cs new file mode 100644 index 000000000000..42381efd709c --- /dev/null +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.cs @@ -0,0 +1,305 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Storage; +using Windows.Storage.Streams; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Microsoft.Extensions.Logging; +using Uno; +using Uno.Disposables; +using Uno.Extensions; +using Uno.Logging; + +namespace Microsoft.Toolkit.Uwp.UI.Lottie +{ + public abstract partial class LottieVisualSourceBase : DependencyObject, IAnimatedVisualSource + { + public delegate void UpdatedAnimation(string animationJson, string cacheKey); + + private AnimatedVisualPlayer? _player; + + public static DependencyProperty UriSourceProperty { get ; } = DependencyProperty.Register( + "UriSource", + typeof(Uri), + typeof(LottieVisualSource), + new FrameworkPropertyMetadata( + default(Uri), + FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, + OnUriSourceChanged)); + + public Uri UriSource + { + get => (Uri)GetValue(UriSourceProperty); + set => SetValue(UriSourceProperty, value); + } + + public static DependencyProperty OptionsProperty { get ; } = DependencyProperty.Register( + "Options", typeof(LottieVisualOptions), typeof(LottieVisualSource), new FrameworkPropertyMetadata(LottieVisualOptions.None)); + + [NotImplemented] + public LottieVisualOptions Options + { + get => (LottieVisualOptions)GetValue(OptionsProperty); + set => SetValue(OptionsProperty, value); + } + + [NotImplemented] + public static LottieVisualSource CreateFromString(string uri) + { + throw new NotImplementedException(); + } + +#if HAS_UNO_WINUI + [NotImplemented] + public IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor, out object diagnostics) + { + throw new NotImplementedException(); + } +#endif + + private static void OnUriSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + if (sender is LottieVisualSource source) + { + source.Update(source._player); + } + } + + public Task SetSourceAsync(Uri sourceUri) + { + UriSource = sourceUri; + + // TODO: this method should not return before the animation is ready. + + return Task.CompletedTask; + } + + +#if !(__WASM__ || __ANDROID__ || __IOS__ || __MACOS__) + + public void Play(double fromProgress, double toProgress, bool looped) + { + throw new NotImplementedException(); + } + + public void Stop() + { + throw new NotImplementedException(); + } + + public void Pause() + { + throw new NotImplementedException(); + } + + public void Resume() + { + throw new NotImplementedException(); + } + + public void SetProgress(double progress) + { + throw new NotImplementedException(); + } + + public void Load() + { + throw new NotImplementedException(); + } + + public void Unload() + { + throw new NotImplementedException(); + } + + public Size Measure(Size availableSize) + { + throw new NotImplementedException(); + } + + private readonly Size CompositionSize = default; +#endif + + private SerialDisposable _updateDisposable = new SerialDisposable(); + + public void Update(AnimatedVisualPlayer? player) + { + _updateDisposable.Disposable = null; + + _player = player; + if (_player != null) + { + var cts = new CancellationDisposable(); + _updateDisposable.Disposable = cts; + var t = InnerUpdate(cts.Token); + } + } + + /// + /// If the payload needs to be altered before being feed to the player + /// + protected abstract bool IsPayloadNeedsToBeUpdated { get; } + + /// + /// Load the animation json payload + /// + protected virtual IDisposable? LoadAndObserveAnimationData( + IInputStream sourceJson, + string sourceCacheKey, + UpdatedAnimation updateCallback) + { + var cts = new CancellationTokenSource(); + + async Task Load(CancellationToken ct) + { + string json; + using (var reader = new StreamReader(sourceJson.AsStreamForRead(0))) + { + json = await reader.ReadToEndAsync(); + } + + // close the input stream + sourceJson.Dispose(); + + // load the stream (not dynamic: won't produce another version) + updateCallback(json, sourceCacheKey); + } + + var t = Load(cts.Token); + + return Disposable.Create(() => + { + cts.Cancel(); + cts.Dispose(); + }); + } + + private void SetIsPlaying(bool isPlaying) => _player?.SetValue(AnimatedVisualPlayer.IsPlayingProperty, isPlaying); + + Size IAnimatedVisualSource.Measure(Size availableSize) + { + if (_player == null) + { + return default; + } + + var compositionSize = CompositionSize; + if (compositionSize == default) + { + return default; + } + + var stretch = _player.Stretch; + + if (stretch == Stretch.None) + { + return compositionSize; + } + + var availableWidth = availableSize.Width; + var availableHeight = availableSize.Height; + + var resultSize = availableSize; + + if (double.IsInfinity(availableWidth)) + { + if (double.IsInfinity(availableHeight)) + { + return compositionSize; + } + + resultSize = new Size(availableHeight * compositionSize.Width / compositionSize.Height, availableHeight); + } + + if (double.IsInfinity(availableHeight)) + { + resultSize = new Size(availableWidth, availableWidth * compositionSize.Height / compositionSize.Width); + } + + InnerMeasure(resultSize); + + return resultSize; + } + + partial void InnerMeasure(Size size); + + private async Task TryLoadDownloadJson(Uri uri, CancellationToken ct) + { + if(await TryLoadEmbeddedJson(uri, ct) is {} json) + { + return json; + } + + return IsPayloadNeedsToBeUpdated + ? await DownloadJsonFromUri(uri, ct) + : null; + } + + private async Task TryLoadEmbeddedJson(Uri uri, CancellationToken ct) + { + if (uri.Scheme != "embedded") + { + return null; + } + + var assemblyName = uri.Host; + + var assembly = assemblyName == "." + ? Application.Current.GetType().Assembly + : Assembly.Load(assemblyName); + + if (assembly == null) + { + return null; + } + + var resourceName = uri.AbsolutePath.Substring(1).Replace("(assembly)", assembly.GetName().Name); + var stream = assembly.GetManifestResourceStream(resourceName); + if (stream == null) + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().Warn($"Unable to find embedded resource named '{resourceName}' to load."); + } + return null; + } + + return stream.AsInputStream(); + } + + private async Task DownloadJsonFromUri(Uri uri, CancellationToken ct) + { + if(uri.Scheme.Equals("ms-appx", StringComparison.OrdinalIgnoreCase)) + { + var storageFile = await StorageFile.GetFileFromApplicationUriAsync(uri).AsTask(ct); + var storageFileStream = await storageFile.OpenReadAsync().AsTask(ct); + return storageFileStream.GetInputStreamAt(0); + } + + using var client = new HttpClient(); + + using var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, ct); + + if (!response.IsSuccessStatusCode) + { + return null; + } + + if (response.Content.Headers.ContentLength is { } length && length < 2) + { + return null; + } + + var stream = await response.Content.ReadAsStreamAsync(); + + return stream.AsInputStream(); + } + } +} diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.net.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.net.cs new file mode 100644 index 000000000000..6f0ebee98dce --- /dev/null +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.net.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Toolkit.Uwp.UI.Lottie +{ + partial class LottieVisualSourceBase + { + private async Task InnerUpdate(CancellationToken ct) + { + } + } +} diff --git a/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj b/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj index 62f83a9fe9f4..4c338e5b4426 100644 --- a/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj +++ b/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj @@ -6,6 +6,7 @@ $(NoWarn);NU1701 true true + enable diff --git a/src/SamplesApp/UITests.Shared/Lottie/SampleLottieAnimation.xaml.cs b/src/SamplesApp/UITests.Shared/Lottie/SampleLottieAnimation.xaml.cs index da0399ad12b3..f15967429806 100644 --- a/src/SamplesApp/UITests.Shared/Lottie/SampleLottieAnimation.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Lottie/SampleLottieAnimation.xaml.cs @@ -32,6 +32,7 @@ public SampleLottieAnimation() "ms-appx:///Lottie/4770-lady-and-dove.json", "ms-appx:///Lottie/4930-checkbox-animation.json", "ms-appx:///Lottie/this-file-does-not-exists.json", + "embedded://./(assembly).Lottie.loading.json" }; file.SelectedIndex = 0;