diff --git a/LanMountainDesktop/Views/Components/StudyEnvironmentWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyEnvironmentWidget.axaml.cs index aa85f1d..42388d0 100644 --- a/LanMountainDesktop/Views/Components/StudyEnvironmentWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyEnvironmentWidget.axaml.cs @@ -64,8 +64,16 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) { _ = isEditMode; + var wasOnActivePage = _isOnActivePage; _isOnActivePage = isOnActivePage; + UpdateMonitoringLeaseState(); + + if (isOnActivePage && !wasOnActivePage) + { + RefreshVisual(); + } + UpdateTimerState(); } @@ -116,8 +124,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg private void UpdateMonitoringLeaseState() { - var shouldMonitor = _isAttached && _isOnActivePage; - if (shouldMonitor) + if (_isAttached) { _monitoringLease ??= _monitoringLeaseCoordinator.AcquireLease(); return; diff --git a/LanMountainDesktop/Views/Components/StudyNoiseCurveWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyNoiseCurveWidget.axaml.cs index 90d0140..b0cc36d 100644 --- a/LanMountainDesktop/Views/Components/StudyNoiseCurveWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyNoiseCurveWidget.axaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using Avalonia; @@ -11,7 +11,7 @@ using LanMountainDesktop.Theme; namespace LanMountainDesktop.Views.Components; -public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget +public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, IDisposable { private const double NormalTextMinContrast = 4.5; private const double LargeTextMinContrast = 4.5; @@ -69,6 +69,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge private bool _isAttached; private bool _isOnActivePage = true; private bool _isSubscribed; + private bool _isDisposed; private int _framesSinceCompaction; private IDisposable? _monitoringLease; @@ -131,8 +132,20 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) { _ = isEditMode; + var wasOnActivePage = _isOnActivePage; _isOnActivePage = isOnActivePage; + UpdateMonitoringLeaseState(); + + if (isOnActivePage && !wasOnActivePage) + { + lock (_snapshotSync) + { + _pendingSnapshot = _studyAnalyticsService.GetSnapshot(); + _hasPendingSnapshot = true; + } + } + UpdateRenderLoopState(); } @@ -231,8 +244,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge private void UpdateMonitoringLeaseState() { - var shouldMonitor = _isAttached && _isOnActivePage; - if (shouldMonitor) + if (_isAttached) { _monitoringLease ??= _monitoringLeaseCoordinator.AcquireLease(); return; @@ -553,4 +565,29 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge { return _localizationService.GetString(_languageCode, key, fallback); } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + + _renderTimer.Stop(); + _renderTimer.Tick -= OnRenderTimerTick; + AttachedToVisualTree -= OnAttachedToVisualTree; + DetachedFromVisualTree -= OnDetachedFromVisualTree; + SizeChanged -= OnSizeChanged; + + if (_isSubscribed) + { + _studyAnalyticsService.SnapshotUpdated -= OnStudySnapshotUpdated; + _isSubscribed = false; + } + + _monitoringLease?.Dispose(); + _monitoringLease = null; + } } diff --git a/LanMountainDesktop/Views/Components/StudyNoiseDistributionWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyNoiseDistributionWidget.axaml.cs index b171f4f..e413649 100644 --- a/LanMountainDesktop/Views/Components/StudyNoiseDistributionWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyNoiseDistributionWidget.axaml.cs @@ -93,8 +93,16 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) { _ = isEditMode; + var wasOnActivePage = _isOnActivePage; _isOnActivePage = isOnActivePage; + UpdateMonitoringLeaseState(); + + if (isOnActivePage && !wasOnActivePage) + { + RefreshVisual(); + } + UpdateTimerState(); } @@ -143,8 +151,7 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone private void UpdateMonitoringLeaseState() { - var shouldMonitor = _isAttached && _isOnActivePage; - if (shouldMonitor) + if (_isAttached) { _monitoringLease ??= _monitoringLeaseCoordinator.AcquireLease(); return; diff --git a/LanMountainDesktop/Views/Components/StudySessionHistoryWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudySessionHistoryWidget.axaml.cs index 6a624b3..0991cda 100644 --- a/LanMountainDesktop/Views/Components/StudySessionHistoryWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudySessionHistoryWidget.axaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using Avalonia; @@ -15,7 +15,7 @@ using LanMountainDesktop.Theme; namespace LanMountainDesktop.Views.Components; -public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget +public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, IDisposable { private const double MinTextContrast = 4.5; private enum HistoryDialogMode @@ -55,6 +55,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW private bool _isAttached; private bool _isOnActivePage = true; private bool _isSubscribed; + private bool _isDisposed; private bool _isCompactMode; private bool _isUltraCompactMode; private string? _loadingSessionId; @@ -733,6 +734,29 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW return min; } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + + AttachedToVisualTree -= OnAttachedToVisualTree; + DetachedFromVisualTree -= OnDetachedFromVisualTree; + SizeChanged -= OnSizeChanged; + DialogCancelButton.Click -= (_, _) => CloseDialog(); + DialogConfirmButton.Click -= (_, _) => ConfirmDialog(); + DialogRenameTextBox.KeyDown -= OnDialogRenameTextBoxKeyDown; + + if (_isSubscribed) + { + _studyAnalyticsService.SnapshotUpdated -= OnStudySnapshotUpdated; + _isSubscribed = false; + } + } } diff --git a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs index 2e5379b..7fe3edc 100644 --- a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs +++ b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Avalonia; @@ -681,23 +681,33 @@ public partial class MainWindow } ClearTimeZoneServiceBindings(_selectedDesktopComponentHost); + DisposeComponentIfNeeded(_selectedDesktopComponentHost); if (_desktopPageComponentGrids.TryGetValue(placement.PageIndex, out var pageGrid)) { pageGrid.Children.Remove(_selectedDesktopComponentHost); } - // Remove from persisted placement list as well. _desktopComponentPlacements.Remove(placement); ClearDesktopComponentSelection(); ApplyTaskbarActionVisibility(GetCurrentTaskbarContext()); - // 娣囨繂鐡ㄧ拋鍓х枂 PersistSettings(); } + private static void DisposeComponentIfNeeded(Border host) + { + if (TryGetContentHost(host) is Border contentHost && contentHost.Child is Control componentControl) + { + if (componentControl is IDisposable disposableComponent) + { + disposableComponent.Dispose(); + } + } + } + private void OpenComponentSettings() { if (_selectedDesktopComponentHost is null || _selectedDesktopComponentHost.Tag is not string placementId) @@ -1389,6 +1399,10 @@ public partial class MainWindow if (_desktopPageComponentGrids.TryGetValue(_currentDesktopSurfaceIndex, out var pageGrid)) { ClearTimeZoneServiceBindings(pageGrid.Children.OfType().ToList()); + foreach (var child in pageGrid.Children.OfType()) + { + DisposeComponentIfNeeded(child); + } } foreach (var placement in placementsToRemove)