diff --git a/Lottie.Android.net6/Additions/AboutAdditions.txt b/Lottie.Android.net6/Additions/AboutAdditions.txt new file mode 100644 index 0000000..c511f1d --- /dev/null +++ b/Lottie.Android.net6/Additions/AboutAdditions.txt @@ -0,0 +1,48 @@ +Additions allow you to add arbitrary C# to the generated classes +before they are compiled. This can be helpful for providing convenience +methods or adding pure C# classes. + +== Adding Methods to Generated Classes == + +Let's say the library being bound has a Rectangle class with a constructor +that takes an x and y position, and a width and length size. It will look like +this: + +public partial class Rectangle +{ + public Rectangle (int x, int y, int width, int height) + { + // JNI bindings + } +} + +Imagine we want to add a constructor to this class that takes a Point and +Size structure instead of 4 ints. We can add a new file called Rectangle.cs +with a partial class containing our new method: + +public partial class Rectangle +{ + public Rectangle (Point location, Size size) : + this (location.X, location.Y, size.Width, size.Height) + { + } +} + +At compile time, the additions class will be added to the generated class +and the final assembly will a Rectangle class with both constructors. + + +== Adding C# Classes == + +Another thing that can be done is adding fully C# managed classes to the +generated library. In the above example, let's assume that there isn't a +Point class available in Java or our library. The one we create doesn't need +to interact with Java, so we'll create it like a normal class in C#. + +By adding a Point.cs file with this class, it will end up in the binding library: + +public class Point +{ + public int X { get; set; } + public int Y { get; set; } +} diff --git a/Lottie.Android.net6/Additions/LottieAnimationView.cs b/Lottie.Android.net6/Additions/LottieAnimationView.cs new file mode 100644 index 0000000..db02201 --- /dev/null +++ b/Lottie.Android.net6/Additions/LottieAnimationView.cs @@ -0,0 +1,31 @@ +using System; +using Android.Graphics; + +namespace Com.Airbnb.Lottie +{ + public partial class LottieAnimationView + { + /// + /// Delegate to handle the loading of bitmaps that are not packaged in the assets of your app. + /// + public void SetImageAssetDelegate(Func funcAssetLoad) + { + this.SetImageAssetDelegate(new ImageAssetDelegateImpl(funcAssetLoad)); + } + + internal sealed class ImageAssetDelegateImpl : Java.Lang.Object, IImageAssetDelegate + { + private readonly Func funcAssetLoad; + + public ImageAssetDelegateImpl(Func funcAssetLoad) + { + this.funcAssetLoad = funcAssetLoad; + } + + public Bitmap FetchBitmap(LottieImageAsset asset) + { + return this.funcAssetLoad(asset); + } + } + } +} diff --git a/Lottie.Android.net6/Additions/LottieComposition.cs b/Lottie.Android.net6/Additions/LottieComposition.cs new file mode 100644 index 0000000..f18ba3d --- /dev/null +++ b/Lottie.Android.net6/Additions/LottieComposition.cs @@ -0,0 +1,160 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Android.Content; + +namespace Com.Airbnb.Lottie +{ + public partial class LottieComposition + { + public partial class Factory + { + /// + /// Asynchronously loads a composition from a file stored in /assets. + /// + public static ICancellable FromAssetFileName(Context context, string fileName, Action onLoaded) + { + return Factory.FromAssetFileName(context, fileName, new ActionCompositionLoaded(onLoaded)); + } + + /// + /// Asynchronously loads a composition from an arbitrary input stream. + /// + public static ICancellable FromInputStream(System.IO.Stream stream, Action onLoaded) + { + return Factory.FromInputStream(stream, new ActionCompositionLoaded(onLoaded)); + } + + /// + /// Asynchronously loads a composition from a json string. This is useful for animations loaded from the network. + /// + public static ICancellable FromJsonString(string jsonString, Action onLoaded) + { + return Factory.FromJsonString(jsonString, new ActionCompositionLoaded(onLoaded)); + } + + ///// + ///// Asynchronously loads a composition from a file stored in /assets. + ///// + public static Task FromAssetFileNameAsync(Context context, string fileName) + { + return FromAssetFileNameAsync(context, fileName, CancellationToken.None); + } + + /////// + /////// Asynchronously loads a composition from a file stored in /assets. + /////// + public static Task FromAssetFileNameAsync(Context context, string fileName, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + var tcs = new TaskCompletionSource(); + var cancelable = Factory.FromAssetFileName(context, fileName, (composition) => + { + cancellationToken.ThrowIfCancellationRequested(); + tcs.SetResult(composition); + }); + + cancellationToken.Register(() => + { + if (!tcs.Task.IsCompleted) + { + cancelable.Cancel(); + tcs.TrySetCanceled(cancellationToken); + } + }); + + return tcs.Task; + } + + ///// + ///// Asynchronously loads a composition from an arbitrary input stream. + ///// + public static Task FromInputStreamAsync(Context context, System.IO.Stream stream) + { + return FromInputStreamAsync(stream, CancellationToken.None); + } + + ///// + ///// Asynchronously loads a composition from an arbitrary input stream. + ///// + public static Task FromInputStreamAsync(System.IO.Stream stream, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + var tcs = new TaskCompletionSource(); + var cancelable = Factory.FromInputStream(stream, (composition) => + { + cancellationToken.ThrowIfCancellationRequested(); + tcs.SetResult(composition); + }); + + cancellationToken.Register(() => + { + if (!tcs.Task.IsCompleted) + { + cancelable.Cancel(); + tcs.TrySetCanceled(cancellationToken); + } + }); + + return tcs.Task; + } + + ///// + ///// Asynchronously loads a composition from a raw json object. This is useful for animations loaded from the network. + ///// + public static Task FromJsonStringAsync(string jsonString) + { + return FromJsonStringAsync(jsonString, CancellationToken.None); + } + + ///// + ///// Asynchronously loads a composition from a raw json object. This is useful for animations loaded from the network. + ///// + public static Task FromJsonStringAsync(string jsonString, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + var tcs = new TaskCompletionSource(); + var cancelable = Factory.FromJsonString(jsonString, (composition) => + { + cancellationToken.ThrowIfCancellationRequested(); + tcs.SetResult(composition); + }); + + cancellationToken.Register(() => + { + if (!tcs.Task.IsCompleted) + { + cancelable.Cancel(); + tcs.TrySetCanceled(cancellationToken); + } + }); + + return tcs.Task; + } + + internal sealed class ActionCompositionLoaded : Java.Lang.Object, IOnCompositionLoadedListener + { + private readonly Action onLoaded; + + public ActionCompositionLoaded(Action onLoaded) + { + this.onLoaded = onLoaded; + } + + public void OnCompositionLoaded(LottieComposition compostion) + { + if (onLoaded != null) + { + onLoaded(compostion); + } + } + } + } + } +} \ No newline at end of file diff --git a/Lottie.Android.net6/Jars/AboutJars.txt b/Lottie.Android.net6/Jars/AboutJars.txt new file mode 100644 index 0000000..e833d78 --- /dev/null +++ b/Lottie.Android.net6/Jars/AboutJars.txt @@ -0,0 +1,36 @@ +This directory is for Android .jars. + +There are 4 types of jars that are supported: + +== Input Jar and Embedded Jar == + +This is the jar that bindings should be generated for. + +For example, if you were binding the Google Maps library, this would +be Google's "maps.jar". + +The difference between EmbeddedJar and InputJar is, EmbeddedJar is to be +embedded in the resulting dll as EmbeddedResource, while InputJar is not. +There are couple of reasons you wouldn't like to embed the target jar +in your dll (the ones that could be internally loaded by +feature e.g. maps.jar, or you cannot embed jars that are under some +proprietary license). + +Set the build action for these jars in the properties page to "InputJar". + + +== Reference Jar and Embedded Reference Jar == + +These are jars that are referenced by the input jar. C# bindings will +not be created for these jars. These jars will be used to resolve +types used by the input jar. + +NOTE: Do not add "android.jar" as a reference jar. It will be added automatically +based on the Target Framework selected. + +Set the build action for these jars in the properties page to "ReferenceJar". + +"EmbeddedJar" works like "ReferenceJar", but like "EmbeddedJar", it is +embedded in your dll. But at application build time, they are not included +in the final apk, like ReferenceJar files. + diff --git a/Lottie.Android.net6/Jars/lottie-4.2.2.aar b/Lottie.Android.net6/Jars/lottie-4.2.2.aar new file mode 100644 index 0000000..f208ffa Binary files /dev/null and b/Lottie.Android.net6/Jars/lottie-4.2.2.aar differ diff --git a/Lottie.Android.net6/JavaDocs/lottie-4.2.2-sources.jar b/Lottie.Android.net6/JavaDocs/lottie-4.2.2-sources.jar new file mode 100644 index 0000000..8a8e962 Binary files /dev/null and b/Lottie.Android.net6/JavaDocs/lottie-4.2.2-sources.jar differ diff --git a/Lottie.Android.net6/Lottie.Android.net6.csproj b/Lottie.Android.net6/Lottie.Android.net6.csproj new file mode 100644 index 0000000..4012a4d --- /dev/null +++ b/Lottie.Android.net6/Lottie.Android.net6.csproj @@ -0,0 +1,32 @@ + + + net6.0-android + Lottie.Android + Lottie.Android + Render After Effects animations natively on Android, iOS, MacOS, TVOs and UWP + Com.Airbnb.Android.Lottie + true + false + 6.0.4 + + XAJavaInterop1 + <_EnableInterfaceMembers>true + true + d8 + r8 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Lottie.Android.net6/Lottie.Android.net6.sln b/Lottie.Android.net6/Lottie.Android.net6.sln new file mode 100644 index 0000000..9705ad2 --- /dev/null +++ b/Lottie.Android.net6/Lottie.Android.net6.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1700.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lottie.Android", "Lottie.Android.net6.csproj", "{C3DB3049-F830-4FB1-844F-3379005C7C78}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C3DB3049-F830-4FB1-844F-3379005C7C78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3DB3049-F830-4FB1-844F-3379005C7C78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3DB3049-F830-4FB1-844F-3379005C7C78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3DB3049-F830-4FB1-844F-3379005C7C78}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BA8370AE-4B2B-429F-9ADA-65BF99FFB691} + EndGlobalSection +EndGlobal diff --git a/Lottie.Android.net6/Transforms/EnumFields.xml b/Lottie.Android.net6/Transforms/EnumFields.xml new file mode 100644 index 0000000..68cc7fd --- /dev/null +++ b/Lottie.Android.net6/Transforms/EnumFields.xml @@ -0,0 +1,19 @@ + + + + diff --git a/Lottie.Android.net6/Transforms/EnumMethods.xml b/Lottie.Android.net6/Transforms/EnumMethods.xml new file mode 100644 index 0000000..bd77573 --- /dev/null +++ b/Lottie.Android.net6/Transforms/EnumMethods.xml @@ -0,0 +1,19 @@ + + + + diff --git a/Lottie.Android.net6/Transforms/Metadata.xml b/Lottie.Android.net6/Transforms/Metadata.xml new file mode 100644 index 0000000..8f46580 --- /dev/null +++ b/Lottie.Android.net6/Transforms/Metadata.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + public + + public + canvas + parentMatrix + alpha + + outBounds + parentMatrix + applyParents + + public + contentsBefore + contentsAfter + + public + contentsBefore + contentsAfter + + + attrs + context + + + public + public + public + + + + + defStyleAttr + + assetDelegate + + animationName + + animationName + cacheStrategy + + json + animationResId + + animationResId + cacheStrategy + + updateListener + updateListener + + listener + listener + + loop + + composition + + + imageAssetsFolder + speed + + name + + composition + + enable + + assetDelegate + enabled + textDelegate + + id + bitmap + + + startProgress + startFrame + + endFrame + endProgress + + minFrame + maxFrame + + minProgress + maxProgress + + + enabled + + fileName + + + fileName + listener + + + stream + listener + + + jsonString + loadedListener + + reader + loadedListener + + + who + who + what + when + + composition + imageAssetsFolder + + whot + what + + colorFilter + canvas + progress + alpha + + assetDelegate + + listener + updateListener + + loop + + listener + updateListener + + speed + + enable + + assetDelegate + + enabled + + textDelegate + + id + bitmap + + startProgress + startFrame + + endFrame + endProgress + + minFrame + maxFrame + + minProgress + maxProgress + + composition + + id + fontFamily + style + + + color + + + drawable + animationView + input + input + output + + input + cacheText + + + asset + + + + contentsBefore + contentsAfter + + outBounds + parentMatrix + + contentsIter + + canvas + parentMatrix + alpha + + + fontFamily + fontFamily + + + + frameInfo + + + property + callback + + keyPath + depth + accumulator + currentPartialKeyPath + \ No newline at end of file diff --git a/Lottie.Android.net6/readme.txt b/Lottie.Android.net6/readme.txt new file mode 100644 index 0000000..5831419 --- /dev/null +++ b/Lottie.Android.net6/readme.txt @@ -0,0 +1,22 @@ +--------------------------------- +Lottie +--------------------------------- + +Lottie is a mobile library for Android and iOS that parses Adobe After Effects animations exported as json with Bodymovin and renders them natively on mobile! + +Using Lottie on Xamarin.Android: + + + +--------------------------------- +Star on Github if this project helps you: https://github.com/Baseflow/LottieXamarin + +Commercial support is available. Integration with your app or services, samples, feature request, etc. Email: hello@baseflow.com +Powered by: https://baseflow.com +--------------------------------- \ No newline at end of file diff --git a/Lottie.Android/Lottie.Android.sln b/Lottie.Android/Lottie.Android.sln new file mode 100644 index 0000000..2e8aa44 --- /dev/null +++ b/Lottie.Android/Lottie.Android.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1700.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lottie.Android", "Lottie.Android.csproj", "{C3DB3049-F830-4FB1-844F-3379005C7C78}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C3DB3049-F830-4FB1-844F-3379005C7C78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3DB3049-F830-4FB1-844F-3379005C7C78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3DB3049-F830-4FB1-844F-3379005C7C78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3DB3049-F830-4FB1-844F-3379005C7C78}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BA8370AE-4B2B-429F-9ADA-65BF99FFB691} + EndGlobalSection +EndGlobal diff --git a/Lottie.Maui/AnimationSource.cs b/Lottie.Maui/AnimationSource.cs new file mode 100644 index 0000000..b94a3e7 --- /dev/null +++ b/Lottie.Maui/AnimationSource.cs @@ -0,0 +1,33 @@ +namespace Lottie.Maui +{ + public enum AnimationSource + { + /// + /// Use when Lottie should load the json file from Asset or Bundle folder. + /// The Animation input should be a string containing the file name + /// + AssetOrBundle, + + /// + /// Url should point to a json file containing the Lottie animation on a remote resource + /// + Url, + + /// + /// Use when passing in json directly as a string + /// + Json, + + /// + /// Stream the Lottie animation to the view + /// + Stream, + + /// + /// When loading from an EmbeddedResource which is compiled into an Assembly + /// Either set the file name as string to make Lottie read from the calling Assembly + /// Or use the syntax "resource://LottieLogo1.json?assembly=Example.Forms" + /// + EmbeddedResource + } +} diff --git a/Lottie.Maui/AnimationView.cs b/Lottie.Maui/AnimationView.cs new file mode 100644 index 0000000..9db25de --- /dev/null +++ b/Lottie.Maui/AnimationView.cs @@ -0,0 +1,480 @@ +using System.Reflection; +using System.Windows.Input; + +namespace Lottie.Maui +{ + public class AnimationView : View + { + //public static readonly BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), + // typeof(ImageSource), typeof(AnimationView), default(ImageSource)); + + public static readonly BindableProperty AnimationProperty = BindableProperty.Create(nameof(Animation), + typeof(object), typeof(AnimationView), default(object)); + + public static readonly BindableProperty AnimationSourceProperty = BindableProperty.Create(nameof(Maui.AnimationSource), + typeof(AnimationSource), typeof(AnimationView), AnimationSource.AssetOrBundle); + + public static readonly BindableProperty CacheCompositionProperty = BindableProperty.Create(nameof(CacheComposition), + typeof(bool), typeof(AnimationView), true); + + public static readonly BindableProperty FallbackResourceProperty = BindableProperty.Create(nameof(FallbackResource), + typeof(ImageSource), typeof(AnimationView), default(ImageSource)); + + //public static readonly BindableProperty CompositionProperty = BindableProperty.Create(nameof(Composition), + // typeof(ILottieComposition), typeof(AnimationView), default(ILottieComposition)); + + public static readonly BindableProperty MinFrameProperty = BindableProperty.Create(nameof(MinFrame), + typeof(int), typeof(AnimationView), int.MinValue); + + public static readonly BindableProperty MinProgressProperty = BindableProperty.Create(nameof(MinProgress), + typeof(float), typeof(AnimationView), float.MinValue); + + public static readonly BindableProperty MaxFrameProperty = BindableProperty.Create(nameof(MaxFrame), + typeof(int), typeof(AnimationView), int.MinValue); + + public static readonly BindableProperty MaxProgressProperty = BindableProperty.Create(nameof(MaxProgress), + typeof(float), typeof(AnimationView), float.MinValue); + + public static readonly BindableProperty SpeedProperty = BindableProperty.Create(nameof(Speed), + typeof(float), typeof(AnimationView), 1.0f); + + public static readonly BindableProperty RepeatModeProperty = BindableProperty.Create(nameof(RepeatMode), + typeof(RepeatMode), typeof(AnimationView), Lottie.Maui.RepeatMode.Restart); + + public static readonly BindableProperty RepeatCountProperty = BindableProperty.Create(nameof(RepeatCount), + typeof(int), typeof(AnimationView), 0); + + public static readonly BindableProperty IsAnimatingProperty = BindableProperty.Create(nameof(IsAnimating), + typeof(bool), typeof(AnimationView), false); + + public static readonly BindableProperty ImageAssetsFolderProperty = BindableProperty.Create(nameof(ImageAssetsFolder), + typeof(string), typeof(AnimationView), default(string)); + + //public static new readonly BindableProperty ScaleProperty = BindableProperty.Create(nameof(Scale), + // typeof(float), typeof(AnimationView), 1.0f); + + public static readonly BindableProperty FrameProperty = BindableProperty.Create(nameof(Frame), + typeof(int), typeof(AnimationView), default(int)); + + public static readonly BindableProperty ProgressProperty = BindableProperty.Create(nameof(Progress), + typeof(float), typeof(AnimationView), 0.0f); + + //TODO: Maybe make TimeSpan + public static readonly BindableProperty DurationProperty = BindableProperty.Create(nameof(Duration), + typeof(long), typeof(AnimationView), default(long)); + + public static readonly BindableProperty AutoPlayProperty = BindableProperty.Create(nameof(AutoPlay), + typeof(bool), typeof(AnimationView), true); + + public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), + typeof(ICommand), typeof(AnimationView)); + + public static readonly BindableProperty EnableMergePathsForKitKatAndAboveProperty = BindableProperty.Create(nameof(EnableMergePathsForKitKatAndAbove), + typeof(bool), typeof(AnimationView), false); + + /// + /// Returns the duration of an animation (Frames / FrameRate * 1000) + /// + public long Duration + { + get { return (long)GetValue(DurationProperty); } + internal set { SetValue(DurationProperty, value); } + } + + /// + /// Indicates if a Lottie Animation should be cached + /// + public bool CacheComposition + { + get { return (bool)GetValue(CacheCompositionProperty); } + set { SetValue(CacheCompositionProperty, value); } + } + + /// + /// Set the Animation that you want to play. This can be a URL (either local path or remote), Json string, or Stream + /// + public object Animation + { + get { return (object)GetValue(AnimationProperty); } + set { SetValue(AnimationProperty, value); } + } + + /// + /// Indicates where the Animation is located and from which source it should be loaded + /// Default value is AssetOrBundle + /// + public AnimationSource AnimationSource + { + get { return (AnimationSource)GetValue(AnimationSourceProperty); } + set { SetValue(AnimationSourceProperty, value); } + } + + /// + /// Used in case an animations fails to load + /// + public ImageSource FallbackResource + { + get { return (ImageSource)GetValue(FallbackResourceProperty); } + set { SetValue(FallbackResourceProperty, value); } + } + + //public ILottieComposition Composition + //{ + // get { return (ILottieComposition)GetValue(CompositionProperty); } + // set { SetValue(CompositionProperty, value); } + //} + + /// + /// Sets or gets the minimum frame that the animation will start from when playing or looping. + /// + public int MinFrame + { + get { return (int)GetValue(MinFrameProperty); } + set { SetValue(MinFrameProperty, value); } + } + + /// + /// Sets or gets the minimum progress that the animation will start from when playing or looping. + /// + public float MinProgress + { + get { return (float)GetValue(MinProgressProperty); } + set { SetValue(MinProgressProperty, value); } + } + + /// + /// Sets or gets the maximum frame that the animation will end at when playing or looping. + /// + public int MaxFrame + { + get { return (int)GetValue(MaxFrameProperty); } + set { SetValue(MaxFrameProperty, value); } + } + + /// + /// Sets or gets the maximum progress that the animation will end at when playing or looping. + /// + public float MaxProgress + { + get { return (float)GetValue(MaxProgressProperty); } + set { SetValue(MaxProgressProperty, value); } + } + + /// + /// Returns the current playback speed. This will be < 0 if the animation is playing backwards. + /// + public float Speed + { + get { return (float)GetValue(SpeedProperty); } + set { SetValue(SpeedProperty, value); } + } + + /// + /// Defines what this animation should do when it reaches the end. + /// This setting is applied only when the repeat count is either greater than 0 or INFINITE. + /// Defaults to RESTART. + /// + public RepeatMode RepeatMode + { + get { return (RepeatMode)GetValue(RepeatModeProperty); } + set { SetValue(RepeatModeProperty, value); } + } + + /// + /// Sets how many times the animation should be repeated. If the repeat count is 0, the animation is never repeated. + /// If the repeat count is greater than 0 or INFINITE, the repeat mode will be taken into account. + /// The repeat count is 0 by default. + /// + public int RepeatCount + { + get { return (int)GetValue(RepeatCountProperty); } + set { SetValue(RepeatCountProperty, value); } + } + + /// + /// Indicates if the Animation is playing + /// + public bool IsAnimating + { + get { return (bool)GetValue(IsAnimatingProperty); } + internal set { SetValue(IsAnimatingProperty, value); } + } + + /// + /// If you use image assets, you must explicitly specify the folder in assets/ in which they are located because bodymovin uses the name filenames across all compositions + /// + public string ImageAssetsFolder + { + get { return (string)GetValue(ImageAssetsFolderProperty); } + set { SetValue(ImageAssetsFolderProperty, value); } + } + + /// + /// Set the scale on the current composition. + /// The only cost of this function is re-rendering the current frame so you may call it frequent to scale something up or down. + /// + //public new float Scale + //{ + // get { return (float)GetValue(ScaleProperty); } + // set { SetValue(ScaleProperty, value); } + //} + + /// + /// Sets the progress to the specified frame. + /// If the composition isn't set yet, the progress will be set to the frame when it is. + /// + public int Frame + { + get { return (int)GetValue(FrameProperty); } + set { SetValue(FrameProperty, value); } + } + + /// + /// Returns the current progress of the animation + /// + public float Progress + { + get { return (float)GetValue(ProgressProperty); } + set { SetValue(ProgressProperty, value); } + } + + /// + /// When true the Lottie animation will automatically start playing when loaded + /// + public bool AutoPlay + { + get { return (bool)GetValue(AutoPlayProperty); } + set { SetValue(AutoPlayProperty, value); } + } + + /// + /// Will be called when the view is clicked + /// + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + /// + /// When true the Lottie animation will enable merge paths for devices with KitKat and above + /// + public bool EnableMergePathsForKitKatAndAbove + { + get { return (bool)GetValue(EnableMergePathsForKitKatAndAboveProperty); } + set { SetValue(EnableMergePathsForKitKatAndAboveProperty, value); } + } + + /// + /// Called when the Lottie animation starts playing + /// + public event EventHandler OnPlayAnimation; + + /// + /// Called when the Lottie animation is paused + /// + public event EventHandler OnPauseAnimation; + + /// + /// Called when the Lottie animation is resumed after pausing + /// + public event EventHandler OnResumeAnimation; + + /// + /// Called when the Lottie animation is stopped + /// + public event EventHandler OnStopAnimation; + + /// + /// Called when the Lottie animation is repeated + /// + public event EventHandler OnRepeatAnimation; + + /// + /// Called when the Lottie animation is clicked + /// + public event EventHandler Clicked; + + /// + /// Called when the Lottie animation is playing with the current progress + /// + public event EventHandler OnAnimationUpdate; + + /// + /// Called when the Lottie animation is loaded with the Lottie Composition as parameter + /// + public event EventHandler OnAnimationLoaded; + + /// + /// Called when the animation fails to load or when an exception happened when trying to play + /// + public event EventHandler OnFailure; + + /// + /// Called when the Lottie animation is finished playing + /// + public event EventHandler OnFinishedAnimation; + + internal void InvokePlayAnimation() + { + OnPlayAnimation?.Invoke(this, EventArgs.Empty); + } + + internal void InvokeResumeAnimation() + { + OnResumeAnimation?.Invoke(this, EventArgs.Empty); + } + + internal void InvokeStopAnimation() + { + OnStopAnimation?.Invoke(this, EventArgs.Empty); + } + + internal void InvokePauseAnimation() + { + OnPauseAnimation?.Invoke(this, EventArgs.Empty); + } + + internal void InvokeRepeatAnimation() + { + OnRepeatAnimation?.Invoke(this, EventArgs.Empty); + } + + internal void InvokeAnimationUpdate(float progress) + { + OnAnimationUpdate?.Invoke(this, progress); + } + + internal void InvokeAnimationLoaded(object animation) + { + OnAnimationLoaded?.Invoke(this, animation); + } + + internal void InvokeFailure(Exception ex) + { + OnFailure?.Invoke(this, ex); + } + + internal void InvokeFinishedAnimation() + { + OnFinishedAnimation?.Invoke(this, EventArgs.Empty); + } + + internal void InvokeClick() + { + Clicked?.Invoke(this, EventArgs.Empty); + Command.ExecuteCommandIfPossible(this); + } + + internal ICommand PlayCommand { get; set; } + internal ICommand PauseCommand { get; set; } + internal ICommand ResumeCommand { get; set; } + internal ICommand StopCommand { get; set; } + internal ICommand ClickCommand { get; set; } + internal ICommand PlayMinAndMaxFrameCommand { get; set; } + internal ICommand PlayMinAndMaxProgressCommand { get; set; } + internal ICommand ReverseAnimationSpeedCommand { get; set; } + + /// + /// Simulate a click action on the view + /// + public void Click() + { + ClickCommand.ExecuteCommandIfPossible(this); + } + + /// + /// Plays the animation from the beginning. If speed is < 0, it will start at the end and play towards the beginning + /// + public void PlayAnimation() + { + PlayCommand.ExecuteCommandIfPossible(); + } + + /// + /// Continues playing the animation from its current position. If speed < 0, it will play backwards from the current position. + /// + public void ResumeAnimation() + { + ResumeCommand.ExecuteCommandIfPossible(); + } + + /// + /// Will stop and reset the currently playing animation + /// + public void StopAnimation() + { + StopCommand.ExecuteCommandIfPossible(); + } + + /// + /// Will pause the currently playing animation. Call ResumeAnimation to continue + /// + public void PauseAnimation() + { + PauseCommand.ExecuteCommandIfPossible(); + } + + public void PlayMinAndMaxFrame(int minFrame, int maxFrame) + { + PlayMinAndMaxFrameCommand.ExecuteCommandIfPossible((minFrame, maxFrame)); + } + + public void PlayMinAndMaxProgress(float minProgress, float maxProgress) + { + PlayMinAndMaxProgressCommand.ExecuteCommandIfPossible((minProgress, maxProgress)); + } + + /// + /// Reverses the current animation speed. This does NOT play the animation. + /// + public void ReverseAnimationSpeed() + { + ReverseAnimationSpeedCommand.ExecuteCommandIfPossible(); + } + + public void SetAnimationFromAssetOrBundle(string path) + { + AnimationSource = AnimationSource.AssetOrBundle; + Animation = path; + } + + public void SetAnimationFromEmbeddedResource(string resourceName, Assembly assembly = null) + { + AnimationSource = AnimationSource.EmbeddedResource; + + if (assembly == null) + assembly = Application.Current.GetType().Assembly; + + Animation = $"resource://{resourceName}?assembly={Uri.EscapeUriString(assembly.FullName)}"; + } + + public void SetAnimationFromJson(string json) + { + AnimationSource = AnimationSource.Json; + Animation = json; + } + + public void SetAnimationFromUrl(string url) + { + AnimationSource = AnimationSource.Url; + Animation = url; + } + + public void SetAnimationFromStream(Stream stream) + { + AnimationSource = AnimationSource.Stream; + Animation = stream; + } + + // setImageAssetDelegate(ImageAssetDelegate assetDelegate) { + + // setFontAssetDelegate( + + // setTextDelegate(TextDelegate textDelegate) + + // setScaleType + + //RenderMode + } +} diff --git a/Lottie.Maui/AnimationViewExtensions.cs b/Lottie.Maui/AnimationViewExtensions.cs new file mode 100644 index 0000000..7fb8078 --- /dev/null +++ b/Lottie.Maui/AnimationViewExtensions.cs @@ -0,0 +1,56 @@ +using System.Reflection; + +namespace Lottie.Maui +{ + public static class AnimationViewExtensions + { + public static Stream GetStreamFromAssembly(this AnimationView animationView) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + if (animationView.Animation is string embeddedAnimation) + { + Assembly assembly = null; + string resourceName = null; + + if (embeddedAnimation.StartsWith("resource://", StringComparison.OrdinalIgnoreCase)) + { + var uri = new Uri(embeddedAnimation); + + var parts = uri.OriginalString.Substring(11).Split('?'); + resourceName = parts.First(); + + if (parts.Length > 1) + { + var name = Uri.UnescapeDataString(uri.Query.Substring(10)); + var assemblyName = new AssemblyName(name); + assembly = Assembly.Load(assemblyName); + } + + if (assembly == null) + { + var callingAssemblyMethod = typeof(Assembly).GetTypeInfo().GetDeclaredMethod("GetCallingAssembly"); + assembly = (Assembly)callingAssemblyMethod.Invoke(null, Array.Empty()); + } + } + + if (assembly == null) + assembly = Application.Current.GetType().Assembly; + + if (string.IsNullOrEmpty(resourceName)) + resourceName = embeddedAnimation; + + var stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.{resourceName}"); + + if (stream == null) + { + return null; + //throw new FileNotFoundException("Cannot find file.", embeddedAnimation); + } + return stream; + } + return null; + } + } +} diff --git a/Lottie.Maui/ILottieComposition.cs b/Lottie.Maui/ILottieComposition.cs new file mode 100644 index 0000000..8ea452c --- /dev/null +++ b/Lottie.Maui/ILottieComposition.cs @@ -0,0 +1,7 @@ +namespace Lottie.Maui +{ + public interface ILottieComposition : IDisposable + { + //TODO: Implement native per platform + } +} diff --git a/Lottie.Maui/Lottie.Maui.csproj b/Lottie.Maui/Lottie.Maui.csproj new file mode 100644 index 0000000..16d9a1e --- /dev/null +++ b/Lottie.Maui/Lottie.Maui.csproj @@ -0,0 +1,38 @@ + + + net6.0;net6.0-android;net6.0-ios + enable + true + + + + Lottie.Maui + Lottie.Maui + Render After Effects animations natively on Android and iOS. + Com.Airbnb.Maui.Lottie + 3.0.4 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Lottie.Maui/Lottie.Maui.sln b/Lottie.Maui/Lottie.Maui.sln new file mode 100644 index 0000000..fe8a173 --- /dev/null +++ b/Lottie.Maui/Lottie.Maui.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1700.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lottie.Maui", "Lottie.Maui.csproj", "{99BD63A0-8C06-4BFE-944A-04231666B35B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {99BD63A0-8C06-4BFE-944A-04231666B35B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99BD63A0-8C06-4BFE-944A-04231666B35B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99BD63A0-8C06-4BFE-944A-04231666B35B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99BD63A0-8C06-4BFE-944A-04231666B35B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DDB5F21F-5C0B-4651-8FBE-AA7C9EE9FF24} + EndGlobalSection +EndGlobal diff --git a/Lottie.Maui/LottieExtensions.cs b/Lottie.Maui/LottieExtensions.cs new file mode 100644 index 0000000..bf880ce --- /dev/null +++ b/Lottie.Maui/LottieExtensions.cs @@ -0,0 +1,15 @@ +using System.Windows.Input; + +namespace Lottie.Maui +{ + public static class LottieExtensions + { + public static void ExecuteCommandIfPossible(this ICommand command, object parameter = null) + { + if (command?.CanExecute(parameter) == true) + { + command.Execute(parameter); + } + } + } +} diff --git a/Lottie.Maui/Platforms/Android/AnimationViewExtensions.cs b/Lottie.Maui/Platforms/Android/AnimationViewExtensions.cs new file mode 100644 index 0000000..e889c9f --- /dev/null +++ b/Lottie.Maui/Platforms/Android/AnimationViewExtensions.cs @@ -0,0 +1,97 @@ +using System.IO; +using Com.Airbnb.Lottie; + +namespace Lottie.Maui.Platforms.Android +{ + public static class AnimationViewExtensions + { + public static void TrySetAnimation(this LottieAnimationView lottieAnimationView, AnimationView animationView) + { + if (lottieAnimationView == null) + throw new ArgumentNullException(nameof(lottieAnimationView)); + + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + switch (animationView.AnimationSource) + { + case AnimationSource.AssetOrBundle: + lottieAnimationView.TrySetAnimation(animationView, animationView.Animation); + break; + case AnimationSource.Url: + if (animationView.Animation is string stringAnimation) + lottieAnimationView.SetAnimationFromUrl(stringAnimation); + break; + case AnimationSource.Json: + if (animationView.Animation is string jsonAnimation) + lottieAnimationView.SetAnimationFromJson(jsonAnimation); + break; + case AnimationSource.Stream: + lottieAnimationView.TrySetAnimation(animationView, animationView.Animation); + break; + case AnimationSource.EmbeddedResource: + lottieAnimationView.TrySetAnimation(animationView, animationView.GetStreamFromAssembly()); + break; + default: + break; + } + } + + public static void TrySetAnimation(this LottieAnimationView lottieAnimationView, AnimationView animationView, object animation) + { + if (lottieAnimationView == null) + throw new ArgumentNullException(nameof(lottieAnimationView)); + + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + switch (animation) + { + case int intAnimation: + lottieAnimationView.SetAnimation(intAnimation); + break; + case string stringAnimation: + + //TODO: check if json + //animationView.SetAnimationFromJson(stringAnimation); + //TODO: check if url + //animationView.SetAnimationFromUrl(stringAnimation); + + lottieAnimationView.SetAnimation(stringAnimation); + break; + case Stream streamAnimation: + lottieAnimationView.SetAnimation(streamAnimation, null); + break; + case null: + lottieAnimationView.ClearAnimation(); + break; + default: + break; + } + } + + public static void ConfigureRepeat(this LottieAnimationView lottieAnimationView, RepeatMode repeatMode, int repeatCount) + { + if (lottieAnimationView == null) + throw new ArgumentNullException(nameof(lottieAnimationView)); + + lottieAnimationView.RepeatCount = repeatCount; + + switch (repeatMode) + { + case RepeatMode.Infinite: + { + lottieAnimationView.RepeatCount = int.MaxValue; + lottieAnimationView.RepeatMode = LottieDrawable.Infinite; + break; + } + case RepeatMode.Restart: + lottieAnimationView.RepeatMode = LottieDrawable.Restart; + break; + case RepeatMode.Reverse: + lottieAnimationView.RepeatMode = LottieDrawable.Reverse; + break; + } + } + } +} diff --git a/Lottie.Maui/Platforms/Android/AnimationViewRenderer.cs b/Lottie.Maui/Platforms/Android/AnimationViewRenderer.cs new file mode 100644 index 0000000..5bb84eb --- /dev/null +++ b/Lottie.Maui/Platforms/Android/AnimationViewRenderer.cs @@ -0,0 +1,213 @@ +using System.ComponentModel; +using Android.Runtime; +using Lottie.Maui; +using Lottie.Maui.Platforms.Android; +using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Platform; +using Com.Airbnb.Lottie; +using Android.Content; + +[assembly: ExportRenderer(typeof(AnimationView), typeof(AnimationViewRenderer))] +#if MAUI +Preserve(AllMembers = true) +#endif +namespace Lottie.Maui.Platforms.Android +{ +#pragma warning disable 0618 + public class AnimationViewRenderer : Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat.ViewRenderer + { + private LottieAnimationView _animationView; + private AnimatorListener _animatorListener; + private AnimatorUpdateListener _animatorUpdateListener; + private LottieOnCompositionLoadedListener _lottieOnCompositionLoadedListener; + private LottieFailureListener _lottieFailureListener; + private ClickListener _clickListener; + + public AnimationViewRenderer(Context context) : base(context) {} + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e == null) + return; + + if (e.OldElement != null) + { + _animationView.RemoveAnimatorListener(_animatorListener); + _animationView.RemoveAllUpdateListeners(); + _animationView.RemoveLottieOnCompositionLoadedListener(_lottieOnCompositionLoadedListener); + _animationView.SetFailureListener(null); + _animationView.SetOnClickListener(null); + } + + if (e.NewElement != null) + { + if (Control == null) + { + _animationView = new LottieAnimationView(Context); + _animatorListener = new AnimatorListener + { + OnAnimationCancelImpl = () => e.NewElement.InvokeStopAnimation(), + OnAnimationEndImpl = () => e.NewElement.InvokeFinishedAnimation(), + OnAnimationPauseImpl = () => e.NewElement.InvokePauseAnimation(), + OnAnimationRepeatImpl = () => e.NewElement.InvokeRepeatAnimation(), + OnAnimationResumeImpl = () => e.NewElement.InvokeResumeAnimation(), + OnAnimationStartImpl = () => e.NewElement.InvokePlayAnimation() + }; + _animatorUpdateListener = new AnimatorUpdateListener + { + OnAnimationUpdateImpl = (progress) => e.NewElement.InvokeAnimationUpdate(progress) + }; + _lottieOnCompositionLoadedListener = new LottieOnCompositionLoadedListener + { + OnCompositionLoadedImpl = (composition) => e.NewElement.InvokeAnimationLoaded(composition) + }; + _lottieFailureListener = new LottieFailureListener + { + OnResultImpl = (exception) => e.NewElement.InvokeFailure(exception) + }; + _clickListener = new ClickListener + { + OnClickImpl = () => e.NewElement.InvokeClick() + }; + + _animationView.AddAnimatorListener(_animatorListener); + _animationView.AddAnimatorUpdateListener(_animatorUpdateListener); + _animationView.AddLottieOnCompositionLoadedListener(_lottieOnCompositionLoadedListener); + _animationView.SetFailureListener(_lottieFailureListener); + _animationView.SetOnClickListener(_clickListener); + + _animationView.TrySetAnimation(e.NewElement); + + e.NewElement.PlayCommand = new Command(() => _animationView.PlayAnimation()); + e.NewElement.PauseCommand = new Command(() => _animationView.PauseAnimation()); + e.NewElement.ResumeCommand = new Command(() => _animationView.ResumeAnimation()); + e.NewElement.StopCommand = new Command(() => + { + _animationView.CancelAnimation(); + _animationView.Progress = 0.0f; + }); + e.NewElement.ClickCommand = new Command(() => _animationView.PerformClick()); + + e.NewElement.PlayMinAndMaxFrameCommand = new Command((object paramter) => + { + if (paramter is (int minFrame, int maxFrame)) + { + _animationView.SetMinAndMaxFrame(minFrame, maxFrame); + _animationView.PlayAnimation(); + } + }); + e.NewElement.PlayMinAndMaxProgressCommand = new Command((object paramter) => + { + if (paramter is (float minProgress, float maxProgress)) + { + _animationView.SetMinAndMaxProgress(minProgress, maxProgress); + _animationView.PlayAnimation(); + } + }); + e.NewElement.ReverseAnimationSpeedCommand = new Command(() => _animationView.ReverseAnimationSpeed()); + + _animationView.SetCacheComposition(e.NewElement.CacheComposition); + //_animationView.SetFallbackResource(e.NewElement.FallbackResource.); + //_animationView.Composition = e.NewElement.Composition; + + if (e.NewElement.MinFrame != int.MinValue) + _animationView.SetMinFrame(e.NewElement.MinFrame); + if (e.NewElement.MinProgress != float.MinValue) + _animationView.SetMinProgress(e.NewElement.MinProgress); + if (e.NewElement.MaxFrame != int.MinValue) + _animationView.SetMaxFrame(e.NewElement.MaxFrame); + if (e.NewElement.MaxProgress != float.MinValue) + _animationView.SetMaxProgress(e.NewElement.MaxProgress); + + _animationView.Speed = e.NewElement.Speed; + + _animationView.ConfigureRepeat(e.NewElement.RepeatMode, e.NewElement.RepeatCount); + + if (!string.IsNullOrEmpty(e.NewElement.ImageAssetsFolder)) + _animationView.ImageAssetsFolder = e.NewElement.ImageAssetsFolder; + + //TODO: see if this needs to be enabled + //_animationView.Scale = Convert.ToSingle(e.NewElement.Scale); + + _animationView.Frame = e.NewElement.Frame; + _animationView.Progress = e.NewElement.Progress; + + _animationView.EnableMergePathsForKitKatAndAbove(e.NewElement.EnableMergePathsForKitKatAndAbove); + + SetNativeControl(_animationView); + + if (e.NewElement.AutoPlay || e.NewElement.IsAnimating) + _animationView.PlayAnimation(); + + e.NewElement.Duration = _animationView.Duration; + e.NewElement.IsAnimating = _animationView.IsAnimating; + } + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_animationView == null || Element == null || e == null) + return; + + if (e.PropertyName == AnimationView.AnimationProperty.PropertyName) + { + _animationView.TrySetAnimation(Element); + + if (Element.AutoPlay || Element.IsAnimating) + _animationView.PlayAnimation(); + } + + //if (e.PropertyName == AnimationView.AutoPlayProperty.PropertyName) + // _animationView.AutoPlay = (Element.AutoPlay); + + if (e.PropertyName == AnimationView.CacheCompositionProperty.PropertyName) + _animationView.SetCacheComposition(Element.CacheComposition); + + //if (e.PropertyName == AnimationView.FallbackResource.PropertyName) + // _animationView.SetFallbackResource(e.NewElement.FallbackResource); + + //if (e.PropertyName == AnimationView.Composition.PropertyName) + // _animationView.Composition = e.NewElement.Composition; + + if (e.PropertyName == AnimationView.EnableMergePathsForKitKatAndAboveProperty.PropertyName) + _animationView.EnableMergePathsForKitKatAndAbove(Element.EnableMergePathsForKitKatAndAbove); + + if (e.PropertyName == AnimationView.MinFrameProperty.PropertyName) + _animationView.SetMinFrame(Element.MinFrame); + + if (e.PropertyName == AnimationView.MinProgressProperty.PropertyName) + _animationView.SetMinProgress(Element.MinProgress); + + if (e.PropertyName == AnimationView.MaxFrameProperty.PropertyName) + _animationView.SetMaxFrame(Element.MaxFrame); + + if (e.PropertyName == AnimationView.MaxProgressProperty.PropertyName) + _animationView.SetMaxProgress(Element.MaxProgress); + + if (e.PropertyName == AnimationView.SpeedProperty.PropertyName) + _animationView.Speed = Element.Speed; + + if (e.PropertyName == AnimationView.RepeatModeProperty.PropertyName || e.PropertyName == AnimationView.RepeatCountProperty.PropertyName) + _animationView.ConfigureRepeat(Element.RepeatMode, Element.RepeatCount); + + if (e.PropertyName == AnimationView.ImageAssetsFolderProperty.PropertyName && !string.IsNullOrEmpty(Element.ImageAssetsFolder)) + _animationView.ImageAssetsFolder = Element.ImageAssetsFolder; + + //TODO: see if this needs to be enabled + //if (e.PropertyName == AnimationView.ScaleProperty.PropertyName) + // _animationView.Scale = Element.Scale; + + if (e.PropertyName == AnimationView.FrameProperty.PropertyName) + _animationView.Frame = Element.Frame; + + if (e.PropertyName == AnimationView.ProgressProperty.PropertyName) + _animationView.Progress = Element.Progress; + + base.OnElementPropertyChanged(sender, e); + } + } +#pragma warning restore 0618 +} diff --git a/Lottie.Maui/Platforms/Android/AnimatorListener.cs b/Lottie.Maui/Platforms/Android/AnimatorListener.cs new file mode 100644 index 0000000..d6e36b6 --- /dev/null +++ b/Lottie.Maui/Platforms/Android/AnimatorListener.cs @@ -0,0 +1,59 @@ +using Android.Animation; +using Android.Runtime; + +namespace Lottie.Maui.Platforms.Android +{ + public class AnimatorListener : AnimatorListenerAdapter + { + public AnimatorListener() + { + } + + public AnimatorListener(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) + { + } + + public Action OnAnimationCancelImpl { get; set; } + public Action OnAnimationEndImpl { get; set; } + public Action OnAnimationPauseImpl { get; set; } + public Action OnAnimationRepeatImpl { get; set; } + public Action OnAnimationResumeImpl { get; set; } + public Action OnAnimationStartImpl { get; set; } + + public override void OnAnimationCancel(Animator animation) + { + base.OnAnimationCancel(animation); + OnAnimationCancelImpl?.Invoke(); + } + + public override void OnAnimationEnd(Animator animation) + { + base.OnAnimationEnd(animation); + OnAnimationEndImpl?.Invoke(); + } + + public override void OnAnimationPause(Animator animation) + { + base.OnAnimationPause(animation); + OnAnimationPauseImpl?.Invoke(); + } + + public override void OnAnimationRepeat(Animator animation) + { + base.OnAnimationRepeat(animation); + OnAnimationRepeatImpl?.Invoke(); + } + + public override void OnAnimationResume(Animator animation) + { + base.OnAnimationResume(animation); + OnAnimationResumeImpl?.Invoke(); + } + + public override void OnAnimationStart(Animator animation) + { + base.OnAnimationStart(animation); + OnAnimationStartImpl?.Invoke(); + } + } +} diff --git a/Lottie.Maui/Platforms/Android/AnimatorUpdateListener.cs b/Lottie.Maui/Platforms/Android/AnimatorUpdateListener.cs new file mode 100644 index 0000000..25bffc4 --- /dev/null +++ b/Lottie.Maui/Platforms/Android/AnimatorUpdateListener.cs @@ -0,0 +1,26 @@ +using Android.Animation; +using Android.Runtime; + +namespace Lottie.Maui.Platforms.Android +{ + public class AnimatorUpdateListener : Java.Lang.Object, ValueAnimator.IAnimatorUpdateListener + { + public AnimatorUpdateListener() + { + } + + public AnimatorUpdateListener(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) + { + } + + public Action OnAnimationUpdateImpl { get; set; } + + public void OnAnimationUpdate(ValueAnimator animation) + { + if (animation == null) + throw new ArgumentNullException(nameof(animation)); + + OnAnimationUpdateImpl?.Invoke(((float)animation.AnimatedValue)); + } + } +} diff --git a/Lottie.Maui/Platforms/Android/ClickListener.cs b/Lottie.Maui/Platforms/Android/ClickListener.cs new file mode 100644 index 0000000..03a5fe6 --- /dev/null +++ b/Lottie.Maui/Platforms/Android/ClickListener.cs @@ -0,0 +1,23 @@ +using Android.Runtime; +using static Android.Views.View; + +namespace Lottie.Maui.Platforms.Android +{ + public class ClickListener : Java.Lang.Object, IOnClickListener + { + public ClickListener() + { + } + + public ClickListener(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) + { + } + + public Action OnClickImpl { get; set; } + + public void OnClick(global::Android.Views.View v) + { + OnClickImpl?.Invoke(); + } + } +} diff --git a/Lottie.Maui/Platforms/Android/LottieAndroidComposition.cs b/Lottie.Maui/Platforms/Android/LottieAndroidComposition.cs new file mode 100644 index 0000000..2a2816b --- /dev/null +++ b/Lottie.Maui/Platforms/Android/LottieAndroidComposition.cs @@ -0,0 +1,9 @@ +using Com.Airbnb.Lottie; + +namespace Lottie.Maui.Platforms.Android +{ + public class LottieAndroidComposition : LottieComposition, ILottieComposition + { + + } +} diff --git a/Lottie.Maui/Platforms/Android/LottieFailureListener.cs b/Lottie.Maui/Platforms/Android/LottieFailureListener.cs new file mode 100644 index 0000000..7c60e6d --- /dev/null +++ b/Lottie.Maui/Platforms/Android/LottieFailureListener.cs @@ -0,0 +1,24 @@ +using Android.Runtime; +using Com.Airbnb.Lottie; + +namespace Lottie.Maui.Platforms.Android +{ + public class LottieFailureListener : Java.Lang.Object, ILottieListener + { + public LottieFailureListener(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) + { + } + + public LottieFailureListener() + { + } + + public Action OnResultImpl { get; set; } + + public void OnResult(Java.Lang.Object p0) + { + var javaError = p0?.ToString(); + OnResultImpl?.Invoke(new Exception(javaError)); + } + } +} diff --git a/Lottie.Maui/Platforms/Android/LottieOnCompositionLoadedListener.cs b/Lottie.Maui/Platforms/Android/LottieOnCompositionLoadedListener.cs new file mode 100644 index 0000000..8c71a46 --- /dev/null +++ b/Lottie.Maui/Platforms/Android/LottieOnCompositionLoadedListener.cs @@ -0,0 +1,23 @@ +using Android.Runtime; +using Com.Airbnb.Lottie; + +namespace Lottie.Maui.Platforms.Android +{ + public class LottieOnCompositionLoadedListener : Java.Lang.Object, ILottieOnCompositionLoadedListener + { + public LottieOnCompositionLoadedListener() + { + } + + public LottieOnCompositionLoadedListener(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) + { + } + + public Action OnCompositionLoadedImpl { get; set; } + + public void OnCompositionLoaded(LottieComposition p0) + { + OnCompositionLoadedImpl?.Invoke(p0); + } + } +} diff --git a/Lottie.Maui/Platforms/Ios/AnimationViewExtensions.cs b/Lottie.Maui/Platforms/Ios/AnimationViewExtensions.cs new file mode 100644 index 0000000..7cc29cc --- /dev/null +++ b/Lottie.Maui/Platforms/Ios/AnimationViewExtensions.cs @@ -0,0 +1,96 @@ +using System.IO; +using Airbnb.Lottie; +using Foundation; +using Lottie.Maui; + +namespace Lottie.Platforms.Ios +{ + public static class AnimationViewExtensions + { + public static LOTComposition GetAnimation(this AnimationView animationView) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + var animation = animationView.Animation; + + LOTComposition composition = null; + switch (animationView.AnimationSource) + { + case AnimationSource.AssetOrBundle: + if (animation is string bundleAnimation) + { + if (!string.IsNullOrEmpty(animationView.ImageAssetsFolder)) + { + var bundle = NSBundle.FromPath(animationView.ImageAssetsFolder); + if (bundle != null) + composition = LOTComposition.AnimationNamed(bundleAnimation, bundle); + } + else + composition = LOTComposition.AnimationNamed(bundleAnimation); + } + break; + case AnimationSource.Url: + if (animation is string stringAnimation) + composition = LOTComposition.AnimationNamed(stringAnimation); + break; + case AnimationSource.Json: + if (animation is string jsonAnimation) + { + NSData objectData = NSData.FromString(jsonAnimation); + NSDictionary jsonData = (NSDictionary)NSJsonSerialization.Deserialize(objectData, NSJsonReadingOptions.MutableContainers, out _); + if (jsonData != null) + composition = LOTComposition.AnimationFromJSON(jsonData); + } + else if (animation is NSDictionary dictAnimation) + composition = LOTComposition.AnimationFromJSON(dictAnimation); + break; + case AnimationSource.Stream: + composition = animationView.GetAnimation(animation); + break; + case AnimationSource.EmbeddedResource: + composition = animationView.GetAnimation(animationView.GetStreamFromAssembly()); + break; + default: + break; + } + return composition; + } + + public static LOTComposition GetAnimation(this AnimationView animationView, object animation) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + LOTComposition composition = null; + switch (animation) + { + case string stringAnimation: + + //TODO: check if json + //animationView.SetAnimationFromJson(stringAnimation); + //TODO: check if url + //animationView.SetAnimationFromUrl(stringAnimation); + + composition = LOTComposition.AnimationNamed(stringAnimation); + break; + case Stream streamAnimation: + using (StreamReader reader = new StreamReader(streamAnimation)) + { + string json = reader.ReadToEnd(); + NSData objectData = NSData.FromString(json); + NSDictionary jsonData = (NSDictionary)NSJsonSerialization.Deserialize(objectData, NSJsonReadingOptions.MutableContainers, out _); + if (jsonData != null) + composition = LOTComposition.AnimationFromJSON(jsonData); + } + break; + case null: + composition = null; + break; + default: + break; + } + return composition; + } + } +} diff --git a/Lottie.Maui/Platforms/Ios/AnimationViewRenderer.cs b/Lottie.Maui/Platforms/Ios/AnimationViewRenderer.cs new file mode 100644 index 0000000..ec97282 --- /dev/null +++ b/Lottie.Maui/Platforms/Ios/AnimationViewRenderer.cs @@ -0,0 +1,236 @@ +using System.ComponentModel; +using Airbnb.Lottie; +using Foundation; +using Lottie.Maui; +using Microsoft.Maui.Controls.Compatibility.Platform.iOS; +using Microsoft.Maui.Controls.Platform; +using UIKit; + +//[assembly: ExportRenderer(typeof(AnimationView), typeof(AnimationViewRenderer)), Preserve(AllMembers = true)] + +namespace Lottie.Platforms.Ios +{ + public class AnimationViewRenderer : ViewRenderer + { + private LOTAnimationCompletionBlock _animationCompletionBlock; + private LOTAnimationView _animationView; + private UITapGestureRecognizer _gestureRecognizer; + private int repeatCount = 1; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e == null) + return; + + if (e.OldElement != null) + { + CleanupResources(); + } + + if (e.NewElement != null) + { + if (Control == null) + { + _animationCompletionBlock = new LOTAnimationCompletionBlock(AnimationCompletionBlock); + + _animationView = new LOTAnimationView() + { + AutoresizingMask = UIViewAutoresizing.All, + ContentMode = UIViewContentMode.ScaleAspectFit, + LoopAnimation = e.NewElement.RepeatMode == RepeatMode.Infinite, + AnimationSpeed = e.NewElement.Speed, + AnimationProgress = e.NewElement.Progress, + CacheEnable = e.NewElement.CacheComposition, + CompletionBlock = _animationCompletionBlock + }; + + var composition = e.NewElement.GetAnimation(); + _animationView.SceneModel = composition; + e.NewElement.InvokeAnimationLoaded(composition); + + e.NewElement.PlayCommand = new Command(() => + { + _animationView.PlayWithCompletion(AnimationCompletionBlock); + e.NewElement.InvokePlayAnimation(); + }); + e.NewElement.PauseCommand = new Command(() => + { + _animationView.Pause(); + e.NewElement.InvokePauseAnimation(); + }); + e.NewElement.ResumeCommand = new Command(() => + { + _animationView.PlayWithCompletion(AnimationCompletionBlock); + e.NewElement.InvokeResumeAnimation(); + }); + e.NewElement.StopCommand = new Command(() => + { + _animationView.Stop(); + e.NewElement.InvokeStopAnimation(); + }); + e.NewElement.ClickCommand = new Command(() => + { + //_animationView.Click(); + //e.NewElement.InvokeClick(); + }); + + e.NewElement.PlayMinAndMaxFrameCommand = new Command((object paramter) => + { + if (paramter is (int minFrame, int maxFrame)) + _animationView.PlayFromFrame(NSNumber.FromInt32(minFrame), NSNumber.FromInt32(maxFrame), AnimationCompletionBlock); + }); + e.NewElement.PlayMinAndMaxProgressCommand = new Command((object paramter) => + { + if (paramter is (float minProgress, float maxProgress)) + _animationView.PlayFromProgress(minProgress, maxProgress, AnimationCompletionBlock); + }); + e.NewElement.ReverseAnimationSpeedCommand = new Command(() => _animationView.AutoReverseAnimation = !_animationView.AutoReverseAnimation); + + _animationView.CacheEnable = e.NewElement.CacheComposition; + //_animationView.SetFallbackResource(e.NewElement.FallbackResource.); + //_animationView.Composition = e.NewElement.Composition; + + //TODO: makes animation stop with current default values + //_animationView.SetMinFrame(e.NewElement.MinFrame); + //_animationView.SetMinProgress(e.NewElement.MinProgress); + //_animationView.SetMaxFrame(e.NewElement.MaxFrame); + //_animationView.SetMaxProgress(e.NewElement.MaxProgress); + + _animationView.AnimationSpeed = e.NewElement.Speed; + _animationView.LoopAnimation = e.NewElement.RepeatMode == RepeatMode.Infinite; + //_animationView.RepeatCount = e.NewElement.RepeatCount; + //if (!string.IsNullOrEmpty(e.NewElement.ImageAssetsFolder)) + // _animationView.ImageAssetsFolder = e.NewElement.ImageAssetsFolder; + + //TODO: see if this needs to be enabled + //_animationView.ContentScaleFactor = Convert.ToSingle(e.NewElement.Scale); + + //_animationView.Frame = e.NewElement.Frame; + _animationView.AnimationProgress = e.NewElement.Progress; + + _gestureRecognizer = new UITapGestureRecognizer(e.NewElement.InvokeClick); + _animationView.AddGestureRecognizer(_gestureRecognizer); + + SetNativeControl(_animationView); + SetNeedsLayout(); + + if (e.NewElement.AutoPlay || e.NewElement.IsAnimating) + _animationView.PlayWithCompletion(AnimationCompletionBlock); + + //e.NewElement.Duration = TimeSpan.FromMilliseconds(_animationView.AnimationDuration); + } + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_animationView == null || Element == null || e == null) + return; + + if (e.PropertyName == AnimationView.AnimationProperty.PropertyName) + { + //CleanupResources(); + var composition = Element.GetAnimation(); + _animationView.SceneModel = composition; + Element.InvokeAnimationLoaded(composition); + + if (Element.AutoPlay || Element.IsAnimating) + _animationView.PlayWithCompletion(AnimationCompletionBlock); + } + + if (e.PropertyName == AnimationView.CacheCompositionProperty.PropertyName) + _animationView.CacheEnable = Element.CacheComposition; + + //_animationView.SetFallbackResource(e.NewElement.FallbackResource.); + //_animationView.Composition = e.NewElement.Composition; + + //if (e.PropertyName == AnimationView.MinFrameProperty.PropertyName) + // _animationView.SetMinFrame(Element.MinFrame); + + //if (e.PropertyName == AnimationView.MinProgressProperty.PropertyName) + // _animationView.SetMinProgress(Element.MinProgress); + + //if (e.PropertyName == AnimationView.MaxFrameProperty.PropertyName) + // _animationView.SetMaxFrame(Element.MaxFrame); + + //if (e.PropertyName == AnimationView.MaxProgressProperty.PropertyName) + // _animationView.SetMaxProgress(Element.MaxProgress); + + if (e.PropertyName == AnimationView.SpeedProperty.PropertyName) + _animationView.AnimationSpeed = Element.Speed; + + if (e.PropertyName == AnimationView.RepeatModeProperty.PropertyName) + _animationView.LoopAnimation = Element.RepeatMode == RepeatMode.Infinite; + + //if (e.PropertyName == AnimationView.RepeatCountProperty.PropertyName) + // _animationView.RepeatCount = Element.RepeatCount; + + //if (e.PropertyName == AnimationView.ImageAssetsFolderProperty.PropertyName && !string.IsNullOrEmpty(Element.ImageAssetsFolder)) + // _animationView.ImageAssetsFolder = Element.ImageAssetsFolder; + + //if (e.PropertyName == AnimationView.ScaleProperty.PropertyName) + // _animationView.Scale = Element.Scale; + + //if (e.PropertyName == AnimationView.FrameProperty.PropertyName) + // _animationView.Frame = Element.Frame; + + if (e.PropertyName == AnimationView.ProgressProperty.PropertyName) + _animationView.AnimationProgress = Element.Progress; + + base.OnElementPropertyChanged(sender, e); + } + + private void AnimationCompletionBlock(bool animationFinished) + { + if (animationFinished) + { + if (_animationView == null || Element == null) + return; + + Element.InvokeFinishedAnimation(); + + // Can be null depending if the user callback is executed very quickly + // and disposes the Xamarin.Forms page containing the Lottie view + if (_animationView == null || Element == null) + return; + + if (Element.RepeatMode == RepeatMode.Infinite) + { + Element.InvokeRepeatAnimation(); + _animationView.PlayWithCompletion(AnimationCompletionBlock); + } + else if (Element.RepeatMode == RepeatMode.Restart && repeatCount < Element.RepeatCount) + { + repeatCount++; + Element.InvokeRepeatAnimation(); + _animationView.PlayWithCompletion(AnimationCompletionBlock); + } + else if (Element.RepeatMode == RepeatMode.Restart && repeatCount == Element.RepeatCount) + { + repeatCount = 1; + } + } + } + + private void CleanupResources() + { + repeatCount = 1; + + if (_gestureRecognizer != null) + { + _animationView?.RemoveGestureRecognizer(_gestureRecognizer); + _gestureRecognizer.Dispose(); + _gestureRecognizer = null; + } + + if (_animationView != null) + { + _animationView.RemoveFromSuperview(); + _animationView.Dispose(); + _animationView = null; + } + } + } +} diff --git a/Lottie.Maui/Platforms/Mac/AnimationViewExtensions.cs b/Lottie.Maui/Platforms/Mac/AnimationViewExtensions.cs new file mode 100644 index 0000000..5190233 --- /dev/null +++ b/Lottie.Maui/Platforms/Mac/AnimationViewExtensions.cs @@ -0,0 +1,89 @@ +using System.IO; +using Airbnb.Lottie; +using Foundation; + +namespace Lottie.Forms.Platforms.Mac +{ + public static class AnimationViewExtensions + { + public static LOTComposition GetAnimation(this AnimationView animationView) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + var animation = animationView.Animation; + + LOTComposition composition = null; + switch (animationView.AnimationSource) + { + case AnimationSource.AssetOrBundle: + if (animation is string bundleAnimation) + { + if (!string.IsNullOrEmpty(animationView.ImageAssetsFolder)) + composition = LOTComposition.AnimationNamed(bundleAnimation, NSBundle.FromPath(animationView.ImageAssetsFolder)); + else + composition = LOTComposition.AnimationNamed(bundleAnimation); + } + break; + case AnimationSource.Url: + if (animation is string stringAnimation) + composition = LOTComposition.AnimationNamed(stringAnimation); + break; + case AnimationSource.Json: + if (animation is string jsonAnimation) + { + NSData objectData = NSData.FromString(jsonAnimation); + NSDictionary jsonData = (NSDictionary)NSJsonSerialization.Deserialize(objectData, NSJsonReadingOptions.MutableContainers, out _); + composition = LOTComposition.AnimationFromJSON(jsonData); + } + else if (animation is NSDictionary dictAnimation) + composition = LOTComposition.AnimationFromJSON(dictAnimation); + break; + case AnimationSource.Stream: + composition = animationView.GetAnimation(animation); + break; + case AnimationSource.EmbeddedResource: + composition = animationView.GetAnimation(animationView.GetStreamFromAssembly()); + break; + default: + break; + } + return composition; + } + + public static LOTComposition GetAnimation(this AnimationView animationView, object animation) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + LOTComposition composition = null; + switch (animation) + { + case string stringAnimation: + + //TODO: check if json + //animationView.SetAnimationFromJson(stringAnimation); + //TODO: check if url + //animationView.SetAnimationFromUrl(stringAnimation); + + composition = LOTComposition.AnimationNamed(stringAnimation); + break; + case Stream streamAnimation: + using (StreamReader reader = new StreamReader(streamAnimation)) + { + string json = reader.ReadToEnd(); + NSData objectData = NSData.FromString(json); + NSDictionary jsonData = (NSDictionary)NSJsonSerialization.Deserialize(objectData, NSJsonReadingOptions.MutableContainers, out _); + composition = LOTComposition.AnimationFromJSON(jsonData); + } + break; + case null: + composition = null; + break; + default: + break; + } + return composition; + } + } +} diff --git a/Lottie.Maui/Platforms/Mac/AnimationViewRenderer.cs b/Lottie.Maui/Platforms/Mac/AnimationViewRenderer.cs new file mode 100644 index 0000000..d2c6fc1 --- /dev/null +++ b/Lottie.Maui/Platforms/Mac/AnimationViewRenderer.cs @@ -0,0 +1,226 @@ +using System.ComponentModel; +using Airbnb.Lottie; +using AppKit; +using Foundation; +using Lottie.Forms; +using Lottie.Forms.Platforms.Mac; +using Xamarin.Forms; +using Xamarin.Forms.Platform.MacOS; + +[assembly: ExportRenderer(typeof(AnimationView), typeof(AnimationViewRenderer)), Xamarin.Forms.Internals.Preserve(AllMembers = true)] + +namespace Lottie.Forms.Platforms.Mac +{ + public class AnimationViewRenderer : ViewRenderer + { + private LOTAnimationCompletionBlock _animationCompletionBlock; + private LOTAnimationView _animationView; + private NSClickGestureRecognizer _gestureRecognizer; + private int repeatCount = 1; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e == null) + return; + + if (e.OldElement != null) + { + CleanupResources(); + } + + if (e.NewElement != null) + { + if (Control == null) + { + _animationCompletionBlock = new LOTAnimationCompletionBlock(AnimationCompletionBlock); + + _animationView = new LOTAnimationView() + { + ContentMode = LOTViewContentMode.ScaleAspectFill, + Frame = Bounds, + AutoresizingMask = NSViewResizingMask.WidthSizable | NSViewResizingMask.HeightSizable, + LoopAnimation = e.NewElement.RepeatMode == RepeatMode.Infinite, + AnimationSpeed = e.NewElement.Speed, + AnimationProgress = e.NewElement.Progress, + CacheEnable = e.NewElement.CacheComposition, + CompletionBlock = _animationCompletionBlock + }; + + var composition = e.NewElement.GetAnimation(); + _animationView.SceneModel = composition; + e.NewElement.InvokeAnimationLoaded(composition); + + e.NewElement.PlayCommand = new Command(() => + { + _animationView.PlayWithCompletion(AnimationCompletionBlock); + e.NewElement.InvokePlayAnimation(); + }); + e.NewElement.PauseCommand = new Command(() => + { + _animationView.Pause(); + e.NewElement.InvokePauseAnimation(); + }); + e.NewElement.ResumeCommand = new Command(() => + { + _animationView.PlayWithCompletion(AnimationCompletionBlock); + e.NewElement.InvokeResumeAnimation(); + }); + e.NewElement.StopCommand = new Command(() => + { + _animationView.Stop(); + e.NewElement.InvokeStopAnimation(); + }); + e.NewElement.ClickCommand = new Command(() => + { + //_animationView.Click(); + //e.NewElement.InvokeClick(); + }); + + e.NewElement.PlayMinAndMaxFrameCommand = new Command((object paramter) => + { + if (paramter is (int minFrame, int maxFrame)) + _animationView.PlayFromFrame(NSNumber.FromInt32(minFrame), NSNumber.FromInt32(maxFrame), AnimationCompletionBlock); + }); + e.NewElement.PlayMinAndMaxProgressCommand = new Command((object paramter) => + { + if (paramter is (float minProgress, float maxProgress)) + _animationView.PlayFromProgress(minProgress, maxProgress, AnimationCompletionBlock); + }); + e.NewElement.ReverseAnimationSpeedCommand = new Command(() => _animationView.AutoReverseAnimation = !_animationView.AutoReverseAnimation); + + _animationView.CacheEnable = e.NewElement.CacheComposition; + //_animationView.SetFallbackResource(e.NewElement.FallbackResource.); + //_animationView.Composition = e.NewElement.Composition; + + //TODO: makes animation stop with current default values + //_animationView.SetMinFrame(e.NewElement.MinFrame); + //_animationView.SetMinProgress(e.NewElement.MinProgress); + //_animationView.SetMaxFrame(e.NewElement.MaxFrame); + //_animationView.SetMaxProgress(e.NewElement.MaxProgress); + + _animationView.AnimationSpeed = e.NewElement.Speed; + _animationView.LoopAnimation = e.NewElement.RepeatMode == RepeatMode.Infinite; + //_animationView.RepeatCount = e.NewElement.RepeatCount; + //if (!string.IsNullOrEmpty(e.NewElement.ImageAssetsFolder)) + // _animationView.ImageAssetsFolder = e.NewElement.ImageAssetsFolder; + //_animationView.ContentScaleFactor = e.NewElement.Scale; + //_animationView.Frame = e.NewElement.Frame; + _animationView.AnimationProgress = e.NewElement.Progress; + + _gestureRecognizer = new NSClickGestureRecognizer(e.NewElement.InvokeClick); + _animationView.AddGestureRecognizer(_gestureRecognizer); + + SetNativeControl(_animationView); + //SetNeedsLayout(); + + if (e.NewElement.AutoPlay || e.NewElement.IsAnimating) + _animationView.PlayWithCompletion(AnimationCompletionBlock); + + //e.NewElement.Duration = TimeSpan.FromMilliseconds(_animationView.AnimationDuration); + } + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_animationView == null || Element == null || e == null) + return; + + if (e.PropertyName == AnimationView.AnimationProperty.PropertyName) + { + //CleanupResources(); + var composition = Element.GetAnimation(); + _animationView.SceneModel = composition; + Element.InvokeAnimationLoaded(composition); + + if (Element.AutoPlay || Element.IsAnimating) + _animationView.PlayWithCompletion(AnimationCompletionBlock); + } + + if (e.PropertyName == AnimationView.CacheCompositionProperty.PropertyName) + _animationView.CacheEnable = Element.CacheComposition; + + //_animationView.SetFallbackResource(e.NewElement.FallbackResource.); + //_animationView.Composition = e.NewElement.Composition; + + //if (e.PropertyName == AnimationView.MinFrameProperty.PropertyName) + // _animationView.SetMinFrame(Element.MinFrame); + + //if (e.PropertyName == AnimationView.MinProgressProperty.PropertyName) + // _animationView.SetMinProgress(Element.MinProgress); + + //if (e.PropertyName == AnimationView.MaxFrameProperty.PropertyName) + // _animationView.SetMaxFrame(Element.MaxFrame); + + //if (e.PropertyName == AnimationView.MaxProgressProperty.PropertyName) + // _animationView.SetMaxProgress(Element.MaxProgress); + + if (e.PropertyName == AnimationView.SpeedProperty.PropertyName) + _animationView.AnimationSpeed = Element.Speed; + + if (e.PropertyName == AnimationView.RepeatModeProperty.PropertyName) + _animationView.LoopAnimation = Element.RepeatMode == RepeatMode.Infinite; + + //if (e.PropertyName == AnimationView.RepeatCountProperty.PropertyName) + // _animationView.RepeatCount = Element.RepeatCount; + + //if (e.PropertyName == AnimationView.ImageAssetsFolderProperty.PropertyName && !string.IsNullOrEmpty(Element.ImageAssetsFolder)) + // _animationView.ImageAssetsFolder = Element.ImageAssetsFolder; + + //if (e.PropertyName == AnimationView.ScaleProperty.PropertyName) + // _animationView.Scale = Element.Scale; + + //if (e.PropertyName == AnimationView.FrameProperty.PropertyName) + // _animationView.Frame = Element.Frame; + + if (e.PropertyName == AnimationView.ProgressProperty.PropertyName) + _animationView.AnimationProgress = Element.Progress; + + base.OnElementPropertyChanged(sender, e); + } + + private void AnimationCompletionBlock(bool animationFinished) + { + if (animationFinished) + { + Element?.InvokeFinishedAnimation(); + if (Element.RepeatMode == RepeatMode.Infinite) + { + Element.InvokeRepeatAnimation(); + _animationView.PlayWithCompletion(AnimationCompletionBlock); + } + else if (Element.RepeatMode == RepeatMode.Restart && repeatCount < Element.RepeatCount) + { + repeatCount++; + Element.InvokeRepeatAnimation(); + _animationView.PlayWithCompletion(AnimationCompletionBlock); + } + else if (Element.RepeatMode == RepeatMode.Restart && repeatCount == Element.RepeatCount) + { + repeatCount = 1; + } + } + } + + private void CleanupResources() + { + repeatCount = 1; + + if (_gestureRecognizer != null) + { + _animationView?.RemoveGestureRecognizer(_gestureRecognizer); + _gestureRecognizer.Dispose(); + _gestureRecognizer = null; + } + + if (_animationView != null) + { + _animationView.RemoveFromSuperview(); + _animationView.Dispose(); + _animationView = null; + } + } + } +} diff --git a/Lottie.Maui/Platforms/Tizen/AnimationViewExtensions.cs b/Lottie.Maui/Platforms/Tizen/AnimationViewExtensions.cs new file mode 100644 index 0000000..8f96cf0 --- /dev/null +++ b/Lottie.Maui/Platforms/Tizen/AnimationViewExtensions.cs @@ -0,0 +1,74 @@ +using System.IO; +using ElottieSharp; +using Xamarin.Forms.Platform.Tizen; + +namespace Lottie.Forms.Platforms.Tizen +{ + public static class AnimationViewExtensions + { + public static void TrySetAnimation(this LottieAnimationView lottieAnimationView, AnimationView animationView) + { + if (lottieAnimationView == null) + throw new ArgumentNullException(nameof(lottieAnimationView)); + + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + switch (animationView.AnimationSource) + { + case AnimationSource.AssetOrBundle: + lottieAnimationView.TrySetAnimation(animationView, ResourcePath.GetPath(animationView.Animation as string)); + break; + case AnimationSource.Url: + if (animationView.Animation is string stringAnimation) + lottieAnimationView.SetAnimation(stringAnimation); + break; + case AnimationSource.Json: + if (animationView.Animation is string jsonAnimation) + lottieAnimationView.SetAnimation(jsonAnimation); + break; + case AnimationSource.Stream: + lottieAnimationView.TrySetAnimation(animationView, animationView.Animation); + break; + case AnimationSource.EmbeddedResource: + lottieAnimationView.TrySetAnimation(animationView, animationView.GetStreamFromAssembly()); + break; + default: + break; + } + } + + public static void TrySetAnimation(this LottieAnimationView lottieAnimationView, AnimationView animationView, object animation) + { + if (lottieAnimationView == null) + throw new ArgumentNullException(nameof(lottieAnimationView)); + + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + switch (animation) + { + case int intAnimation: + //lottieAnimationView.SetAnimation(intAnimation); + break; + case string stringAnimation: + + //TODO: check if json + //animationView.SetAnimationFromJson(stringAnimation); + //TODO: check if url + //animationView.SetAnimationFromUrl(stringAnimation); + + lottieAnimationView.SetAnimation(stringAnimation); + break; + case Stream streamAnimation: + //lottieAnimationView.SetAnimation(streamAnimation, null); + break; + case null: + lottieAnimationView.Stop(); + break; + default: + break; + } + } + } +} diff --git a/Lottie.Maui/Platforms/Tizen/AnimationViewRenderer.cs b/Lottie.Maui/Platforms/Tizen/AnimationViewRenderer.cs new file mode 100644 index 0000000..631f3c2 --- /dev/null +++ b/Lottie.Maui/Platforms/Tizen/AnimationViewRenderer.cs @@ -0,0 +1,217 @@ +using System.ComponentModel; +using ElottieSharp; +using Lottie.Forms; +using Lottie.Forms.Platforms.Tizen; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Tizen; + +[assembly: ExportRenderer(typeof(AnimationView), typeof(AnimationViewRenderer)), Xamarin.Forms.Internals.Preserve(AllMembers = true)] +namespace Lottie.Forms.Platforms.Tizen +{ + public class AnimationViewRenderer : ViewRenderer + { + private LottieAnimationView _animationView; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e == null) + return; + + if (e.OldElement != null) + { + _animationView.Finished -= _animationView_Finished; + _animationView.Paused -= _animationView_Paused; + _animationView.Shown -= _animationView_Shown; + _animationView.Started -= _animationView_Started; + _animationView.Stopped -= _animationView_Stopped; + _animationView.FrameUpdated -= _animationView_FrameUpdated; + } + + if (e.NewElement != null) + { + if (Control == null) + { + _animationView = new LottieAnimationView(Xamarin.Forms.Forms.NativeParent); + _animationView.Finished += _animationView_Finished; + _animationView.Paused += _animationView_Paused; + _animationView.Shown += _animationView_Shown; + _animationView.Started += _animationView_Started; + _animationView.Stopped += _animationView_Stopped; + _animationView.FrameUpdated += _animationView_FrameUpdated; + + /* + _clickListener = new ClickListener + { + OnClickImpl = () => e.NewElement.InvokeClick() + };*/ + + _animationView.TrySetAnimation(e.NewElement); + + e.NewElement.PlayCommand = new Command(() => _animationView.Play()); + e.NewElement.PauseCommand = new Command(() => _animationView.Pause()); + e.NewElement.ResumeCommand = new Command(() => _animationView.Play()); + e.NewElement.StopCommand = new Command(() => _animationView.Stop()); + //e.NewElement.ClickCommand = new Command(() => _animationView.PerformClick()); + + e.NewElement.PlayMinAndMaxFrameCommand = new Command((object paramter) => + { + if (paramter is (int minFrame, int maxFrame)) + { + _animationView.Play(minFrame, maxFrame); + } + }); + e.NewElement.PlayMinAndMaxProgressCommand = new Command((object paramter) => + { + if (paramter is (float minProgress, float maxProgress)) + { + _animationView.Play(minProgress, maxProgress); + } + }); + //e.NewElement.ReverseAnimationSpeedCommand = new Command(() => _animationView.ReverseAnimationSpeed()); + + //_animationView.SetCacheComposition(e.NewElement.CacheComposition); + //_animationView.SetFallbackResource(e.NewElement.FallbackResource.); + //_animationView.Composition = e.NewElement.Composition; + + //if (e.NewElement.MinFrame != int.MinValue) + // _animationView.SetMinFrame(e.NewElement.MinFrame); + if (e.NewElement.MinProgress != float.MinValue) + _animationView.MinimumProgress = e.NewElement.MinProgress; + //if (e.NewElement.MaxFrame != int.MinValue) + // _animationView.SetMaxFrame(e.NewElement.MaxFrame); + if (e.NewElement.MaxProgress != float.MinValue) + _animationView.MaximumProgress = e.NewElement.MaxProgress; + + _animationView.Speed = e.NewElement.Speed; + _animationView.AutoRepeat = e.NewElement.RepeatMode == RepeatMode.Infinite; + //_animationView.RepeatCount = e.NewElement.RepeatCount; + + //if (!string.IsNullOrEmpty(e.NewElement.ImageAssetsFolder)) + // _animationView.ImageAssetsFolder = e.NewElement.ImageAssetsFolder; + //_animationView.Scale = e.NewElement.Scale; + //_animationView.Frame = e.NewElement.Frame; + _animationView.SeekTo(Element.Progress); + + SetNativeControl(_animationView); + + if (e.NewElement.AutoPlay || e.NewElement.IsAnimating) + _animationView.Play(); + + //e.NewElement.Duration = _animationView.DurationTime; + e.NewElement.IsAnimating = _animationView.IsPlaying; + } + } + } + + private void _animationView_FrameUpdated(object sender, FrameEventArgs e) + { + Element?.InvokeAnimationUpdate(e.CurrentFrame); + } + + private void _animationView_Stopped(object sender, System.EventArgs e) + { + Element?.InvokeStopAnimation(); + } + + private void _animationView_Started(object sender, System.EventArgs e) + { + Element?.InvokePlayAnimation(); + } + + private void _animationView_Shown(object sender, System.EventArgs e) + { + + } + + private void _animationView_Paused(object sender, System.EventArgs e) + { + Element?.InvokePauseAnimation(); + } + + private void _animationView_Finished(object sender, System.EventArgs e) + { + Element?.InvokeFinishedAnimation(); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_animationView == null || Element == null || e == null) + return; + + if (e.PropertyName == AnimationView.AnimationProperty.PropertyName) + { + _animationView.TrySetAnimation(Element); + + if (Element.AutoPlay || Element.IsAnimating) + _animationView.Play(); + } + + //if (e.PropertyName == AnimationView.AutoPlayProperty.PropertyName) + // _animationView.AutoPlay = (Element.AutoPlay); + + //if (e.PropertyName == AnimationView.CacheCompositionProperty.PropertyName) + // _animationView.SetCacheComposition(Element.CacheComposition); + + //if (e.PropertyName == AnimationView.FallbackResource.PropertyName) + // _animationView.SetFallbackResource(e.NewElement.FallbackResource); + + //if (e.PropertyName == AnimationView.Composition.PropertyName) + // _animationView.Composition = e.NewElement.Composition; + + //if (e.PropertyName == AnimationView.MinFrameProperty.PropertyName) + // _animationView.SetMinFrame(Element.MinFrame); + + if (e.PropertyName == AnimationView.MinProgressProperty.PropertyName) + _animationView.MinimumProgress = Element.MinProgress; + + //if (e.PropertyName == AnimationView.MaxFrameProperty.PropertyName) + // _animationView.SetMaxFrame(Element.MaxFrame); + + if (e.PropertyName == AnimationView.MaxProgressProperty.PropertyName) + _animationView.MaximumProgress = Element.MaxProgress; + + if (e.PropertyName == AnimationView.SpeedProperty.PropertyName) + _animationView.Speed = (double)new decimal(Element.Speed); + + if (e.PropertyName == AnimationView.RepeatModeProperty.PropertyName) + _animationView.AutoRepeat = Element.RepeatMode == RepeatMode.Infinite; + + //if (e.PropertyName == AnimationView.RepeatCountProperty.PropertyName) + // _animationView.RepeatCount = Element.RepeatCount; + + //if (e.PropertyName == AnimationView.ImageAssetsFolderProperty.PropertyName && !string.IsNullOrEmpty(Element.ImageAssetsFolder)) + // _animationView.ImageAssetsFolder = Element.ImageAssetsFolder; + + //if (e.PropertyName == AnimationView.ScaleProperty.PropertyName) + // _animationView.Scale = Element.Scale; + + //if (e.PropertyName == AnimationView.FrameProperty.PropertyName) + // _animationView.Frame = Element.Frame; + + if (e.PropertyName == AnimationView.ProgressProperty.PropertyName) + _animationView.SeekTo(Element.Progress); + + base.OnElementPropertyChanged(sender, e); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_animationView != null) + { + _animationView.Finished -= _animationView_Finished; + _animationView.Paused -= _animationView_Paused; + _animationView.Shown -= _animationView_Shown; + _animationView.Started -= _animationView_Started; + _animationView.Stopped -= _animationView_Stopped; + _animationView.FrameUpdated -= _animationView_FrameUpdated; + } + } + + base.Dispose(disposing); + } + } +} diff --git a/Lottie.Maui/Platforms/Uap/AnimationViewExtensions.cs b/Lottie.Maui/Platforms/Uap/AnimationViewExtensions.cs new file mode 100644 index 0000000..62f6f01 --- /dev/null +++ b/Lottie.Maui/Platforms/Uap/AnimationViewExtensions.cs @@ -0,0 +1,76 @@ +using System.IO; +using Microsoft.Toolkit.Uwp.UI.Lottie; +using Microsoft.UI.Xaml.Controls; + +namespace Lottie.Forms.Platforms.Uap +{ + public static class AnimationViewExtensions + { + public static async Task GetAnimationAsync(this AnimationView animationView) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + IAnimatedVisualSource animatedVisualSource = null; + switch (animationView.AnimationSource) + { + case AnimationSource.AssetOrBundle: + if (animationView.Animation is string assetAnnimation) + { + var assets = "Assets"; + + if (!string.IsNullOrEmpty(animationView.ImageAssetsFolder)) + { + assets = animationView.ImageAssetsFolder; + } + + var path = $"ms-appx:///{assets}/{assetAnnimation}"; + animatedVisualSource = await animationView.GetAnimationAsync(path); + } + break; + case AnimationSource.Url: + if (animationView.Animation is string stringAnimation) + animatedVisualSource = await animationView.GetAnimationAsync(stringAnimation); + break; + case AnimationSource.Json: + //if (animation is string jsonAnimation) + // animatedVisualSource = LottieVisualSource.CreateFromString(jsonAnimation); + break; + case AnimationSource.Stream: + animatedVisualSource = await animationView.GetAnimationAsync(animationView.Animation); + break; + case AnimationSource.EmbeddedResource: + animatedVisualSource = await animationView.GetAnimationAsync(animationView.GetStreamFromAssembly()); + break; + default: + break; + } + return animatedVisualSource; + } + + public static async Task GetAnimationAsync(this AnimationView animationView, object animation) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + IAnimatedVisualSource animatedVisualSource = null; + switch (animation) + { + case string stringAnimation: + animatedVisualSource = LottieVisualSource.CreateFromString(stringAnimation); + break; + case Stream streamAnimation: + var source = new LottieVisualSource(); + await source.SetSourceAsync(streamAnimation.AsInputStream()); + animatedVisualSource = source; + break; + case null: + animatedVisualSource = null; + break; + default: + break; + } + return animatedVisualSource; + } + } +} diff --git a/Lottie.Maui/Platforms/Uap/AnimationViewRenderer.cs b/Lottie.Maui/Platforms/Uap/AnimationViewRenderer.cs new file mode 100644 index 0000000..a1ec423 --- /dev/null +++ b/Lottie.Maui/Platforms/Uap/AnimationViewRenderer.cs @@ -0,0 +1,272 @@ +using System.ComponentModel; +using Lottie.Forms; +using Lottie.Forms.Platforms.Uap; +using Microsoft.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Xamarin.Forms; +using Xamarin.Forms.Platform.UWP; + +[assembly: ExportRenderer(typeof(AnimationView), typeof(AnimationViewRenderer)), Xamarin.Forms.Internals.Preserve(AllMembers = true)] + +namespace Lottie.Forms.Platforms.Uap +{ + public class AnimationViewRenderer : ViewRenderer + { + private AnimatedVisualPlayer _animationView; + //private bool _needToReverseAnimationSpeed; + //private bool _needToResetFrames; + + public static int PlayDelay { get; set; } = 1000; + + protected override async void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e == null) + return; + + if (e.OldElement != null) + { + _animationView.Loaded -= _animationView_Loaded; + _animationView.Tapped -= _animationView_Tapped; + } + + if (e.NewElement != null) + { + if (Control == null) + { + _animationView = new AnimatedVisualPlayer + { + AutoPlay = false, + PlaybackRate = e.NewElement.Speed, + //Scale = new System.Numerics.Vector3(e.NewElement.Scale) + }; + _animationView.Loaded += _animationView_Loaded; + _animationView.Tapped += _animationView_Tapped; + + var composition = await e.NewElement.GetAnimationAsync(); + _animationView.Source = composition; + e.NewElement.InvokeAnimationLoaded(composition); + + e.NewElement.PlayCommand = new Command(() => + { + _animationView.PlayAsync(0, 1, Element.RepeatMode == RepeatMode.Infinite).AsTask(); + e.NewElement.InvokePlayAnimation(); + }); + e.NewElement.PauseCommand = new Command(() => + { + _animationView.Pause(); + e.NewElement.InvokePauseAnimation(); + }); + e.NewElement.ResumeCommand = new Command(() => + { + _animationView.Resume(); + e.NewElement.InvokeResumeAnimation(); + }); + e.NewElement.StopCommand = new Command(() => + { + _animationView.Stop(); + e.NewElement.InvokeStopAnimation(); + }); + e.NewElement.ClickCommand = new Command(() => + { + //_animationView.Click(); + //e.NewElement.InvokeClick(); + }); + + e.NewElement.PlayMinAndMaxFrameCommand = new Command((object paramter) => + { + if (paramter is (int minFrame, int maxFrame)) + { + _ = _animationView.PlayAsync(minFrame, maxFrame, Element.RepeatMode == RepeatMode.Infinite).AsTask(); + } + }); + e.NewElement.PlayMinAndMaxProgressCommand = new Command((object paramter) => + { + if (paramter is (float minProgress, float maxProgress)) + { + _ = _animationView.PlayAsync(minProgress, maxProgress, Element.RepeatMode == RepeatMode.Infinite).AsTask(); + } + }); + e.NewElement.ReverseAnimationSpeedCommand = new Command(() => + { + _animationView.PlaybackRate = -1; + }); + + e.NewElement.Duration = _animationView.Duration.Ticks; + e.NewElement.IsAnimating = _animationView.IsPlaying; + + SetNativeControl(_animationView); + } + } + } + + private void _animationView_Tapped(object sender, TappedRoutedEventArgs e) + { + Element?.InvokeClick(); + } + + private async void _animationView_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + if (_animationView == null || Element == null || e == null) + return; + + if (Element.AutoPlay || Element.IsAnimating) + { + await Task.Delay(PlayDelay); + _ = _animationView.PlayAsync(0, 1, Element.RepeatMode == RepeatMode.Infinite).AsTask(); + + Element.IsAnimating = _animationView.IsPlaying; + Element.InvokePlayAnimation(); + } + } + + protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_animationView == null || Element == null || e == null) + return; + + if (e.PropertyName == AnimationView.AnimationProperty.PropertyName) + { + var composition = await Element.GetAnimationAsync(); + _animationView.Source = composition; + Element.InvokeAnimationLoaded(composition); + } + + if (e.PropertyName == AnimationView.AutoPlayProperty.PropertyName) + _animationView.AutoPlay = Element.AutoPlay; + + //if (e.PropertyName == AnimationView.CacheCompositionProperty.PropertyName) + // _animationView.SetCacheComposition(Element.CacheComposition); + + //if (e.PropertyName == AnimationView.FallbackResource.PropertyName) + // _animationView.SetFallbackResource(e.NewElement.FallbackResource); + + //if (e.PropertyName == AnimationView.Composition.PropertyName) + // _animationView.Composition = e.NewElement.Composition; + + //if (e.PropertyName == AnimationView.MinFrameProperty.PropertyName) + // _animationView.SetMinFrame(Element.MinFrame); + + //if (e.PropertyName == AnimationView.MinProgressProperty.PropertyName) + // _animationView.SetMinProgress(Element.MinProgress); + + //if (e.PropertyName == AnimationView.MaxFrameProperty.PropertyName) + // _animationView.SetMaxFrame(Element.MaxFrame); + + //if (e.PropertyName == AnimationView.MaxProgressProperty.PropertyName) + // _animationView.SetMaxProgress(Element.MaxProgress); + + if (e.PropertyName == AnimationView.SpeedProperty.PropertyName) + _animationView.PlaybackRate = Element.Speed; + + //if (e.PropertyName == AnimationView.RepeatModeProperty.PropertyName) + // _animationView.RepeatMode = (int)Element.RepeatMode; + + //if (e.PropertyName == AnimationView.RepeatCountProperty.PropertyName) + // _animationView.RepeatCount = Element.RepeatCount; + + //if (e.PropertyName == AnimationView.ImageAssetsFolderProperty.PropertyName && !string.IsNullOrEmpty(Element.ImageAssetsFolder)) + // _animationView.ImageAssetsFolder = Element.ImageAssetsFolder; + + //if (e.PropertyName == AnimationView.ScaleProperty.PropertyName) + // _animationView.Scale = Element.Scale; + + //if (e.PropertyName == AnimationView.FrameProperty.PropertyName) + // _animationView.Frame = Element.Frame; + + if (e.PropertyName == AnimationView.ProgressProperty.PropertyName) + _animationView.SetProgress(Element.Progress); + + base.OnElementPropertyChanged(sender, e); + } + + /* + + private void PrepareReverseAnimation(Action action, float from, float to) + { + var minValue = Math.Min(from, to); + var maxValue = Math.Max(from, to); + var needReverse = from > to; + + action(minValue, maxValue); + + if (needReverse && !_needToReverseAnimationSpeed) + { + _needToReverseAnimationSpeed = true; + _animationView.PlaybackRate = -1; + } + + Play(minValue, maxValue); + } + + private void OnPlayProgressSegment(object sender, ProgressSegmentEventArgs e) + { + if (_animationView != null && Element != null) + { + PrepareReverseAnimation((min, max) => Play(min, max), e.From, e.To); + } + } + + private void OnPlayFrameSegment(object sender, FrameSegmentEventArgs e) + { + if (_animationView != null && Element != null) + { + PrepareReverseAnimation((min, max) => + { + Play(min, max); + _needToResetFrames = true; + }, e.From, e.To); + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (_animationView == null || Element == null) + { + + return; + } + + if (e.PropertyName == AnimationView.AnimationProperty.PropertyName) + { + if (string.IsNullOrEmpty(Element.Animation)) + { + _animationView.Stop(); + RestAnimation(); + Element.Duration = TimeSpan.Zero; + Element.IsPlaying = false; + return; + } + + SetAnimation(Element.Animation); + Element.Duration = _animationView.Duration; + + #pragma warning disable CS0618 // Type or member is obsolete + if (Element.AutoPlay || Element.IsPlaying) + #pragma warning restore CS0618 // Type or member is obsolete + { + Play(); + } + else + { + _animationView.Stop(); + } + } + else if (e.PropertyName == AnimationView.IsPlayingProperty.PropertyName && !string.IsNullOrEmpty(Element.Animation)) + { + if (Element?.IsPlaying == true) + { + Play(); + } + else + { + _animationView?.Pause(); + } + } + } + */ + } +} diff --git a/Lottie.Maui/Platforms/Wpf/AnimationViewExtensions.cs b/Lottie.Maui/Platforms/Wpf/AnimationViewExtensions.cs new file mode 100644 index 0000000..843fe57 --- /dev/null +++ b/Lottie.Maui/Platforms/Wpf/AnimationViewExtensions.cs @@ -0,0 +1,76 @@ +using System.IO; +using LottieSharp; + +namespace Lottie.Forms.Platforms.Wpf +{ + public static class AnimationViewExtensions + { + public static LottieComposition GetAnimation(this AnimationView animationView) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + LottieComposition composition = null; + switch (animationView.AnimationSource) + { + case AnimationSource.AssetOrBundle: + if (animationView.Animation is string assetAnnimation) + { + var assets = "Assets"; + + if (!string.IsNullOrEmpty(animationView.ImageAssetsFolder)) + { + assets = animationView.ImageAssetsFolder; + } + + var path = $"ms-appx:///{assets}/{assetAnnimation}"; + composition = animationView.GetAnimation(path); + } + break; + case AnimationSource.Url: + if (animationView.Animation is string stringAnimation) + composition = animationView.GetAnimation(stringAnimation); + break; + case AnimationSource.Json: + //if (animation is string jsonAnimation) + // animatedVisualSource = LottieVisualSource.CreateFromString(jsonAnimation); + break; + case AnimationSource.Stream: + composition = animationView.GetAnimation(animationView.Animation); + break; + case AnimationSource.EmbeddedResource: + composition = animationView.GetAnimation(animationView.GetStreamFromAssembly()); + break; + default: + break; + } + return composition; + } + + public static LottieComposition GetAnimation(this AnimationView animationView, object animation) + { + if (animationView == null) + throw new ArgumentNullException(nameof(animationView)); + + LottieComposition composition = null; + switch (animation) + { + case string stringAnimation: + composition = LottieCompositionFactory.FromJsonStringSync(stringAnimation, null).Value; + break; + case Stream streamAnimation: + //TODO: api for this will be added in next Lottie UWP update + //var source = new LottieVisualSource(); + //source.SetSourceAsync(streamAnimation); + //animatedVisualSource = source; + break; + case null: + composition = null; + break; + default: + break; + } + return composition; + } + } +} diff --git a/Lottie.Maui/Platforms/Wpf/AnimationViewRenderer.cs b/Lottie.Maui/Platforms/Wpf/AnimationViewRenderer.cs new file mode 100644 index 0000000..0c0d7b5 --- /dev/null +++ b/Lottie.Maui/Platforms/Wpf/AnimationViewRenderer.cs @@ -0,0 +1,191 @@ +using System.ComponentModel; +using Lottie.Forms; +using Lottie.Forms.Platforms.Wpf; +using LottieSharp; +using Xamarin.Forms; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Platform.WPF; + +[assembly: ExportRenderer(typeof(AnimationView), typeof(AnimationViewRenderer)), Preserve(AllMembers = true)] + +namespace Lottie.Forms.Platforms.Wpf +{ + public class AnimationViewRenderer : ViewRenderer + { + private LottieAnimationView _animationView; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e == null) + return; + + if (e.OldElement != null) + { + _animationView.Loaded -= _animationView_Loaded; + _animationView.MouseDown -= _animationView_MouseDown; + } + + if (e.NewElement != null) + { + if (Control == null) + { + _animationView = new LottieAnimationView + { + AutoPlay = false, + //PlaybackRate = e.NewElement.Speed, + //Scale = new System.Numerics.Vector3(e.NewElement.Scale) + }; + _animationView.Loaded += _animationView_Loaded; + _animationView.MouseDown += _animationView_MouseDown; + + //_animationView.FileName = e.NewElement.Animation as string; + var composition = e.NewElement.GetAnimation(); + _animationView.Composition = composition; + e.NewElement.InvokeAnimationLoaded(composition); + + e.NewElement.PlayCommand = new Command(() => + { + _animationView.PlayAnimation(); + e.NewElement.InvokePlayAnimation(); + }); + e.NewElement.PauseCommand = new Command(() => + { + _animationView.PauseAnimation(); + e.NewElement.InvokePauseAnimation(); + }); + e.NewElement.ResumeCommand = new Command(() => + { + _animationView.ResumeAnimation(); + e.NewElement.InvokeResumeAnimation(); + }); + e.NewElement.StopCommand = new Command(() => + { + _animationView.CancelAnimation(); + e.NewElement.InvokeStopAnimation(); + }); + e.NewElement.ClickCommand = new Command(() => + { + //_animationView.Click(); + //e.NewElement.InvokeClick(); + }); + + e.NewElement.PlayMinAndMaxFrameCommand = new Command((object paramter) => + { + if (paramter is (int minFrame, int maxFrame)) + { + _animationView.SetMinAndMaxFrame(minFrame, maxFrame); + _animationView.PlayAnimation(); + } + }); + e.NewElement.PlayMinAndMaxProgressCommand = new Command((object paramter) => + { + if (paramter is (float minProgress, float maxProgress)) + { + _animationView.SetMinAndMaxProgress(minProgress, maxProgress); + _animationView.PlayAnimation(); + } + }); + e.NewElement.ReverseAnimationSpeedCommand = new Command(() => + { + _animationView.ReverseAnimationSpeed(); + }); + + //e.NewElement.Duration = _animationView.Duration.Ticks; + //e.NewElement.IsAnimating = _animationView.IsPlaying; + + SetNativeControl(_animationView); + + if (e.NewElement.AutoPlay || e.NewElement.IsAnimating) + _animationView.PlayAnimation(); + } + } + } + + private void _animationView_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + Element?.InvokeClick(); + } + + private void _animationView_Loaded(object sender, System.Windows.RoutedEventArgs e) + { + if (_animationView == null || Element == null || e == null) + return; + + if (Element.AutoPlay || Element.IsAnimating) + { + _animationView.PlayAnimation(); + + //await Task.Delay(PlayDelay); + //_ = _animationView.PlayAsync(0, 1, Element.RepeatMode == RepeatMode.Infinite).AsTask(); + + //Element.IsAnimating = _animationView.IsPlaying; + Element.InvokePlayAnimation(); + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_animationView == null || Element == null || e == null) + return; + + if (e.PropertyName == AnimationView.AnimationProperty.PropertyName) + { + var composition = Element.GetAnimation(); + _animationView.Composition = composition; + Element.InvokeAnimationLoaded(composition); + + if (Element.AutoPlay || Element.IsAnimating) + _animationView.PlayAnimation(); + } + + if (e.PropertyName == AnimationView.AutoPlayProperty.PropertyName) + _animationView.AutoPlay = Element.AutoPlay; + + //if (e.PropertyName == AnimationView.CacheCompositionProperty.PropertyName) + // _animationView.DefaultCacheStrategy = LottieAnimationView.CacheStrategy.Strong (Element.CacheComposition); + + //if (e.PropertyName == AnimationView.FallbackResource.PropertyName) + // _animationView.SetFallbackResource(e.NewElement.FallbackResource); + + //if (e.PropertyName == AnimationView.Composition.PropertyName) + // _animationView.Composition = e.NewElement.Composition; + + if (e.PropertyName == AnimationView.MinFrameProperty.PropertyName) + _animationView.MinFrame = Element.MinFrame; + + if (e.PropertyName == AnimationView.MinProgressProperty.PropertyName) + _animationView.MinProgress = Element.MinProgress; + + if (e.PropertyName == AnimationView.MaxFrameProperty.PropertyName) + _animationView.MaxFrame = Element.MaxFrame; + + if (e.PropertyName == AnimationView.MaxProgressProperty.PropertyName) + _animationView.MaxProgress = Element.MaxProgress; + + if (e.PropertyName == AnimationView.SpeedProperty.PropertyName) + _animationView.Speed = Element.Speed; + + if (e.PropertyName == AnimationView.RepeatModeProperty.PropertyName) + _animationView.RepeatMode = (LottieSharp.RepeatMode)(int)Element.RepeatMode; + + if (e.PropertyName == AnimationView.RepeatCountProperty.PropertyName) + _animationView.RepeatCount = Element.RepeatCount; + + //if (e.PropertyName == AnimationView.ImageAssetsFolderProperty.PropertyName && !string.IsNullOrEmpty(Element.ImageAssetsFolder)) + // _animationView.ImageAssetsFolder = Element.ImageAssetsFolder; + + if (e.PropertyName == AnimationView.ScaleProperty.PropertyName) + _animationView.Scale = Element.Scale; + + if (e.PropertyName == AnimationView.FrameProperty.PropertyName) + _animationView.Frame = Element.Frame; + + if (e.PropertyName == AnimationView.ProgressProperty.PropertyName) + _animationView.Progress = Element.Progress; + + base.OnElementPropertyChanged(sender, e); + } + } +} diff --git a/Lottie.Maui/RenderMode.cs b/Lottie.Maui/RenderMode.cs new file mode 100644 index 0000000..fce9dbe --- /dev/null +++ b/Lottie.Maui/RenderMode.cs @@ -0,0 +1,9 @@ +namespace Lottie.Maui +{ + public enum RenderMode + { + Automatic, + Hardware, + Software + } +} diff --git a/Lottie.Maui/RepeatMode.cs b/Lottie.Maui/RepeatMode.cs new file mode 100644 index 0000000..bcc2516 --- /dev/null +++ b/Lottie.Maui/RepeatMode.cs @@ -0,0 +1,20 @@ +namespace Lottie.Maui +{ + public enum RepeatMode + { + /// + /// When the animation reaches the end and RepeatCount is Infinite or a positive value, the animation restarts from the beginning. + /// + Restart = 0, + + /// + /// When the animation reaches the end and RepeatCount is Infinite or a positive value, the animation reverses direction on every iteration. + /// + Reverse = 1, + + /// + /// Repeat the animation indefinitely. + /// + Infinite = 2 + } +} diff --git a/Lottie.Maui/readme.txt b/Lottie.Maui/readme.txt new file mode 100644 index 0000000..cc3fffe --- /dev/null +++ b/Lottie.Maui/readme.txt @@ -0,0 +1,66 @@ +--------------------------------- +Lottie +--------------------------------- + +Lottie is a mobile library for Android and iOS that parses Adobe After Effects animations exported as json with Bodymovin and renders them natively on mobile! + +Using Lottie on Xamarin.Forms: + +Namespace: + +xmlns:forms="clr-namespace:Lottie.Forms;assembly=Lottie.Forms" + +Example: + + + +All options: + + + + +--------------------------------- +Star on Github if this project helps you: https://github.com/Baseflow/LottieXamarin + +Commercial support is available. Integration with your app or services, samples, feature request, etc. Email: hello@baseflow.com +Powered by: https://baseflow.com +--------------------------------- \ No newline at end of file diff --git a/Lottie.iOS.net6/ApiDefinitions.Ios.cs b/Lottie.iOS.net6/ApiDefinitions.Ios.cs new file mode 100644 index 0000000..a49e4d7 --- /dev/null +++ b/Lottie.iOS.net6/ApiDefinitions.Ios.cs @@ -0,0 +1,726 @@ +using System; +using CoreGraphics; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Airbnb.Lottie +{ + // @interface LOTAnimationTransitionController : NSObject + [BaseType(typeof(NSObject))] + interface LOTAnimationTransitionController : IUIViewControllerAnimatedTransitioning + { + // -(instancetype _Nonnull)initWithAnimationNamed:(NSString * _Nonnull)animation fromLayerNamed:(NSString * _Nullable)fromLayer toLayerNamed:(NSString * _Nullable)toLayer applyAnimationTransform:(BOOL)applyAnimationTransform; + [Export("initWithAnimationNamed:fromLayerNamed:toLayerNamed:applyAnimationTransform:")] + IntPtr Constructor(string animation, [NullAllowed] string fromLayer, [NullAllowed] string toLayer, bool applyAnimationTransform); + + // -(instancetype _Nonnull)initWithAnimationNamed:(NSString * _Nonnull)animation fromLayerNamed:(NSString * _Nullable)fromLayer toLayerNamed:(NSString * _Nullable)toLayer applyAnimationTransform:(BOOL)applyAnimationTransform inBundle:(NSBundle * _Nonnull)bundle; + [Export("initWithAnimationNamed:fromLayerNamed:toLayerNamed:applyAnimationTransform:inBundle:")] + IntPtr Constructor(string animation, [NullAllowed] string fromLayer, [NullAllowed] string toLayer, bool applyAnimationTransform, NSBundle bundle); + } + + // @interface LOTAnimatedControl : UIControl + [BaseType(typeof(UIControl))] + interface LOTAnimatedControl + { + // -(void)setLayerName:(NSString * _Nonnull)layerName forState:(UIControlState)state; + [Export("setLayerName:forState:")] + void SetLayerName(string layerName, UIControlState state); + + // @property (readonly, nonatomic) LOTAnimationView * _Nonnull animationView; + [Export("animationView")] + LOTAnimationView AnimationView { get; } + + // @property (nonatomic) LOTComposition * _Nullable animationComp; + [NullAllowed, Export("animationComp", ArgumentSemantic.Assign)] + LOTComposition AnimationComp { get; set; } + } + + // @interface LOTAnimatedSwitch : LOTAnimatedControl + [BaseType(typeof(LOTAnimatedControl))] + interface LOTAnimatedSwitch + { + // +(instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName; + [Static] + [Export("switchNamed:")] + LOTAnimatedSwitch SwitchNamed(string toggleName); + + // +(instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName inBundle:(NSBundle * _Nonnull)bundle; + [Static] + [Export("switchNamed:inBundle:")] + LOTAnimatedSwitch SwitchNamed(string toggleName, NSBundle bundle); + + // @property (getter = isOn, nonatomic) BOOL on; + [Export("on")] + bool On { [Bind("isOn")] get; set; } + + // @property (nonatomic) BOOL interactiveGesture; + [Export("interactiveGesture")] + bool InteractiveGesture { get; set; } + + // -(void)setOn:(BOOL)on animated:(BOOL)animated; + [Export("setOn:animated:")] + void SetOn(bool on, bool animated); + + // -(void)setProgressRangeForOnState:(CGFloat)fromProgress toProgress:(CGFloat)toProgress; + [Export("setProgressRangeForOnState:toProgress:")] + void SetProgressRangeForOnState(nfloat fromProgress, nfloat toProgress); + + // -(void)setProgressRangeForOffState:(CGFloat)fromProgress toProgress:(CGFloat)toProgress; + [Export("setProgressRangeForOffState:toProgress:")] + void SetProgressRangeForOffState(nfloat fromProgress, nfloat toProgress); + } + + // @interface LOTCacheProvider : NSObject + [BaseType(typeof(NSObject))] + interface LOTCacheProvider + { + // +(id)imageCache; + // +(void)setImageCache:(id)cache; + [Static] + [Export("imageCache")] + LOTImageCache ImageCache { get; set; } + } + + // @protocol LOTImageCache + [Protocol, Model] + [BaseType(typeof(NSObject))] + interface LOTImageCache + { + // @required -(UIImage *)imageForKey:(NSString *)key; + [Abstract] + [Export("imageForKey:")] + UIImage ImageForKey(string key); + + // @required -(void)setImage:(UIImage *)image forKey:(NSString *)key; + [Abstract] + [Export("setImage:forKey:")] + void SetImage(UIImage image, string key); + } + + // @interface LOTComposition : NSObject + [BaseType(typeof(NSObject))] + interface LOTComposition + { + // +(instancetype _Nullable)animationNamed:(NSString * _Nonnull)animationName; + [Static] + [Export("animationNamed:")] + [return: NullAllowed] + LOTComposition AnimationNamed(string animationName); + + // +(instancetype _Nullable)animationNamed:(NSString * _Nonnull)animationName inBundle:(NSBundle * _Nonnull)bundle; + [Static] + [Export("animationNamed:inBundle:")] + [return: NullAllowed] + LOTComposition AnimationNamed(string animationName, NSBundle bundle); + + // +(instancetype _Nullable)animationWithFilePath:(NSString * _Nonnull)filePath; + [Static] + [Export("animationWithFilePath:")] + [return: NullAllowed] + LOTComposition AnimationWithFilePath(string filePath); + + // +(instancetype _Nonnull)animationFromJSON:(NSDictionary * _Nonnull)animationJSON; + [Static] + [Export("animationFromJSON:")] + LOTComposition AnimationFromJSON(NSDictionary animationJSON); + + // +(instancetype _Nonnull)animationFromJSON:(NSDictionary * _Nullable)animationJSON inBundle:(NSBundle * _Nullable)bundle; + [Static] + [Export("animationFromJSON:inBundle:")] + LOTComposition AnimationFromJSON([NullAllowed] NSDictionary animationJSON, [NullAllowed] NSBundle bundle); + + // -(instancetype _Nonnull)initWithJSON:(NSDictionary * _Nullable)jsonDictionary withAssetBundle:(NSBundle * _Nullable)bundle; + [Export("initWithJSON:withAssetBundle:")] + IntPtr Constructor([NullAllowed] NSDictionary jsonDictionary, [NullAllowed] NSBundle bundle); + + // @property (readonly, nonatomic) CGRect compBounds; + [Export("compBounds")] + CGRect CompBounds { get; } + + // @property (readonly, nonatomic) NSNumber * _Nullable startFrame; + [NullAllowed, Export("startFrame")] + NSNumber StartFrame { get; } + + // @property (readonly, nonatomic) NSNumber * _Nullable endFrame; + [NullAllowed, Export("endFrame")] + NSNumber EndFrame { get; } + + // @property (readonly, nonatomic) NSNumber * _Nullable framerate; + [NullAllowed, Export("framerate")] + NSNumber Framerate { get; } + + // @property (readonly, nonatomic) NSTimeInterval timeDuration; + [Export("timeDuration")] + double TimeDuration { get; } + + // @property (readonly, nonatomic) LOTLayerGroup * _Nullable layerGroup; + //[NullAllowed, Export("layerGroup")] + //LOTLayerGroup LayerGroup { get; } + + //// @property (readonly, nonatomic) LOTAssetGroup * _Nullable assetGroup; + //[NullAllowed, Export("assetGroup")] + //LOTAssetGroup AssetGroup { get; } + + // @property (readwrite, nonatomic) NSString * _Nullable rootDirectory; + [NullAllowed, Export("rootDirectory")] + string RootDirectory { get; set; } + + // @property (readonly, nonatomic) NSBundle * _Nullable assetBundle; + [NullAllowed, Export("assetBundle")] + NSBundle AssetBundle { get; } + + // @property (copy, nonatomic) NSString * _Nullable cacheKey; + [NullAllowed, Export("cacheKey")] + string CacheKey { get; set; } + } + + + // @interface LOTKeypath : NSObject + [BaseType(typeof(NSObject))] + interface LOTKeypath + { + + // +(LOTKeypath * _Nonnull)keypathWithString:(NSString * _Nonnull)keypath; + [Static] + [Export("keypathWithString:")] + LOTKeypath KeypathWithString(string keypath); + + // +(LOTKeypath * _Nonnull)keypathWithKeys:(NSString * _Nonnull)firstKey, ... __attribute__((sentinel(0, 1))); + [Static, Internal] + [Export("keypathWithKeys:", IsVariadic = true)] + LOTKeypath KeypathWithKeys(string firstKey, IntPtr varArgs); + + // @property (readonly, nonatomic) NSString * _Nonnull absoluteKeypath; + [Export("absoluteKeypath")] + string AbsoluteKeypath { get; } + + // @property (readonly, nonatomic) NSString * _Nonnull currentKey; + [Export("currentKey")] + string CurrentKey { get; } + + // @property (readonly, nonatomic) NSString * _Nonnull currentKeyPath; + [Export("currentKeyPath")] + string CurrentKeyPath { get; } + + // @property (readonly, nonatomic) NSDictionary * _Nonnull searchResults; + [Export("searchResults")] + NSDictionary SearchResults { get; } + + // @property (readonly, nonatomic) BOOL hasFuzzyWildcard; + [Export("hasFuzzyWildcard")] + bool HasFuzzyWildcard { get; } + + // @property (readonly, nonatomic) BOOL hasWildcard; + [Export("hasWildcard")] + bool HasWildcard { get; } + + // @property (readonly, nonatomic) BOOL endOfKeypath; + [Export("endOfKeypath")] + bool EndOfKeypath { get; } + + // -(BOOL)pushKey:(NSString * _Nonnull)key; + [Export("pushKey:")] + bool PushKey(string key); + + // -(void)popKey; + [Export("popKey")] + void PopKey(); + + // -(void)popToRootKey; + [Export("popToRootKey")] + void PopToRootKey(); + + // -(void)addSearchResultForCurrentPath:(id _Nonnull)result; + [Export("addSearchResultForCurrentPath:")] + void AddSearchResultForCurrentPath(NSObject result); + } + + // @protocol LOTValueDelegate + [Protocol, Model] + [BaseType(typeof(NSObject))] + interface LOTValueDelegate + { + } + + // @protocol LOTColorValueDelegate + [Protocol, Model] + [BaseType(typeof(LOTValueDelegate))] + interface LOTColorValueDelegate + { + // @required -(CGColorRef)colorForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startColor:(CGColorRef)startColor endColor:(CGColorRef)endColor currentColor:(CGColorRef)interpolatedColor; + [Abstract] + [Export("colorForFrame:startKeyframe:endKeyframe:interpolatedProgress:startColor:endColor:currentColor:")] + unsafe CGColor StartKeyframe(nfloat currentFrame, nfloat startKeyframe, nfloat endKeyframe, nfloat interpolatedProgress, CGColor startColor, CGColor endColor, CGColor interpolatedColor); + } + + // @protocol LOTNumberValueDelegate + [Protocol, Model] + [BaseType(typeof(LOTValueDelegate))] + interface LOTNumberValueDelegate + { + // @required -(CGFloat)floatValueForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startValue:(CGFloat)startValue endValue:(CGFloat)endValue currentValue:(CGFloat)interpolatedValue; + [Abstract] + [Export("floatValueForFrame:startKeyframe:endKeyframe:interpolatedProgress:startValue:endValue:currentValue:")] + nfloat StartKeyframe(nfloat currentFrame, nfloat startKeyframe, nfloat endKeyframe, nfloat interpolatedProgress, nfloat startValue, nfloat endValue, nfloat interpolatedValue); + } + + // @protocol LOTPointValueDelegate + [Protocol, Model] + [BaseType(typeof(LOTValueDelegate))] + interface LOTPointValueDelegate + { + // @required -(CGPoint)pointForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint currentPoint:(CGPoint)interpolatedPoint; + [Abstract] + [Export("pointForFrame:startKeyframe:endKeyframe:interpolatedProgress:startPoint:endPoint:currentPoint:")] + CGPoint StartKeyframe(nfloat currentFrame, nfloat startKeyframe, nfloat endKeyframe, nfloat interpolatedProgress, CGPoint startPoint, CGPoint endPoint, CGPoint interpolatedPoint); + } + + // @protocol LOTSizeValueDelegate + [Protocol, Model] + [BaseType(typeof(LOTValueDelegate))] + interface LOTSizeValueDelegate + { + // @required -(CGSize)sizeForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress startSize:(CGSize)startSize endSize:(CGSize)endSize currentSize:(CGSize)interpolatedSize; + [Abstract] + [Export("sizeForFrame:startKeyframe:endKeyframe:interpolatedProgress:startSize:endSize:currentSize:")] + CGSize StartKeyframe(nfloat currentFrame, nfloat startKeyframe, nfloat endKeyframe, nfloat interpolatedProgress, CGSize startSize, CGSize endSize, CGSize interpolatedSize); + } + + // @protocol LOTPathValueDelegate + [Protocol, Model] + [BaseType(typeof(LOTValueDelegate))] + interface LOTPathValueDelegate + { + // @required -(CGPathRef)pathForFrame:(CGFloat)currentFrame startKeyframe:(CGFloat)startKeyframe endKeyframe:(CGFloat)endKeyframe interpolatedProgress:(CGFloat)interpolatedProgress; + [Abstract] + [Export("pathForFrame:startKeyframe:endKeyframe:interpolatedProgress:")] + unsafe CGPath StartKeyframe(nfloat currentFrame, nfloat startKeyframe, nfloat endKeyframe, nfloat interpolatedProgress); + } + + // typedef void (^LOTAnimationCompletionBlock)(BOOL); + delegate void LOTAnimationCompletionBlock(bool animationFinished); + + // @interface LOTAnimationView : UIView + [BaseType(typeof(UIView))] + interface LOTAnimationView + { + // +(instancetype _Nonnull)animationNamed:(NSString * _Nonnull)animationName; + [Static] + [Export("animationNamed:")] + LOTAnimationView AnimationNamed(string animationName); + + // +(instancetype _Nonnull)animationNamed:(NSString * _Nonnull)animationName inBundle:(NSBundle * _Nonnull)bundle; + [Static] + [Export("animationNamed:inBundle:")] + LOTAnimationView AnimationNamed(string animationName, NSBundle bundle); + + // +(instancetype _Nonnull)animationFromJSON:(NSDictionary * _Nonnull)animationJSON; + [Static] + [Export("animationFromJSON:")] + LOTAnimationView AnimationFromJSON(NSDictionary animationJSON); + + // +(instancetype _Nonnull)animationWithFilePath:(NSString * _Nonnull)filePath; + [Static] + [Export("animationWithFilePath:")] + LOTAnimationView AnimationWithFilePath(string filePath); + + // +(instancetype _Nonnull)animationFromJSON:(NSDictionary * _Nullable)animationJSON inBundle:(NSBundle * _Nullable)bundle; + [Static] + [Export("animationFromJSON:inBundle:")] + LOTAnimationView AnimationFromJSON([NullAllowed] NSDictionary animationJSON, [NullAllowed] NSBundle bundle); + + // -(instancetype _Nonnull)initWithModel:(LOTComposition * _Nullable)model inBundle:(NSBundle * _Nullable)bundle; + [Export("initWithModel:inBundle:")] + IntPtr Constructor([NullAllowed] LOTComposition model, [NullAllowed] NSBundle bundle); + + // -(instancetype _Nonnull)initWithContentsOfURL:(NSURL * _Nonnull)url; + [Export("initWithContentsOfURL:")] + IntPtr Constructor(NSUrl url); + + // -(void)setAnimationNamed:(NSString * _Nonnull)animationName; + [Export("setAnimationNamed:")] + void SetAnimationNamed(string animationName); + + // @property (readonly, nonatomic) BOOL isAnimationPlaying; + [Export("isAnimationPlaying")] + bool IsAnimationPlaying { get; } + + // @property (assign, nonatomic) BOOL loopAnimation; + [Export("loopAnimation")] + bool LoopAnimation { get; set; } + + // @property (assign, nonatomic) BOOL autoReverseAnimation; + [Export("autoReverseAnimation")] + bool AutoReverseAnimation { get; set; } + + // @property (assign, nonatomic) CGFloat animationProgress; + [Export("animationProgress")] + nfloat AnimationProgress { get; set; } + + // @property (assign, nonatomic) CGFloat animationSpeed; + [Export("animationSpeed")] + nfloat AnimationSpeed { get; set; } + + // @property (readonly, nonatomic) CGFloat animationDuration; + [Export("animationDuration")] + nfloat AnimationDuration { get; } + + // @property (assign, nonatomic) BOOL cacheEnable; + [Export("cacheEnable")] + bool CacheEnable { get; set; } + + // @property (assign, nonatomic) BOOL shouldRasterizeWhenIdle; + [Export("shouldRasterizeWhenIdle")] + bool ShouldRasterizeWhenIdle { get; set; } + + // @property (copy, nonatomic) LOTAnimationCompletionBlock _Nullable completionBlock; + [NullAllowed, Export("completionBlock", ArgumentSemantic.Copy)] + LOTAnimationCompletionBlock CompletionBlock { get; set; } + + // @property (nonatomic, strong) LOTComposition * _Nullable sceneModel; + [NullAllowed, Export("sceneModel", ArgumentSemantic.Strong)] + LOTComposition SceneModel { get; set; } + + // -(void)playToProgress:(CGFloat)toProgress withCompletion:(LOTAnimationCompletionBlock _Nullable)completion; + [Export("playToProgress:withCompletion:")] + void PlayToProgress(nfloat toProgress, [NullAllowed] LOTAnimationCompletionBlock completion); + + // -(void)playFromProgress:(CGFloat)fromStartProgress toProgress:(CGFloat)toEndProgress withCompletion:(LOTAnimationCompletionBlock _Nullable)completion; + [Export("playFromProgress:toProgress:withCompletion:")] + void PlayFromProgress(nfloat fromStartProgress, nfloat toEndProgress, [NullAllowed] LOTAnimationCompletionBlock completion); + + // -(void)playToFrame:(NSNumber * _Nonnull)toFrame withCompletion:(LOTAnimationCompletionBlock _Nullable)completion; + [Export("playToFrame:withCompletion:")] + void PlayToFrame(NSNumber toFrame, [NullAllowed] LOTAnimationCompletionBlock completion); + + // -(void)playFromFrame:(NSNumber * _Nonnull)fromStartFrame toFrame:(NSNumber * _Nonnull)toEndFrame withCompletion:(LOTAnimationCompletionBlock _Nullable)completion; + [Export("playFromFrame:toFrame:withCompletion:")] + void PlayFromFrame(NSNumber fromStartFrame, NSNumber toEndFrame, [NullAllowed] LOTAnimationCompletionBlock completion); + + // -(void)playWithCompletion:(LOTAnimationCompletionBlock _Nullable)completion; + [Export("playWithCompletion:")] + void PlayWithCompletion([NullAllowed] LOTAnimationCompletionBlock completion); + + // -(void)play; + [Export("play")] + void Play(); + + // -(void)pause; + [Export("pause")] + void Pause(); + + // -(void)stop; + [Export("stop")] + void Stop(); + + // -(void)setProgressWithFrame:(NSNumber * _Nonnull)currentFrame; + [Export("setProgressWithFrame:")] + void SetProgressWithFrame(NSNumber currentFrame); + + // -(void)forceDrawingUpdate; + [Export("forceDrawingUpdate")] + void ForceDrawingUpdate(); + + // -(void)logHierarchyKeypaths; + [Export("logHierarchyKeypaths")] + void LogHierarchyKeypaths(); + + // -(void)setValueDelegate:(id _Nonnull)delegates forKeypath:(LOTKeypath * _Nonnull)keypath; + [Export("setValueDelegate:forKeypath:")] + void SetValueDelegate(NSObject delegates, LOTKeypath keypath); + + // -(NSArray * _Nullable)keysForKeyPath:(LOTKeypath * _Nonnull)keypath; + [Export("keysForKeyPath:")] + [return: NullAllowed] + LOTKeypath[] KeysForKeyPath(LOTKeypath keypath); + + // -(CGPoint)convertPoint:(CGPoint)point toKeypathLayer:(LOTKeypath * _Nonnull)keypath; + [Export("convertPoint:toKeypathLayer:")] + CGPoint ConvertPointToKeypath(CGPoint point, LOTKeypath keypath); + + // -(CGRect)convertRect:(CGRect)rect toKeypathLayer:(LOTKeypath * _Nonnull)keypath; + [Export("convertRect:toKeypathLayer:")] + CGRect ConvertRectToKeypath(CGRect rect, LOTKeypath keypath); + + // -(CGPoint)convertPoint:(CGPoint)point fromKeypathLayer:(LOTKeypath * _Nonnull)keypath; + [Export("convertPoint:fromKeypathLayer:")] + CGPoint ConvertPointFromKeypath(CGPoint point, LOTKeypath keypath); + + // -(CGRect)convertRect:(CGRect)rect fromKeypathLayer:(LOTKeypath * _Nonnull)keypath; + [Export("convertRect:fromKeypathLayer:")] + CGRect ConvertRectFromKeypath(CGRect rect, LOTKeypath keypath); + + // -(void)addSubview:(UIView * _Nonnull)view toKeypathLayer:(LOTKeypath * _Nonnull)keypath; + [Export("addSubview:toKeypathLayer:")] + void AddSubview(UIView view, LOTKeypath keypath); + + // -(void)maskSubview:(UIView * _Nonnull)view toKeypathLayer:(LOTKeypath * _Nonnull)keypath; + [Export("maskSubview:toKeypathLayer:")] + void MaskSubview(UIView view, LOTKeypath keypath); + + // -(void)setValue:(id _Nonnull)value forKeypath:(NSString * _Nonnull)keypath atFrame:(NSNumber * _Nullable)frame __attribute__((deprecated(""))); + [Export("setValue:forKeypath:atFrame:")] + void SetValue(NSObject value, string keypath, [NullAllowed] NSNumber frame); + + // -(void)addSubview:(UIView * _Nonnull)view toLayerNamed:(NSString * _Nonnull)layer applyTransform:(BOOL)applyTransform __attribute__((deprecated(""))); + [Export("addSubview:toLayerNamed:applyTransform:")] + void AddSubview(UIView view, string layer, bool applyTransform); + + // -(CGRect)convertRect:(CGRect)rect toLayerNamed:(NSString * _Nullable)layerName __attribute__((deprecated(""))); + [Export("convertRect:toLayerNamed:")] + CGRect ConvertRect(CGRect rect, [NullAllowed] string layerName); + } + + // @interface LOTAnimationCache : NSObject + [BaseType(typeof(NSObject))] + interface LOTAnimationCache + { + // +(instancetype _Nonnull)sharedCache; + [Static] + [Export("sharedCache")] + LOTAnimationCache SharedCache(); + + // -(void)addAnimation:(LOTComposition * _Nonnull)animation forKey:(NSString * _Nonnull)key; + [Export("addAnimation:forKey:")] + void AddAnimation(LOTComposition animation, string key); + + // -(LOTComposition * _Nullable)animationForKey:(NSString * _Nonnull)key; + [Export("animationForKey:")] + [return: NullAllowed] + LOTComposition AnimationForKey(string key); + + // -(void)removeAnimationForKey:(NSString * _Nonnull)key; + [Export("removeAnimationForKey:")] + void RemoveAnimationForKey(string key); + + // -(void)clearCache; + [Export("clearCache")] + void ClearCache(); + + // -(void)disableCaching; + [Export("disableCaching")] + void DisableCaching(); + } + + // typedef CGColorRef _Nonnull (^LOTColorValueCallbackBlock)(CGFloat, CGFloat, CGFloat, CGFloat, CGColorRef _Nullable, CGColorRef _Nullable, CGColorRef _Nullable); + unsafe delegate IntPtr LOTColorValueCallbackBlock(nfloat currentFrame, nfloat startKeyFrame, nfloat endKeyFrame, nfloat interpolatedProgress, ObjCRuntime.NativeHandle startColor, ObjCRuntime.NativeHandle endColor, ObjCRuntime.NativeHandle interpolatedColor); + + // typedef CGFloat (^LOTNumberValueCallbackBlock)(CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat); + delegate nfloat LOTNumberValueCallbackBlock(nfloat currentFrame, nfloat startKeyFrame, nfloat endKeyFrame, nfloat interpolatedProgress, nfloat startValue, nfloat endValue, nfloat interpolatedValue); + + // typedef CGPoint (^LOTPointValueCallbackBlock)(CGFloat, CGFloat, CGFloat, CGFloat, CGPoint, CGPoint, CGPoint); + delegate CGPoint LOTPointValueCallbackBlock(nfloat currentFrame, nfloat startKeyFrame, nfloat endKeyFrame, nfloat interpolatedProgress, CGPoint startPoint, CGPoint endPoint, CGPoint interpolatedPoint); + + // typedef CGSize (^LOTSizeValueCallbackBlock)(CGFloat, CGFloat, CGFloat, CGFloat, CGSize, CGSize, CGSize); + delegate CGSize LOTSizeValueCallbackBlock(nfloat currentFrame, nfloat startKeyFrame, nfloat endKeyFrame, nfloat interpolatedProgress, CGSize startSize, CGSize endSize, CGSize interpolatedSize); + + // typedef CGPathRef _Nonnull (^LOTPathValueCallbackBlock)(CGFloat, CGFloat, CGFloat, CGFloat); + unsafe delegate CGPath LOTPathValueCallbackBlock(nfloat currentFrame, nfloat startKeyFrame, nfloat endKeyFrame, nfloat interpolatedProgress); + + // @interface LOTColorBlockCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTColorBlockCallback : LOTColorValueDelegate + { + // +(instancetype _Nonnull)withBlock:(LOTColorValueCallbackBlock _Nonnull)block; + [Static] + [Export("withBlock:")] + LOTColorBlockCallback WithBlock(LOTColorValueCallbackBlock block); + + // @property (copy, nonatomic) LOTColorValueCallbackBlock _Nonnull callback; + [Export("callback", ArgumentSemantic.Copy)] + LOTColorValueCallbackBlock Callback { get; set; } + } + + // @interface LOTNumberBlockCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTNumberBlockCallback : LOTNumberValueDelegate + { + // +(instancetype _Nonnull)withBlock:(LOTNumberValueCallbackBlock _Nonnull)block; + [Static] + [Export("withBlock:")] + LOTNumberBlockCallback WithBlock(LOTNumberValueCallbackBlock block); + + // @property (copy, nonatomic) LOTNumberValueCallbackBlock _Nonnull callback; + [Export("callback", ArgumentSemantic.Copy)] + LOTNumberValueCallbackBlock Callback { get; set; } + } + + // @interface LOTPointBlockCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTPointBlockCallback : LOTPointValueDelegate + { + // +(instancetype _Nonnull)withBlock:(LOTPointValueCallbackBlock _Nonnull)block; + [Static] + [Export("withBlock:")] + LOTPointBlockCallback WithBlock(LOTPointValueCallbackBlock block); + + // @property (copy, nonatomic) LOTPointValueCallbackBlock _Nonnull callback; + [Export("callback", ArgumentSemantic.Copy)] + LOTPointValueCallbackBlock Callback { get; set; } + } + + // @interface LOTSizeBlockCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTSizeBlockCallback : LOTSizeValueDelegate + { + // +(instancetype _Nonnull)withBlock:(LOTSizeValueCallbackBlock _Nonnull)block; + [Static] + [Export("withBlock:")] + LOTSizeBlockCallback WithBlock(LOTSizeValueCallbackBlock block); + + // @property (copy, nonatomic) LOTSizeValueCallbackBlock _Nonnull callback; + [Export("callback", ArgumentSemantic.Copy)] + LOTSizeValueCallbackBlock Callback { get; set; } + } + + // @interface LOTPathBlockCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTPathBlockCallback : LOTPathValueDelegate + { + // +(instancetype _Nonnull)withBlock:(LOTPathValueCallbackBlock _Nonnull)block; + [Static] + [Export("withBlock:")] + LOTPathBlockCallback WithBlock(LOTPathValueCallbackBlock block); + + // @property (copy, nonatomic) LOTPathValueCallbackBlock _Nonnull callback; + [Export("callback", ArgumentSemantic.Copy)] + LOTPathValueCallbackBlock Callback { get; set; } + } + + // @interface LOTPointInterpolatorCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTPointInterpolatorCallback : LOTPointValueDelegate + { + // +(instancetype _Nonnull)withFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint; + [Static] + [Export("withFromPoint:toPoint:")] + LOTPointInterpolatorCallback WithFromPoint(CGPoint fromPoint, CGPoint toPoint); + + // @property (nonatomic) CGPoint fromPoint; + [Export("fromPoint", ArgumentSemantic.Assign)] + CGPoint FromPoint { get; set; } + + // @property (nonatomic) CGPoint toPoint; + [Export("toPoint", ArgumentSemantic.Assign)] + CGPoint ToPoint { get; set; } + + // @property (assign, nonatomic) CGFloat currentProgress; + [Export("currentProgress")] + nfloat CurrentProgress { get; set; } + } + + // @interface LOTSizeInterpolatorCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTSizeInterpolatorCallback : LOTSizeValueDelegate + { + // +(instancetype _Nonnull)withFromSize:(CGSize)fromSize toSize:(CGSize)toSize; + [Static] + [Export("withFromSize:toSize:")] + LOTSizeInterpolatorCallback WithFromSize(CGSize fromSize, CGSize toSize); + + // @property (nonatomic) CGSize fromSize; + [Export("fromSize", ArgumentSemantic.Assign)] + CGSize FromSize { get; set; } + + // @property (nonatomic) CGSize toSize; + [Export("toSize", ArgumentSemantic.Assign)] + CGSize ToSize { get; set; } + + // @property (assign, nonatomic) CGFloat currentProgress; + [Export("currentProgress")] + nfloat CurrentProgress { get; set; } + } + + // @interface LOTFloatInterpolatorCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTFloatInterpolatorCallback : LOTNumberValueDelegate + { + // +(instancetype _Nonnull)withFromFloat:(CGFloat)fromFloat toFloat:(CGFloat)toFloat; + [Static] + [Export("withFromFloat:toFloat:")] + LOTFloatInterpolatorCallback WithFromFloat(nfloat fromFloat, nfloat toFloat); + + // @property (nonatomic) CGFloat fromFloat; + [Export("fromFloat")] + nfloat FromFloat { get; set; } + + // @property (nonatomic) CGFloat toFloat; + [Export("toFloat")] + nfloat ToFloat { get; set; } + + // @property (assign, nonatomic) CGFloat currentProgress; + [Export("currentProgress")] + nfloat CurrentProgress { get; set; } + } + + // @interface LOTColorValueCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTColorValueCallback : LOTColorValueDelegate + { + // +(instancetype _Nonnull)withCGColor:(CGColorRef _Nonnull)color; + [Static] + [Export("withCGColor:")] + unsafe LOTColorValueCallback WithCGColor(CGColor color); + + // @property (nonatomic) CGColorRef _Nonnull colorValue; + [Export("colorValue", ArgumentSemantic.Assign)] + unsafe CGColor ColorValue { get; set; } + } + + // @interface LOTNumberValueCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTNumberValueCallback : LOTNumberValueDelegate + { + // +(instancetype _Nonnull)withFloatValue:(CGFloat)numberValue; + [Static] + [Export("withFloatValue:")] + LOTNumberValueCallback WithFloatValue(nfloat numberValue); + + // @property (assign, nonatomic) CGFloat numberValue; + [Export("numberValue")] + nfloat NumberValue { get; set; } + } + + // @interface LOTPointValueCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTPointValueCallback : LOTPointValueDelegate + { + // +(instancetype _Nonnull)withPointValue:(CGPoint)pointValue; + [Static] + [Export("withPointValue:")] + LOTPointValueCallback WithPointValue(CGPoint pointValue); + + // @property (assign, nonatomic) CGPoint pointValue; + [Export("pointValue", ArgumentSemantic.Assign)] + CGPoint PointValue { get; set; } + } + + // @interface LOTSizeValueCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTSizeValueCallback : LOTSizeValueDelegate + { + // +(instancetype _Nonnull)withPointValue:(CGSize)sizeValue; + [Static] + [Export("withPointValue:")] + LOTSizeValueCallback WithPointValue(CGSize sizeValue); + + // @property (assign, nonatomic) CGSize sizeValue; + [Export("sizeValue", ArgumentSemantic.Assign)] + CGSize SizeValue { get; set; } + } + + // @interface LOTPathValueCallback : NSObject + [BaseType(typeof(NSObject))] + interface LOTPathValueCallback : LOTPathValueDelegate + { + // +(instancetype _Nonnull)withCGPath:(CGPathRef _Nonnull)path; + [Static] + [Export("withCGPath:")] + unsafe LOTPathValueCallback WithCGPath(CGPath path); + + // @property (nonatomic) CGPathRef _Nonnull pathValue; + [Export("pathValue", ArgumentSemantic.Assign)] + unsafe CGPath PathValue { get; set; } + } +} diff --git a/Lottie.iOS.net6/LOTAnimationView.cs b/Lottie.iOS.net6/LOTAnimationView.cs new file mode 100644 index 0000000..83bd7e9 --- /dev/null +++ b/Lottie.iOS.net6/LOTAnimationView.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using Foundation; + +namespace Airbnb.Lottie +{ + public partial class LOTAnimationView + { + /// + /// Asynchronously play the animation. + /// + public async Task PlayAsync() + { + var tcs = new TaskCompletionSource(); + + this.CompletionBlock = animationFinished => tcs.SetResult(true); + + this.Play(); + + return await tcs.Task; + } + + /// + /// Plays the animation from its current position to a specific progress. + /// The animation will start from its current position. + /// + public Task PlayToProgressAsync(nfloat toProgress) + { + var tcs = new TaskCompletionSource(); + + this.PlayToProgress(toProgress, (bool animationFinished) => tcs.SetResult(animationFinished)); + + return tcs.Task; + } + + public Task PlayFromProgressAsync(nfloat fromStartProgress, nfloat toEndProgress) + { + var tcs = new TaskCompletionSource(); + + this.PlayFromProgress(fromStartProgress, toEndProgress, (bool animationFinished) => tcs.SetResult(animationFinished)); + + return tcs.Task; + } + + public Task PlayToFrameAsync(NSNumber toFrame) + { + var tcs = new TaskCompletionSource(); + + this.PlayToFrame(toFrame, (bool animationFinished) => tcs.SetResult(animationFinished)); + + return tcs.Task; + } + + public Task PlayFromFrameAsync(NSNumber fromStartFrame, NSNumber toEndFrame) + { + var tcs = new TaskCompletionSource(); + + this.PlayFromFrame(fromStartFrame, toEndFrame, (bool animationFinished) => tcs.SetResult(animationFinished)); + + return tcs.Task; + } + + } +} diff --git a/Lottie.iOS.net6/Linker.cs b/Lottie.iOS.net6/Linker.cs new file mode 100644 index 0000000..2a14bf9 --- /dev/null +++ b/Lottie.iOS.net6/Linker.cs @@ -0,0 +1,28 @@ + + +namespace DreamTeam.Xamarin.lottie_ios +{ + public static class ___DreamTeam_Xamarin_lottie_ios + { + static ___DreamTeam_Xamarin_lottie_ios() + { + DontLooseMeDuringBuild(); + } + + public static void DontLooseMeDuringBuild() + { + + } + } +} + +namespace ApiDefinitions +{ + partial class Messaging + { + static Messaging() + { + DreamTeam.Xamarin.lottie_ios.___DreamTeam_Xamarin_lottie_ios.DontLooseMeDuringBuild(); + } + } +} diff --git a/Lottie.iOS.net6/Lottie.framework.linkwith.cs b/Lottie.iOS.net6/Lottie.framework.linkwith.cs new file mode 100644 index 0000000..8ec1834 --- /dev/null +++ b/Lottie.iOS.net6/Lottie.framework.linkwith.cs @@ -0,0 +1,8 @@ + +using ObjCRuntime; +[assembly: LinkWith ("Lottie.framework", LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator | LinkTarget.Arm64 | LinkTarget.Simulator64, +Frameworks = "UIKit CoreGraphics Foundation QuartzCore", +//IsCxx = true, +SmartLink = true, +LinkerFlags="-ObjC", +ForceLoad = true)] diff --git a/Lottie.iOS.net6/Lottie.iOS.net6.csproj b/Lottie.iOS.net6/Lottie.iOS.net6.csproj new file mode 100644 index 0000000..1981bf7 --- /dev/null +++ b/Lottie.iOS.net6/Lottie.iOS.net6.csproj @@ -0,0 +1,39 @@ + + + + + net6.0-ios + enable + true + Lottie.iOS + Lottie.iOS + Render After Effects animations natively on Android, iOS, MacOS, TVOs and UWP + Com.Airbnb.iOS.Lottie + false + true + 3.0.4 + + + + + + + + + + + + + + + + Static + UIKit + true + true + -ObjC + true + + + + diff --git a/Lottie.iOS.net6/StructsAndEnums.cs b/Lottie.iOS.net6/StructsAndEnums.cs new file mode 100644 index 0000000..6431b26 --- /dev/null +++ b/Lottie.iOS.net6/StructsAndEnums.cs @@ -0,0 +1,23 @@ +using System; +using ObjCRuntime; + +namespace Airbnb.Lottie +{ + [Native] + public enum LOTViewContentMode : ulong + { + ScaleToFill, + ScaleAspectFit, + ScaleAspectFill, + Redraw, + Center, + Top, + Bottom, + Left, + Right, + TopLeft, + TopRight, + BottomLeft, + BottomRight + } +} diff --git a/Lottie.iOS.net6/libLottie-ios.a b/Lottie.iOS.net6/libLottie-ios.a new file mode 100644 index 0000000..7068d94 Binary files /dev/null and b/Lottie.iOS.net6/libLottie-ios.a differ