diff --git a/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml b/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml
index deeeada..596f612 100644
--- a/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml
+++ b/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml
@@ -165,12 +165,7 @@
Margin="0">
-
+
diff --git a/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs b/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs
index d2af457..8e9f57a 100644
--- a/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs
+++ b/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs
@@ -2,22 +2,189 @@ using ReactiveUI.Avalonia;
using Avalonia.Markup.Xaml;
using AuroraDesk.Presentation.ViewModels.Pages;
using ReactiveUI;
+using System;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using Avalonia.Controls;
+using Avalonia.Threading;
+using Avalonia.Skia.Lottie;
+using Avalonia.VisualTree;
+using Avalonia.Interactivity;
namespace AuroraDesk.Presentation.Views.Pages;
public partial class BreathePageView : ReactiveUserControl
{
+ private Lottie? _lottieControl;
+ private ContentControl? _lottieContainer;
+ private bool _isDisposed;
+ private IDisposable? _repeatCountSubscription;
+
public BreathePageView()
{
InitializeComponent();
- // 若后续需要生命周期 Hook,可在此处使用 WhenActivated
- this.WhenActivated(_ => { });
+ this.WhenActivated(disposables =>
+ {
+ // 重置停用标志
+ _isDisposed = false;
+
+ // 查找容器控件
+ _lottieContainer = this.FindControl("LottieContainer");
+
+ if (_lottieContainer == null || ViewModel == null)
+ {
+ return;
+ }
+
+ // 创建新的 Lottie 控件实例
+ CreateLottieControl(disposables);
+
+ // 订阅动画路径变化
+ var pathSubscription = ViewModel
+ .WhenAnyValue(x => x.AnimationPath)
+ .Where(path => !string.IsNullOrEmpty(path))
+ .DistinctUntilChanged()
+ .ObserveOn(AvaloniaScheduler.Instance)
+ .Subscribe(path =>
+ {
+ if (_isDisposed || _lottieControl == null)
+ {
+ return;
+ }
+
+ try
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (!_isDisposed && _lottieControl != null && !string.IsNullOrEmpty(path))
+ {
+ try
+ {
+ _lottieControl.Path = path;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to set animation path: {ex.GetType().Name} - {ex.Message}");
+ }
+ }
+ }, DispatcherPriority.Normal);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to update animation path: {ex.GetType().Name} - {ex.Message}");
+ }
+ });
+
+ disposables.Add(pathSubscription);
+
+ // 页面停用时的清理
+ disposables.Add(Disposable.Create(() =>
+ {
+ _isDisposed = true;
+ _repeatCountSubscription?.Dispose();
+ _repeatCountSubscription = null;
+ DestroyLottieControl();
+ }));
+ });
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
+
+ private void CreateLottieControl(CompositeDisposable disposables)
+ {
+ if (_lottieContainer == null || ViewModel == null || _isDisposed)
+ {
+ return;
+ }
+
+ try
+ {
+ // 获取 baseUri(用于资源加载)
+ // 使用程序集资源的基础 URI
+ var baseUri = new Uri("avares://AuroraDesk.Presentation/");
+
+ // 创建新的 Lottie 控件实例
+ _lottieControl = new Lottie(baseUri)
+ {
+ RepeatCount = ViewModel.RepeatCount,
+ Stretch = Avalonia.Media.Stretch.Uniform,
+ HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
+ VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
+ ClipToBounds = false
+ };
+
+ // 设置初始路径
+ if (!string.IsNullOrEmpty(ViewModel.AnimationPath))
+ {
+ _lottieControl.Path = ViewModel.AnimationPath;
+ }
+
+ // 订阅 RepeatCount 变化
+ if (ViewModel != null)
+ {
+ _repeatCountSubscription = ViewModel.WhenAnyValue(x => x.RepeatCount)
+ .Subscribe(count =>
+ {
+ if (_lottieControl != null && !_isDisposed)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (_lottieControl != null && !_isDisposed)
+ {
+ _lottieControl.RepeatCount = count;
+ }
+ });
+ }
+ });
+
+ if (_repeatCountSubscription != null)
+ {
+ disposables.Add(_repeatCountSubscription);
+ }
+ }
+
+ // 将控件添加到容器
+ _lottieContainer.Content = _lottieControl;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to create Lottie control: {ex.GetType().Name} - {ex.Message}");
+ }
+ }
+
+ private void DestroyLottieControl()
+ {
+ try
+ {
+ if (_lottieContainer != null)
+ {
+ // 清空容器内容,这会触发 Lottie 控件的卸载和资源清理
+ Dispatcher.UIThread.Post(() =>
+ {
+ try
+ {
+ if (_lottieContainer != null)
+ {
+ _lottieContainer.Content = null;
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to clear container: {ex.GetType().Name} - {ex.Message}");
+ }
+ }, DispatcherPriority.Normal);
+ }
+
+ _lottieControl = null;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to destroy Lottie control: {ex.GetType().Name} - {ex.Message}");
+ }
+ }
}
diff --git a/ilspy_lottie/Avalonia.Skia.Lottie.csproj b/ilspy_lottie/Avalonia.Skia.Lottie.csproj
new file mode 100644
index 0000000..b803e15
--- /dev/null
+++ b/ilspy_lottie/Avalonia.Skia.Lottie.csproj
@@ -0,0 +1,22 @@
+
+
+ Avalonia.Skia.Lottie
+ False
+ netcoreapp7.0
+
+
+ 12.0
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/Lottie.cs b/ilspy_lottie/Avalonia.Skia.Lottie/Lottie.cs
new file mode 100644
index 0000000..caaa597
--- /dev/null
+++ b/ilspy_lottie/Avalonia.Skia.Lottie/Lottie.cs
@@ -0,0 +1,397 @@
+using System;
+using System.IO;
+using System.Numerics;
+using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Logging;
+using Avalonia.Media;
+using Avalonia.Metadata;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using SkiaSharp;
+using SkiaSharp.SceneGraph;
+using SkiaSharp.Skottie;
+
+namespace Avalonia.Skia.Lottie;
+
+public class Lottie : Control
+{
+ private Animation? _animation;
+
+ private int _repeatCount;
+
+ private readonly Uri _baseUri;
+
+ private string? _preloadPath;
+
+ private CompositionCustomVisual? _customVisual;
+
+ public const int Infinity = -1;
+
+ public static readonly StyledProperty PathProperty = AvaloniaProperty.Register("Path", (string)null, false, (BindingMode)1, (Func)null, (Func)null, false);
+
+ public static readonly StyledProperty StretchProperty = AvaloniaProperty.Register("Stretch", (Stretch)2, false, (BindingMode)1, (Func)null, (Func)null, false);
+
+ public static readonly StyledProperty StretchDirectionProperty = AvaloniaProperty.Register("StretchDirection", (StretchDirection)2, false, (BindingMode)1, (Func)null, (Func)null, false);
+
+ public static readonly StyledProperty RepeatCountProperty = AvaloniaProperty.Register("RepeatCount", -1, false, (BindingMode)1, (Func)null, (Func)null, false);
+
+ [Content]
+ public string? Path
+ {
+ get
+ {
+ return ((AvaloniaObject)this).GetValue(PathProperty);
+ }
+ set
+ {
+ ((AvaloniaObject)this).SetValue(PathProperty, value, (BindingPriority)0);
+ }
+ }
+
+ public Stretch Stretch
+ {
+ get
+ {
+ //IL_0006: Unknown result type (might be due to invalid IL or missing references)
+ return ((AvaloniaObject)this).GetValue(StretchProperty);
+ }
+ set
+ {
+ //IL_0006: Unknown result type (might be due to invalid IL or missing references)
+ ((AvaloniaObject)this).SetValue(StretchProperty, value, (BindingPriority)0);
+ }
+ }
+
+ public StretchDirection StretchDirection
+ {
+ get
+ {
+ //IL_0006: Unknown result type (might be due to invalid IL or missing references)
+ return ((AvaloniaObject)this).GetValue(StretchDirectionProperty);
+ }
+ set
+ {
+ //IL_0006: Unknown result type (might be due to invalid IL or missing references)
+ ((AvaloniaObject)this).SetValue(StretchDirectionProperty, value, (BindingPriority)0);
+ }
+ }
+
+ public int RepeatCount
+ {
+ get
+ {
+ return ((AvaloniaObject)this).GetValue(RepeatCountProperty);
+ }
+ set
+ {
+ ((AvaloniaObject)this).SetValue(RepeatCountProperty, value, (BindingPriority)0);
+ }
+ }
+
+ public Lottie(Uri baseUri)
+ {
+ _baseUri = baseUri;
+ }
+
+ public Lottie(IServiceProvider serviceProvider)
+ {
+ _baseUri = serviceProvider.GetContextBaseUri();
+ }
+
+ protected override void OnLoaded(RoutedEventArgs routedEventArgs)
+ {
+ //IL_006f: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0074: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0077: Unknown result type (might be due to invalid IL or missing references)
+ //IL_007c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0086: Unknown result type (might be due to invalid IL or missing references)
+ //IL_008b: Unknown result type (might be due to invalid IL or missing references)
+ //IL_008e: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0093: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00a1: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00b9: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00c4: Unknown result type (might be due to invalid IL or missing references)
+ ((Control)this).OnLoaded(routedEventArgs);
+ CompositionVisual elementVisual = ElementComposition.GetElementVisual((Visual)(object)this);
+ Compositor val = ((elementVisual != null) ? ((CompositionObject)elementVisual).Compositor : null);
+ if (val != null)
+ {
+ _customVisual = val.CreateCustomVisual((CompositionCustomVisualHandler)(object)new LottieCompositionCustomVisualHandler());
+ ElementComposition.SetElementChildVisual((Visual)(object)this, (CompositionVisual)(object)_customVisual);
+ ((Layoutable)this).LayoutUpdated += OnLayoutUpdated;
+ if (_preloadPath != null)
+ {
+ DisposeImpl();
+ Load(_preloadPath);
+ CompositionCustomVisual? customVisual = _customVisual;
+ Rect bounds = ((Visual)this).Bounds;
+ Size size = ((Rect)(ref bounds)).Size;
+ float x = (float)((Size)(ref size)).Width;
+ bounds = ((Visual)this).Bounds;
+ size = ((Rect)(ref bounds)).Size;
+ ((CompositionVisual)customVisual).Size = Vector.op_Implicit(new Vector2(x, (float)((Size)(ref size)).Height));
+ _customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Update, _animation, Stretch, StretchDirection));
+ Start();
+ _preloadPath = null;
+ }
+ }
+ }
+
+ protected override void OnUnloaded(RoutedEventArgs routedEventArgs)
+ {
+ ((Control)this).OnUnloaded(routedEventArgs);
+ ((Layoutable)this).LayoutUpdated -= OnLayoutUpdated;
+ Stop();
+ DisposeImpl();
+ }
+
+ private void OnLayoutUpdated(object? sender, EventArgs e)
+ {
+ //IL_0010: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0015: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0018: Unknown result type (might be due to invalid IL or missing references)
+ //IL_001d: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0027: Unknown result type (might be due to invalid IL or missing references)
+ //IL_002c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_002f: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0034: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0042: Unknown result type (might be due to invalid IL or missing references)
+ //IL_005a: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0065: Unknown result type (might be due to invalid IL or missing references)
+ if (_customVisual != null)
+ {
+ CompositionCustomVisual? customVisual = _customVisual;
+ Rect bounds = ((Visual)this).Bounds;
+ Size size = ((Rect)(ref bounds)).Size;
+ float x = (float)((Size)(ref size)).Width;
+ bounds = ((Visual)this).Bounds;
+ size = ((Rect)(ref bounds)).Size;
+ ((CompositionVisual)customVisual).Size = Vector.op_Implicit(new Vector2(x, (float)((Size)(ref size)).Height));
+ _customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Update, _animation, Stretch, StretchDirection));
+ }
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ //IL_000a: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0010: Unknown result type (might be due to invalid IL or missing references)
+ //IL_002b: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0030: Unknown result type (might be due to invalid IL or missing references)
+ //IL_003f: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0044: Unknown result type (might be due to invalid IL or missing references)
+ //IL_004d: Unknown result type (might be due to invalid IL or missing references)
+ //IL_001c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0022: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0052: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0054: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0059: Unknown result type (might be due to invalid IL or missing references)
+ //IL_005a: Unknown result type (might be due to invalid IL or missing references)
+ //IL_005c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0061: Unknown result type (might be due to invalid IL or missing references)
+ if (_animation == null)
+ {
+ return default(Size);
+ }
+ _003F val;
+ if (_animation == null)
+ {
+ val = default(Size);
+ }
+ else
+ {
+ SKSize size = _animation.Size;
+ double num = ((SKSize)(ref size)).Width;
+ size = _animation.Size;
+ val = new Size(num, (double)((SKSize)(ref size)).Height);
+ }
+ Size val2 = (Size)val;
+ return MediaExtensions.CalculateSize(Stretch, availableSize, val2, StretchDirection);
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ //IL_000a: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0010: Unknown result type (might be due to invalid IL or missing references)
+ //IL_002b: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0030: Unknown result type (might be due to invalid IL or missing references)
+ //IL_003f: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0044: Unknown result type (might be due to invalid IL or missing references)
+ //IL_004d: Unknown result type (might be due to invalid IL or missing references)
+ //IL_001c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0022: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0052: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0054: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0059: Unknown result type (might be due to invalid IL or missing references)
+ //IL_005a: Unknown result type (might be due to invalid IL or missing references)
+ //IL_005c: Unknown result type (might be due to invalid IL or missing references)
+ if (_animation == null)
+ {
+ return default(Size);
+ }
+ _003F val;
+ if (_animation == null)
+ {
+ val = default(Size);
+ }
+ else
+ {
+ SKSize size = _animation.Size;
+ double num = ((SKSize)(ref size)).Width;
+ size = _animation.Size;
+ val = new Size(num, (double)((SKSize)(ref size)).Height);
+ }
+ Size val2 = (Size)val;
+ return MediaExtensions.CalculateSize(Stretch, finalSize, val2, (StretchDirection)2);
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ ((Control)this).OnPropertyChanged(change);
+ string name = change.Property.Name;
+ if (!(name == "Path"))
+ {
+ if (name == "RepeatCount")
+ {
+ _repeatCount = AvaloniaPropertyChangedExtensions.GetNewValue(change);
+ Stop();
+ Start();
+ }
+ }
+ else
+ {
+ string newValue = AvaloniaPropertyChangedExtensions.GetNewValue(change);
+ if (_preloadPath == null && _customVisual == null)
+ {
+ _preloadPath = newValue;
+ }
+ else
+ {
+ Load(newValue);
+ }
+ }
+ }
+
+ private Animation? Load(Stream stream)
+ {
+ //IL_0001: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0007: Expected O, but got Unknown
+ //IL_00f7: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00fc: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0040: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0045: Unknown result type (might be due to invalid IL or missing references)
+ SKManagedStream val = new SKManagedStream(stream);
+ try
+ {
+ Animation val2 = default(Animation);
+ ParametrizedLogger valueOrDefault;
+ if (Animation.TryCreate((SKStream)(object)val, ref val2))
+ {
+ val2.Seek(0.0, (InvalidationController)null);
+ ParametrizedLogger? val3 = Logger.TryGet((LogEventLevel)2, "Control");
+ if (val3.HasValue)
+ {
+ valueOrDefault = val3.GetValueOrDefault();
+ ((ParametrizedLogger)(ref valueOrDefault)).Log((object)this, $"Version: {val2.Version} Duration: {val2.Duration} Fps:{val2.Fps} InPoint: {val2.InPoint} OutPoint: {val2.OutPoint}");
+ }
+ }
+ else
+ {
+ ParametrizedLogger? val3 = Logger.TryGet((LogEventLevel)3, "Control");
+ if (val3.HasValue)
+ {
+ valueOrDefault = val3.GetValueOrDefault();
+ ((ParametrizedLogger)(ref valueOrDefault)).Log((object)this, "Failed to load animation.");
+ }
+ }
+ return val2;
+ }
+ finally
+ {
+ ((IDisposable)val)?.Dispose();
+ }
+ }
+
+ private Animation? Load(string path, Uri? baseUri)
+ {
+ Uri uri = (path.StartsWith("/") ? new Uri(path, UriKind.Relative) : new Uri(path, UriKind.RelativeOrAbsolute));
+ if (uri.IsAbsoluteUri && uri.IsFile)
+ {
+ using (FileStream stream = File.OpenRead(uri.LocalPath))
+ {
+ return Load(stream);
+ }
+ }
+ using Stream stream2 = AssetLoader.Open(uri, baseUri);
+ if (stream2 == null)
+ {
+ return null;
+ }
+ return Load(stream2);
+ }
+
+ private void Load(string? path)
+ {
+ //IL_006d: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0072: Unknown result type (might be due to invalid IL or missing references)
+ Stop();
+ if (path == null)
+ {
+ DisposeImpl();
+ return;
+ }
+ DisposeImpl();
+ try
+ {
+ _repeatCount = RepeatCount;
+ _animation = Load(path, _baseUri);
+ if (_animation != null)
+ {
+ ((Layoutable)this).InvalidateArrange();
+ ((Layoutable)this).InvalidateMeasure();
+ Start();
+ }
+ }
+ catch (Exception ex)
+ {
+ ParametrizedLogger? val = Logger.TryGet((LogEventLevel)3, "Control");
+ if (val.HasValue)
+ {
+ ParametrizedLogger valueOrDefault = val.GetValueOrDefault();
+ ((ParametrizedLogger)(ref valueOrDefault)).Log((object)this, "Failed to load animation: " + ex);
+ }
+ _animation = null;
+ }
+ }
+
+ private void Start()
+ {
+ //IL_0013: Unknown result type (might be due to invalid IL or missing references)
+ //IL_001e: Unknown result type (might be due to invalid IL or missing references)
+ CompositionCustomVisual? customVisual = _customVisual;
+ if (customVisual != null)
+ {
+ customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Start, _animation, Stretch, StretchDirection, _repeatCount));
+ }
+ }
+
+ private void Stop()
+ {
+ CompositionCustomVisual? customVisual = _customVisual;
+ if (customVisual != null)
+ {
+ customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Stop));
+ }
+ }
+
+ private void DisposeImpl()
+ {
+ CompositionCustomVisual? customVisual = _customVisual;
+ if (customVisual != null)
+ {
+ customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Dispose));
+ }
+ }
+}
diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/LottieCommand.cs b/ilspy_lottie/Avalonia.Skia.Lottie/LottieCommand.cs
new file mode 100644
index 0000000..bd2de33
--- /dev/null
+++ b/ilspy_lottie/Avalonia.Skia.Lottie/LottieCommand.cs
@@ -0,0 +1,9 @@
+namespace Avalonia.Skia.Lottie;
+
+internal enum LottieCommand
+{
+ Start,
+ Stop,
+ Update,
+ Dispose
+}
diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/LottieCompositionCustomVisualHandler.cs b/ilspy_lottie/Avalonia.Skia.Lottie/LottieCompositionCustomVisualHandler.cs
new file mode 100644
index 0000000..0e70205
--- /dev/null
+++ b/ilspy_lottie/Avalonia.Skia.Lottie/LottieCompositionCustomVisualHandler.cs
@@ -0,0 +1,368 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using SkiaSharp;
+using SkiaSharp.SceneGraph;
+using SkiaSharp.Skottie;
+
+namespace Avalonia.Skia.Lottie;
+
+internal class LottieCompositionCustomVisualHandler : CompositionCustomVisualHandler
+{
+ private TimeSpan _primaryTimeElapsed;
+
+ private TimeSpan _animationElapsed;
+
+ private TimeSpan? _lastServerTime;
+
+ private bool _running;
+
+ private Animation? _animation;
+
+ private Stretch? _stretch;
+
+ private StretchDirection? _stretchDirection;
+
+ private InvalidationController? _ic;
+
+ private readonly object _sync = new object();
+
+ private int _repeatCount;
+
+ private int _count;
+
+ public override void OnMessage(object message)
+ {
+ //IL_00b2: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00b7: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0058: Unknown result type (might be due to invalid IL or missing references)
+ //IL_005d: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00cf: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00d4: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0075: Unknown result type (might be due to invalid IL or missing references)
+ //IL_007a: Unknown result type (might be due to invalid IL or missing references)
+ //IL_012a: Unknown result type (might be due to invalid IL or missing references)
+ //IL_012b: Unknown result type (might be due to invalid IL or missing references)
+ //IL_012d: Unknown result type (might be due to invalid IL or missing references)
+ //IL_012e: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0131: Unknown result type (might be due to invalid IL or missing references)
+ //IL_013e: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00eb: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00f7: Unknown result type (might be due to invalid IL or missing references)
+ if (!(message is LottiePayload lottiePayload))
+ {
+ return;
+ }
+ switch (lottiePayload.LottieCommand)
+ {
+ case LottieCommand.Start:
+ {
+ Animation animation = lottiePayload.Animation;
+ if (animation == null)
+ {
+ break;
+ }
+ Stretch? stretch = lottiePayload.Stretch;
+ if (!stretch.HasValue)
+ {
+ break;
+ }
+ Stretch valueOrDefault = stretch.GetValueOrDefault();
+ StretchDirection? stretchDirection = lottiePayload.StretchDirection;
+ if (stretchDirection.HasValue)
+ {
+ StretchDirection valueOrDefault2 = stretchDirection.GetValueOrDefault();
+ int? repeatCount = lottiePayload.RepeatCount;
+ if (repeatCount.HasValue)
+ {
+ int valueOrDefault3 = repeatCount.GetValueOrDefault();
+ _running = true;
+ _lastServerTime = null;
+ _stretch = valueOrDefault;
+ _stretchDirection = valueOrDefault2;
+ _animation = animation;
+ _repeatCount = valueOrDefault3;
+ _count = 0;
+ _animationElapsed = TimeSpan.Zero;
+ ((CompositionCustomVisualHandler)this).RegisterForNextAnimationFrameUpdate();
+ }
+ }
+ break;
+ }
+ case LottieCommand.Update:
+ {
+ Stretch? stretch = lottiePayload.Stretch;
+ if (stretch.HasValue)
+ {
+ Stretch valueOrDefault = stretch.GetValueOrDefault();
+ StretchDirection? stretchDirection = lottiePayload.StretchDirection;
+ if (stretchDirection.HasValue)
+ {
+ StretchDirection valueOrDefault2 = stretchDirection.GetValueOrDefault();
+ Stretch value = valueOrDefault;
+ StretchDirection value2 = valueOrDefault2;
+ _stretch = value;
+ _stretchDirection = value2;
+ ((CompositionCustomVisualHandler)this).RegisterForNextAnimationFrameUpdate();
+ }
+ }
+ break;
+ }
+ case LottieCommand.Stop:
+ _running = false;
+ _animationElapsed = TimeSpan.Zero;
+ _count = 0;
+ break;
+ case LottieCommand.Dispose:
+ DisposeImpl();
+ break;
+ }
+ }
+
+ public override void OnAnimationFrameUpdate()
+ {
+ if (_running)
+ {
+ if (_repeatCount == 0 || (_repeatCount > 0 && _count >= _repeatCount))
+ {
+ _running = false;
+ _animationElapsed = TimeSpan.Zero;
+ }
+ ((CompositionCustomVisualHandler)this).Invalidate();
+ ((CompositionCustomVisualHandler)this).RegisterForNextAnimationFrameUpdate();
+ }
+ }
+
+ private void DisposeImpl()
+ {
+ lock (_sync)
+ {
+ Animation? animation = _animation;
+ if (animation != null)
+ {
+ ((SKNativeObject)animation).Dispose();
+ }
+ _animation = null;
+ InvalidationController? ic = _ic;
+ if (ic != null)
+ {
+ ic.End();
+ }
+ InvalidationController? ic2 = _ic;
+ if (ic2 != null)
+ {
+ ((SKNativeObject)ic2).Dispose();
+ }
+ _ic = null;
+ }
+ }
+
+ private double GetFrameTime()
+ {
+ if (_animation == null)
+ {
+ return 0.0;
+ }
+ double totalSeconds = _animationElapsed.TotalSeconds;
+ if (_animationElapsed.TotalSeconds > _animation.Duration.TotalSeconds)
+ {
+ _animationElapsed = TimeSpan.Zero;
+ InvalidationController? ic = _ic;
+ if (ic != null)
+ {
+ ic.End();
+ }
+ InvalidationController? ic2 = _ic;
+ if (ic2 != null)
+ {
+ ic2.Begin();
+ }
+ _count++;
+ }
+ return totalSeconds;
+ }
+
+ private void Draw(SKCanvas canvas)
+ {
+ //IL_0014: Unknown result type (might be due to invalid IL or missing references)
+ //IL_001e: Expected O, but got Unknown
+ //IL_0067: Unknown result type (might be due to invalid IL or missing references)
+ //IL_006c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0076: Unknown result type (might be due to invalid IL or missing references)
+ //IL_007b: Unknown result type (might be due to invalid IL or missing references)
+ //IL_009a: Unknown result type (might be due to invalid IL or missing references)
+ Animation animation = _animation;
+ if (animation == null)
+ {
+ return;
+ }
+ if (_ic == null)
+ {
+ _ic = new InvalidationController();
+ _ic.Begin();
+ }
+ InvalidationController ic = _ic;
+ if (_repeatCount != 0)
+ {
+ double num = GetFrameTime();
+ if (!_running)
+ {
+ num = (float)animation.Duration.TotalSeconds;
+ }
+ SKSize size = animation.Size;
+ float width = ((SKSize)(ref size)).Width;
+ size = animation.Size;
+ SKRect val = default(SKRect);
+ ((SKRect)(ref val))._002Ector(0f, 0f, width, ((SKSize)(ref size)).Height);
+ animation.SeekFrameTime(num, ic);
+ canvas.Save();
+ animation.Render(canvas, val);
+ canvas.Restore();
+ ic.Reset();
+ }
+ }
+
+ public unsafe override void OnRender(ImmediateDrawingContext context)
+ {
+ //IL_0092: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0097: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00ab: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00b0: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00cb: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00d0: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00d6: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00e3: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00e8: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00f3: Unknown result type (might be due to invalid IL or missing references)
+ //IL_00f8: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0130: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0133: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0138: Unknown result type (might be due to invalid IL or missing references)
+ //IL_013a: Unknown result type (might be due to invalid IL or missing references)
+ //IL_013c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0141: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0143: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0145: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0147: Unknown result type (might be due to invalid IL or missing references)
+ //IL_014c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0150: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0152: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0157: Unknown result type (might be due to invalid IL or missing references)
+ //IL_015c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0160: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0162: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0167: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0169: Unknown result type (might be due to invalid IL or missing references)
+ //IL_016b: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0170: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0176: Unknown result type (might be due to invalid IL or missing references)
+ //IL_017b: Unknown result type (might be due to invalid IL or missing references)
+ //IL_017d: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0182: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0187: Unknown result type (might be due to invalid IL or missing references)
+ //IL_018c: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0190: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0196: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0199: Unknown result type (might be due to invalid IL or missing references)
+ //IL_019e: Unknown result type (might be due to invalid IL or missing references)
+ //IL_01a3: Unknown result type (might be due to invalid IL or missing references)
+ //IL_01c3: Unknown result type (might be due to invalid IL or missing references)
+ //IL_01c8: Unknown result type (might be due to invalid IL or missing references)
+ //IL_01fc: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0201: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0204: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0206: Unknown result type (might be due to invalid IL or missing references)
+ //IL_020b: Unknown result type (might be due to invalid IL or missing references)
+ //IL_020e: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0210: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0212: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0217: Unknown result type (might be due to invalid IL or missing references)
+ //IL_021c: Unknown result type (might be due to invalid IL or missing references)
+ lock (_sync)
+ {
+ if (_running)
+ {
+ if (_lastServerTime.HasValue)
+ {
+ TimeSpan timeSpan = ((CompositionCustomVisualHandler)this).CompositionNow - _lastServerTime.Value;
+ _primaryTimeElapsed += timeSpan;
+ _animationElapsed += timeSpan;
+ }
+ _lastServerTime = ((CompositionCustomVisualHandler)this).CompositionNow;
+ }
+ Animation animation = _animation;
+ if (animation == null)
+ {
+ return;
+ }
+ Stretch? stretch = _stretch;
+ if (!stretch.HasValue)
+ {
+ return;
+ }
+ Stretch valueOrDefault = stretch.GetValueOrDefault();
+ StretchDirection? stretchDirection = _stretchDirection;
+ if (!stretchDirection.HasValue)
+ {
+ return;
+ }
+ StretchDirection valueOrDefault2 = stretchDirection.GetValueOrDefault();
+ ISkiaSharpApiLeaseFeature val = OptionalFeatureProviderExtensions.TryGetFeature((IOptionalFeatureProvider)(object)context);
+ if (val == null)
+ {
+ return;
+ }
+ Rect renderBounds = ((CompositionCustomVisualHandler)this).GetRenderBounds();
+ Rect val2 = default(Rect);
+ ((Rect)(ref val2))._002Ector(((Rect)(ref renderBounds)).Size);
+ SKSize size = animation.Size;
+ double num = ((SKSize)(ref size)).Width;
+ size = animation.Size;
+ Size val3 = default(Size);
+ ((Size)(ref val3))._002Ector(num, (double)((SKSize)(ref size)).Height);
+ if (((Size)(ref val3)).Width <= 0.0 || ((Size)(ref val3)).Height <= 0.0)
+ {
+ return;
+ }
+ Vector val4 = MediaExtensions.CalculateScaling(valueOrDefault, ((Rect)(ref renderBounds)).Size, val3, valueOrDefault2);
+ Size val5 = val3 * val4;
+ Rect val6 = ((Rect)(ref val2)).CenterRect(new Rect(val5));
+ Rect val7 = ((Rect)(ref val6)).Intersect(val2);
+ val6 = new Rect(val3);
+ Rect val8 = ((Rect)(ref val6)).CenterRect(new Rect(((Rect)(ref val7)).Size / val4));
+ SKRect val9 = SKRect.Create(default(SKPoint), animation.Size);
+ Matrix val10 = Matrix.CreateScale(((Rect)(ref val7)).Width / ((Rect)(ref val8)).Width, ((Rect)(ref val7)).Height / ((Rect)(ref val8)).Height);
+ Matrix val11 = Matrix.CreateTranslation(0.0 - ((Rect)(ref val8)).X + ((Rect)(ref val7)).X - (double)((SKRect)(ref val9)).Top, 0.0 - ((Rect)(ref val8)).Y + ((Rect)(ref val7)).Y - (double)((SKRect)(ref val9)).Left);
+ PushedState val12 = context.PushClip(val7);
+ try
+ {
+ PushedState val13 = context.PushPostTransform(val11 * val10);
+ try
+ {
+ ISkiaSharpApiLease val14 = val.Lease();
+ try
+ {
+ SKCanvas val15 = ((val14 != null) ? val14.SkCanvas : null);
+ if (val15 != null)
+ {
+ Draw(val15);
+ }
+ }
+ finally
+ {
+ ((IDisposable)val14)?.Dispose();
+ }
+ }
+ finally
+ {
+ ((IDisposable)(*(PushedState*)(&val13))/*cast due to .constrained prefix*/).Dispose();
+ }
+ }
+ finally
+ {
+ ((IDisposable)(*(PushedState*)(&val12))/*cast due to .constrained prefix*/).Dispose();
+ }
+ }
+ }
+}
diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/LottiePayload.cs b/ilspy_lottie/Avalonia.Skia.Lottie/LottiePayload.cs
new file mode 100644
index 0000000..4664b33
--- /dev/null
+++ b/ilspy_lottie/Avalonia.Skia.Lottie/LottiePayload.cs
@@ -0,0 +1,6 @@
+using Avalonia.Media;
+using SkiaSharp.Skottie;
+
+namespace Avalonia.Skia.Lottie;
+
+internal record struct LottiePayload(LottieCommand LottieCommand, Animation? Animation = null, Stretch? Stretch = null, StretchDirection? StretchDirection = null, int? RepeatCount = null);
diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/ServiceProviderExtensions.cs b/ilspy_lottie/Avalonia.Skia.Lottie/ServiceProviderExtensions.cs
new file mode 100644
index 0000000..7a7f63f
--- /dev/null
+++ b/ilspy_lottie/Avalonia.Skia.Lottie/ServiceProviderExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+using Avalonia.Markup.Xaml;
+
+namespace Avalonia.Skia.Lottie;
+
+internal static class ServiceProviderExtensions
+{
+ public static T GetService(this IServiceProvider sp)
+ {
+ return (T)(sp?.GetService(typeof(T)));
+ }
+
+ public static Uri GetContextBaseUri(this IServiceProvider ctx)
+ {
+ return ctx.GetService().BaseUri;
+ }
+}
diff --git a/ilspy_lottie/Properties/AssemblyInfo.cs b/ilspy_lottie/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..8193002
--- /dev/null
+++ b/ilspy_lottie/Properties/AssemblyInfo.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+using Avalonia.Metadata;
+
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Skia.Lottie")]
+[assembly: AssemblyCompany("Wiesław Šoltés")]
+[assembly: AssemblyConfiguration("Release")]
+[assembly: AssemblyCopyright("Copyright © Wiesław Šoltés 2023")]
+[assembly: AssemblyDescription("An lottie animation player control for Avalonia.")]
+[assembly: AssemblyFileVersion("11.0.0.0")]
+[assembly: AssemblyInformationalVersion("11.0.0")]
+[assembly: AssemblyProduct("Avalonia.Skia.Lottie")]
+[assembly: AssemblyTitle("Avalonia.Skia.Lottie")]
+[assembly: AssemblyVersion("11.0.0.0")]
+[module: System.Runtime.CompilerServices.RefSafetyRules(11)]
diff --git a/modify.md b/modify.md
index 86a30af..b3d4238 100644
--- a/modify.md
+++ b/modify.md
@@ -2,6 +2,39 @@
## 2025年修改记录
+### 修复 BreathePageView 页面切换闪退问题
+- **日期**: 2025年1月
+- **问题**: 第一次进入 BreathePageView 没问题,导航到其他页面后再切换回来时出现 `System.ExecutionEngineException` 闪退(HResult: -2146233082)
+- **问题分析**:
+ - ❌ **生命周期管理缺失**: BreathePageView 没有正确处理页面激活/停用生命周期,导致 Lottie 控件资源管理混乱
+ - ❌ **XAML 绑定时机问题**: 页面重新激活时,XAML 绑定立即更新 Path 属性,但 Lottie 控件的资源可能还未完全清理
+ - ❌ **资源释放冲突**: 当页面重新加载时,控件试图访问已经被释放或正在释放的 Skia 底层资源,导致 ExecutionEngineException
+ - ❌ **控件实例复用**: 同一个 Lottie 控件实例在页面切换时被重复使用,导致资源状态混乱
+- **修改文件**:
+ - `AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml` (使用 ContentControl 作为容器)
+ - `AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs` (动态创建和销毁 Lottie 控件)
+- **主要修复**:
+ - ✅ **条件渲染**: 使用 `ContentControl` 作为容器,不在 XAML 中直接放置 Lottie 控件,改为在代码后台动态创建
+ - ✅ **动态创建控件**: 在页面激活时(`WhenActivated`)创建全新的 Lottie 控件实例,确保每次都是干净的状态
+ - ✅ **完全销毁控件**: 在页面停用时,通过设置 `ContentControl.Content = null` 完全移除控件,触发控件的 `OnUnloaded` 和资源清理
+ - ✅ **生命周期管理**: 使用 `WhenActivated` 管理页面生命周期,确保控件在正确的时机创建和销毁
+ - ✅ **订阅管理**: 正确管理 `RepeatCount` 和 `AnimationPath` 的订阅,在停用时自动清理
+ - ✅ **线程安全**: 所有 UI 操作都通过 `Dispatcher.UIThread.Post` 在 UI 线程上执行
+ - ✅ **异常处理**: 添加 try-catch 捕获所有异常,避免崩溃
+- **技术细节**:
+ - 在 XAML 中使用 `` 作为容器
+ - 在 `CreateLottieControl` 方法中动态创建新的 `Lottie` 控件实例
+ - 设置控件的初始属性(RepeatCount、Stretch、对齐方式等)
+ - 订阅 `ViewModel.RepeatCount` 和 `ViewModel.AnimationPath` 变化
+ - 将控件添加到 `ContentControl.Content`,触发加载和初始化
+ - 在 `DestroyLottieControl` 中设置 `Content = null`,触发控件的卸载和资源清理
+ - 所有订阅都添加到 `CompositeDisposable`,确保在停用时自动清理
+- **效果**:
+ - ✅ **页面切换正常**: 每次页面激活时创建全新的控件实例,避免了资源冲突
+ - ✅ **资源管理正确**: 通过完全移除控件,确保 Skia 资源被正确释放
+ - ✅ **异常安全**: 即使出现异常,也不会导致应用崩溃,只会记录日志
+ - ✅ **彻底解决**: 通过动态创建和销毁控件,从根本上避免了 ExecutionEngineException
+
### 新增文件浏览页面
- **日期**: 2025年11月14日
- **调整内容**: