using System; using System.Globalization; using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; using LanMontainDesktop.Models; using LanMontainDesktop.Services; namespace LanMontainDesktop.Views.Components; public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget { private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault(); private readonly AppSettingsService _settingsService = new(); private readonly LocalizationService _localizationService = new(); private readonly DispatcherTimer _uiTimer = new() { Interval = TimeSpan.FromMilliseconds(250) }; private double _currentCellSize = 48; private bool _showDisplayDb = true; private bool _showDbfs; private string _languageCode = "zh-CN"; private bool _isAttached; private bool _isOnActivePage = true; public StudyEnvironmentWidget() { InitializeComponent(); _uiTimer.Tick += OnUiTimerTick; AttachedToVisualTree += OnAttachedToVisualTree; DetachedFromVisualTree += OnDetachedFromVisualTree; SizeChanged += OnSizeChanged; ReloadDisplaySettings(); ApplyCellSize(_currentCellSize); RefreshVisual(); } public void ApplyCellSize(double cellSize) { _currentCellSize = Math.Max(1, cellSize); var scale = Math.Clamp(_currentCellSize / 48d, 0.82, 2.2); RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.34, 10, 28)); RootBorder.Padding = new Thickness( Math.Clamp(14 * scale, 8, 20), Math.Clamp(10 * scale, 6, 16)); StatusTitleTextBlock.FontSize = Math.Clamp(11 * scale, 9, 18); StatusValueTextBlock.FontSize = Math.Clamp(20 * scale, 12, 34); NoiseValueTextBlock.FontSize = Math.Clamp(22 * scale, 12, 38); NoiseSubValueTextBlock.FontSize = Math.Clamp(12 * scale, 9, 18); UpdateAdaptiveLayout(); } public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) { _ = isEditMode; _isOnActivePage = isOnActivePage; UpdateTimerState(); } public void RefreshFromSettings() { ReloadDisplaySettings(); RefreshVisual(); } private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) { _isAttached = true; ReloadDisplaySettings(); _ = _studyAnalyticsService.StartOrResumeMonitoring(); UpdateTimerState(); RefreshVisual(); } private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e) { _isAttached = false; _uiTimer.Stop(); } private void OnSizeChanged(object? sender, SizeChangedEventArgs e) { ApplyCellSize(_currentCellSize); UpdateAdaptiveLayout(); } private void OnUiTimerTick(object? sender, EventArgs e) { RefreshVisual(); } private void UpdateTimerState() { if (_isAttached && _isOnActivePage) { _uiTimer.Start(); return; } _uiTimer.Stop(); } private void ReloadDisplaySettings() { var snapshot = _settingsService.Load(); _languageCode = _localizationService.NormalizeLanguageCode(snapshot.LanguageCode); _showDisplayDb = snapshot.StudyEnvironmentShowDisplayDb; _showDbfs = snapshot.StudyEnvironmentShowDbfs; if (!_showDisplayDb && !_showDbfs) { _showDisplayDb = true; } } private void RefreshVisual() { var snapshot = _studyAnalyticsService.GetSnapshot(); StatusTitleTextBlock.Text = L("study.environment.status_label", "Environment"); StatusValueTextBlock.Text = ResolveStatusText(snapshot); StatusValueTextBlock.Foreground = ResolveStatusBrush(snapshot); if (snapshot.LatestRealtimePoint is not { } realtimePoint) { NoiseValueTextBlock.Text = L("study.environment.value.unavailable", "--"); NoiseSubValueTextBlock.IsVisible = false; UpdateAdaptiveLayout(); return; } var showDisplay = _showDisplayDb; var showDbfs = _showDbfs; if (!showDisplay && !showDbfs) { showDisplay = true; } if (showDisplay && showDbfs) { NoiseValueTextBlock.Text = FormatDisplayDb(realtimePoint.DisplayDb); NoiseSubValueTextBlock.Text = FormatDbfs(realtimePoint.Dbfs); NoiseSubValueTextBlock.IsVisible = true; return; } NoiseValueTextBlock.Text = showDisplay ? FormatDisplayDb(realtimePoint.DisplayDb) : FormatDbfs(realtimePoint.Dbfs); NoiseSubValueTextBlock.IsVisible = false; UpdateAdaptiveLayout(); } private void UpdateAdaptiveLayout() { var scale = Math.Clamp(_currentCellSize / 48d, 0.82, 2.2); var width = Bounds.Width; var height = Bounds.Height; var showingDualNoiseLines = _showDisplayDb && _showDbfs; // Collapse the "Environment" label when space is tight so core values remain readable. var collapseByCell = _currentCellSize <= 40; var collapseByBounds = (width > 0 && width < (showingDualNoiseLines ? 230 : 200)) || (height > 0 && height < (showingDualNoiseLines ? 102 : 82)); var hideStatusLabel = collapseByCell || collapseByBounds; StatusTitleTextBlock.IsVisible = !hideStatusLabel; LeftStatusStack.Spacing = hideStatusLabel ? 0 : Math.Clamp(2 * scale, 1, 4); LayoutGrid.ColumnSpacing = hideStatusLabel ? Math.Clamp(6 * scale, 4, 10) : Math.Clamp(10 * scale, 7, 14); } private string ResolveStatusText(StudyAnalyticsSnapshot snapshot) { if (snapshot.State == StudyAnalyticsRuntimeState.Unsupported) { return L("study.environment.status.unsupported", "Unsupported"); } if (snapshot.State == StudyAnalyticsRuntimeState.Error || snapshot.StreamStatus == NoiseStreamStatus.Error) { return L("study.environment.status.error", "Error"); } if (snapshot.State == StudyAnalyticsRuntimeState.Paused) { return L("study.environment.status.paused", "Paused"); } if (snapshot.StreamStatus == NoiseStreamStatus.Noisy) { return L("study.environment.status.noisy", "Noisy"); } if (snapshot.State == StudyAnalyticsRuntimeState.Running && snapshot.StreamStatus == NoiseStreamStatus.Quiet) { return L("study.environment.status.quiet", "Quiet"); } if (snapshot.State == StudyAnalyticsRuntimeState.Ready) { return L("study.environment.status.ready", "Ready"); } return L("study.environment.status.initializing", "Initializing"); } private IBrush ResolveStatusBrush(StudyAnalyticsSnapshot snapshot) { if (snapshot.State == StudyAnalyticsRuntimeState.Unsupported || snapshot.State == StudyAnalyticsRuntimeState.Error || snapshot.StreamStatus == NoiseStreamStatus.Error) { return CreateBrush("#FFFF7B7B"); } if (snapshot.StreamStatus == NoiseStreamStatus.Noisy) { return CreateBrush("#FFFFB14A"); } if (snapshot.State == StudyAnalyticsRuntimeState.Running && snapshot.StreamStatus == NoiseStreamStatus.Quiet) { return CreateBrush("#FF6FD7A2"); } return TryResolveThemeBrush("AdaptiveTextPrimaryBrush", "#FFEFF3FF"); } private string FormatDisplayDb(double value) { return string.Format( CultureInfo.InvariantCulture, L("study.environment.value.display_format", "{0:F1} dB"), value); } private string FormatDbfs(double value) { return string.Format( CultureInfo.InvariantCulture, L("study.environment.value.dbfs_format", "{0:F1} dBFS"), value); } private string L(string key, string fallback) { return _localizationService.GetString(_languageCode, key, fallback); } private static SolidColorBrush CreateBrush(string hexColor) { return new SolidColorBrush(Color.Parse(hexColor)); } private IBrush TryResolveThemeBrush(string resourceKey, string fallbackHex) { if (Resources.TryGetResource(resourceKey, ActualThemeVariant, out var resource) && resource is IBrush brush) { return brush; } return CreateBrush(fallbackHex); } }