Compare commits

...

3 Commits

Author SHA1 Message Date
lincube
594a62132f 0.6.6
滑动优化
2026-03-18 20:09:00 +08:00
lincube
15e589aedd 0.6.5
流畅性优化测试
2026-03-17 18:36:10 +08:00
lincube
ac4617f5cf 0.6.4 2026-03-17 14:57:41 +08:00
10 changed files with 263 additions and 12 deletions

View File

@@ -510,6 +510,8 @@ public partial class App : Application
if (languageChanged) if (languageChanged)
{ {
// 清除本地化缓存,强制重新加载语言文件
_localizationService.ClearCache();
ApplyCurrentCultureFromSettings(); ApplyCurrentCultureFromSettings();
if (_trayIcons is not null) if (_trayIcons is not null)
{ {

View File

@@ -892,6 +892,6 @@
"single_instance.notice.title": "应用已经运行", "single_instance.notice.title": "应用已经运行",
"single_instance.notice.description": "应用已经运行,无需多次点击打开。", "single_instance.notice.description": "应用已经运行,无需多次点击打开。",
"single_instance.notice.button": "确定", "single_instance.notice.button": "确定",
"market.status.install_success_restart_format": "✓ 插件"{0}"安装成功!请重启应用以激活它。", "market.status.install_success_restart_format": "✓ 插件'{0}'安装成功!请重启应用以激活它。",
"market.dialog.restart_message_format": "插件"{0}"已成功安装。\n\n要使用此插件您需要立即重启应用。\n\n是否立即重启" "market.dialog.restart_message_format": "插件'{0}'已成功安装。\n\n要使用此插件您需要立即重启应用。\n\n是否立即重启"
} }

View File

@@ -17,6 +17,23 @@ public sealed class LocalizationService
private readonly Dictionary<string, Dictionary<string, string>> _cache = private readonly Dictionary<string, Dictionary<string, string>> _cache =
new(StringComparer.OrdinalIgnoreCase); new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// 清除指定语言代码的缓存,强制下次重新加载。
/// 在语言切换时调用此方法以确保加载最新的语言文件。
/// </summary>
public void ClearCache(string? languageCode = null)
{
if (string.IsNullOrWhiteSpace(languageCode))
{
_cache.Clear();
}
else
{
var normalizedCode = NormalizeLanguageCode(languageCode);
_cache.Remove(normalizedCode);
}
}
public string NormalizeLanguageCode(string? languageCode) public string NormalizeLanguageCode(string? languageCode)
{ {
return string.Equals(languageCode, "en-US", StringComparison.OrdinalIgnoreCase) return string.Equals(languageCode, "en-US", StringComparison.OrdinalIgnoreCase)
@@ -53,7 +70,7 @@ public sealed class LocalizationService
{ {
json = json.TrimStart('\uFEFF'); json = json.TrimStart('\uFEFF');
var data = JsonSerializer.Deserialize<Dictionary<string, string>>(json, JsonOptions); var data = JsonSerializer.Deserialize<Dictionary<string, string>>(json, JsonOptions);
if (data is not null) if (data is not null && data.Count > 0)
{ {
result = new Dictionary<string, string>(data, StringComparer.OrdinalIgnoreCase); result = new Dictionary<string, string>(data, StringComparer.OrdinalIgnoreCase);
} }
@@ -64,7 +81,11 @@ public sealed class LocalizationService
// Keep empty table for resilience. // Keep empty table for resilience.
} }
_cache[languageCode] = result; // 只有当语言表非空时才缓存,这样如果加载失败可以下次重试
if (result.Count > 0)
{
_cache[languageCode] = result;
}
return result; return result;
} }

View File

@@ -294,6 +294,8 @@ internal sealed class SettingsWindowService : ISettingsWindowService
if (languageChanged) if (languageChanged)
{ {
var regionState = _settingsFacade.Region.Get(); var regionState = _settingsFacade.Region.Get();
// 清除本地化缓存,强制重新加载语言文件
_localizationService.ClearCache();
_viewModel.RefreshLanguage(regionState.LanguageCode); _viewModel.RefreshLanguage(regionState.LanguageCode);
_pageRegistry.Rebuild(); _pageRegistry.Rebuild();
_window.ReloadPages(_viewModel.CurrentPageId); _window.ReloadPages(_viewModel.CurrentPageId);

View File

@@ -964,6 +964,7 @@ public partial class MainWindow
DisposeComponentIfNeeded(host); DisposeComponentIfNeeded(host);
contentHost.Child = component; contentHost.Child = component;
ApplyDesktopEditStateToHost(host, _isComponentLibraryOpen); ApplyDesktopEditStateToHost(host, _isComponentLibraryOpen);
InvalidateDesktopPageAwareComponentContextCache();
UpdateDesktopPageAwareComponentContext(); UpdateDesktopPageAwareComponentContext();
if (_selectedDesktopComponentHost == host) if (_selectedDesktopComponentHost == host)
{ {
@@ -1102,6 +1103,7 @@ public partial class MainWindow
ClearTimeZoneServiceBindings(pageGrid.Children.OfType<Control>().ToList()); ClearTimeZoneServiceBindings(pageGrid.Children.OfType<Control>().ToList());
pageGrid.Children.Clear(); pageGrid.Children.Clear();
InvalidateDesktopPageAwareComponentContextCache();
var maxColumns = pageGrid.ColumnDefinitions.Count; var maxColumns = pageGrid.ColumnDefinitions.Count;
var maxRows = pageGrid.RowDefinitions.Count; var maxRows = pageGrid.RowDefinitions.Count;
@@ -1204,6 +1206,7 @@ public partial class MainWindow
pageGrid.Children.Add(host); pageGrid.Children.Add(host);
_desktopComponentPlacements.Add(placement); _desktopComponentPlacements.Add(placement);
InvalidateDesktopPageAwareComponentContextCache();
UpdateDesktopPageAwareComponentContext(); UpdateDesktopPageAwareComponentContext();
PersistSettings(); PersistSettings();
@@ -1577,14 +1580,86 @@ public partial class MainWindow
} }
} }
private void InvalidateDesktopPageAwareComponentContextCache()
{
_desktopPageContextInitialized = false;
_desktopPageContextActiveMask = 0;
}
private int BuildDesktopPageAwareComponentActiveMask()
{
if (_isSettingsOpen)
{
return 0;
}
var activeMask = 0;
if (_desktopSurfacePageWidth > 1 &&
_desktopPagesHostTransform is not null &&
(_isDesktopSwipeActive ||
_desktopPageContextSettlingSourceIndex is not null ||
_desktopPageContextSettlingTargetIndex is not null))
{
var viewportLeft = -_desktopPagesHostTransform.X;
var viewportRight = viewportLeft + _desktopSurfacePageWidth;
for (var pageIndex = 0; pageIndex < _desktopPageCount; pageIndex++)
{
var pageLeft = pageIndex * _desktopSurfacePageWidth;
var pageRight = pageLeft + _desktopSurfacePageWidth;
if (pageRight > viewportLeft + 0.5d && pageLeft < viewportRight - 0.5d)
{
activeMask |= 1 << pageIndex;
}
}
}
if (_currentDesktopSurfaceIndex >= 0 && _currentDesktopSurfaceIndex < _desktopPageCount)
{
activeMask |= 1 << _currentDesktopSurfaceIndex;
}
if (_desktopPageContextSettlingSourceIndex is int sourceIndex &&
sourceIndex >= 0 &&
sourceIndex < _desktopPageCount)
{
activeMask |= 1 << sourceIndex;
}
if (_desktopPageContextSettlingTargetIndex is int targetIndex &&
targetIndex >= 0 &&
targetIndex < _desktopPageCount)
{
activeMask |= 1 << targetIndex;
}
return activeMask;
}
private void UpdateDesktopPageAwareComponentContext() private void UpdateDesktopPageAwareComponentContext()
{ {
var activeDesktopPageIndex = _isSettingsOpen ? -1 : _currentDesktopSurfaceIndex;
var isEditMode = _isComponentLibraryOpen || _isSettingsOpen; var isEditMode = _isComponentLibraryOpen || _isSettingsOpen;
var activeMask = BuildDesktopPageAwareComponentActiveMask();
var pageUpdateMask = !_desktopPageContextInitialized || isEditMode != _desktopPageContextEditMode
? _desktopPageComponentGrids.Keys.Aggregate(0, (mask, pageIndex) => mask | (1 << pageIndex))
: activeMask ^ _desktopPageContextActiveMask;
if (_desktopPageContextInitialized &&
pageUpdateMask == 0 &&
isEditMode == _desktopPageContextEditMode &&
activeMask == _desktopPageContextActiveMask)
{
return;
}
foreach (var pair in _desktopPageComponentGrids) foreach (var pair in _desktopPageComponentGrids)
{ {
var isOnActivePage = pair.Key == activeDesktopPageIndex; var pageBit = 1 << pair.Key;
if ((pageUpdateMask & pageBit) == 0)
{
continue;
}
var isOnActivePage = (activeMask & pageBit) != 0;
foreach (var host in pair.Value.Children.OfType<Border>()) foreach (var host in pair.Value.Children.OfType<Border>())
{ {
if (!host.Classes.Contains(DesktopComponentHostClass)) if (!host.Classes.Contains(DesktopComponentHostClass))
@@ -1598,6 +1673,10 @@ public partial class MainWindow
} }
} }
} }
_desktopPageContextInitialized = true;
_desktopPageContextEditMode = isEditMode;
_desktopPageContextActiveMask = activeMask;
} }
private static void ApplyDesktopPageContext(Control root, bool isOnActivePage, bool isEditMode) private static void ApplyDesktopPageContext(Control root, bool isOnActivePage, bool isEditMode)

View File

@@ -5,6 +5,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@@ -16,6 +17,7 @@ using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views; namespace LanMountainDesktop.Views;
@@ -54,6 +56,8 @@ public partial class MainWindow
private int _currentDesktopSurfaceIndex; private int _currentDesktopSurfaceIndex;
private double _desktopSurfacePageWidth; private double _desktopSurfacePageWidth;
private TranslateTransform? _desktopPagesHostTransform; private TranslateTransform? _desktopPagesHostTransform;
private Transitions? _desktopPagesHostSnapTransitions;
private bool _desktopPagesHostTransitionsSuspended;
private bool _isDesktopSwipeActive; private bool _isDesktopSwipeActive;
private bool _isDesktopSwipeDirectionLocked; private bool _isDesktopSwipeDirectionLocked;
private Point _desktopSwipeStartPoint; private Point _desktopSwipeStartPoint;
@@ -62,6 +66,12 @@ public partial class MainWindow
private long _desktopSwipeLastTimestamp; private long _desktopSwipeLastTimestamp;
private double _desktopSwipeVelocityX; private double _desktopSwipeVelocityX;
private double _desktopSwipeBaseOffset; private double _desktopSwipeBaseOffset;
private bool _desktopPageContextInitialized;
private bool _desktopPageContextEditMode;
private int _desktopPageContextActiveMask;
private int? _desktopPageContextSettlingSourceIndex;
private int? _desktopPageContextSettlingTargetIndex;
private int _desktopPageContextSettleRevision;
private int LauncherSurfaceIndex => Math.Max(MinDesktopPageCount, _desktopPageCount); private int LauncherSurfaceIndex => Math.Max(MinDesktopPageCount, _desktopPageCount);
@@ -164,6 +174,15 @@ public partial class MainWindow
DesktopPagesHost.RenderTransform = _desktopPagesHostTransform; DesktopPagesHost.RenderTransform = _desktopPagesHostTransform;
} }
if (_desktopPagesHostTransitionsSuspended)
{
_desktopPagesHostTransform.Transitions = null;
}
else
{
_desktopPagesHostSnapTransitions ??= _desktopPagesHostTransform.Transitions;
}
var viewportRow = gridMetrics.RowCount > 2 ? 1 : 0; var viewportRow = gridMetrics.RowCount > 2 ? 1 : 0;
var viewportRowSpan = gridMetrics.RowCount > 2 ? gridMetrics.RowCount - 2 : 1; var viewportRowSpan = gridMetrics.RowCount > 2 ? gridMetrics.RowCount - 2 : 1;
var pageWidth = Math.Max(1, gridMetrics.GridWidthPx); var pageWidth = Math.Max(1, gridMetrics.GridWidthPx);
@@ -200,6 +219,7 @@ public partial class MainWindow
DesktopPagesContainer.Width = pageWidth * _desktopPageCount; DesktopPagesContainer.Width = pageWidth * _desktopPageCount;
DesktopPagesContainer.Height = pageHeight; DesktopPagesContainer.Height = pageHeight;
_desktopPageComponentGrids.Clear(); _desktopPageComponentGrids.Clear();
InvalidateDesktopPageAwareComponentContextCache();
for (var index = 0; index < _desktopPageCount; index++) for (var index = 0; index < _desktopPageCount; index++)
{ {
DesktopPagesContainer.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(pageWidth, GridUnitType.Pixel))); DesktopPagesContainer.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(pageWidth, GridUnitType.Pixel)));
@@ -354,6 +374,88 @@ public partial class MainWindow
UpdateDesktopPageAwareComponentContext(); UpdateDesktopPageAwareComponentContext();
} }
private void SetDesktopPagesHostSnapAnimationEnabled(bool enabled)
{
if (_desktopPagesHostTransform is null)
{
return;
}
if (enabled)
{
if (!_desktopPagesHostTransitionsSuspended)
{
return;
}
_desktopPagesHostTransform.Transitions = _desktopPagesHostSnapTransitions;
_desktopPagesHostTransitionsSuspended = false;
return;
}
if (_desktopPagesHostTransitionsSuspended)
{
return;
}
_desktopPagesHostSnapTransitions ??= _desktopPagesHostTransform.Transitions;
_desktopPagesHostTransform.Transitions = null;
_desktopPagesHostTransitionsSuspended = true;
}
private void ClearDesktopPageContextSettle(bool refreshContext)
{
_desktopPageContextSettleRevision++;
_desktopPageContextSettlingSourceIndex = null;
_desktopPageContextSettlingTargetIndex = null;
if (refreshContext)
{
UpdateDesktopPageAwareComponentContext();
}
}
private void BeginDesktopPageContextSettle(int previousIndex, int targetIndex)
{
var sourceIndex = previousIndex >= 0 && previousIndex < _desktopPageCount
? previousIndex
: (int?)null;
var destinationIndex = targetIndex >= 0 && targetIndex < _desktopPageCount
? targetIndex
: (int?)null;
if (sourceIndex == destinationIndex && destinationIndex is not null)
{
ClearDesktopPageContextSettle(refreshContext: false);
return;
}
if (sourceIndex is null && destinationIndex is null)
{
ClearDesktopPageContextSettle(refreshContext: false);
return;
}
_desktopPageContextSettleRevision++;
var settleRevision = _desktopPageContextSettleRevision;
_desktopPageContextSettlingSourceIndex = sourceIndex;
_desktopPageContextSettlingTargetIndex = destinationIndex;
DispatcherTimer.RunOnce(
() =>
{
if (settleRevision != _desktopPageContextSettleRevision)
{
return;
}
_desktopPageContextSettlingSourceIndex = null;
_desktopPageContextSettlingTargetIndex = null;
UpdateDesktopPageAwareComponentContext();
},
FluttermotionToken.Page + TimeSpan.FromMilliseconds(36));
}
private void MoveSurfaceBy(int delta) private void MoveSurfaceBy(int delta)
{ {
if (delta == 0) if (delta == 0)
@@ -373,9 +475,11 @@ public partial class MainWindow
return; return;
} }
var previousIndex = _currentDesktopSurfaceIndex;
_currentDesktopSurfaceIndex = target; _currentDesktopSurfaceIndex = target;
BeginDesktopPageContextSettle(previousIndex, target);
ApplyDesktopSurfaceOffset(); ApplyDesktopSurfaceOffset();
PersistSettings(); SchedulePersistSettings(delayMs: Math.Max(280, (int)FluttermotionToken.Page.TotalMilliseconds + 80));
} }
private bool CanSwipeDesktopSurface() private bool CanSwipeDesktopSurface()
@@ -426,6 +530,7 @@ public partial class MainWindow
return; return;
} }
ClearDesktopPageContextSettle(refreshContext: false);
_isDesktopSwipeActive = true; _isDesktopSwipeActive = true;
_isDesktopSwipeDirectionLocked = false; _isDesktopSwipeDirectionLocked = false;
_desktopSwipeStartPoint = pointerInViewport; _desktopSwipeStartPoint = pointerInViewport;
@@ -603,6 +708,7 @@ public partial class MainWindow
} }
_isDesktopSwipeDirectionLocked = true; _isDesktopSwipeDirectionLocked = true;
SetDesktopPagesHostSnapAnimationEnabled(enabled: false);
if (e.Pointer.Captured != DesktopPagesViewport) if (e.Pointer.Captured != DesktopPagesViewport)
{ {
e.Pointer.Capture(DesktopPagesViewport); e.Pointer.Capture(DesktopPagesViewport);
@@ -621,6 +727,7 @@ public partial class MainWindow
} }
_desktopPagesHostTransform.X = tentative; _desktopPagesHostTransform.X = tentative;
UpdateDesktopPageAwareComponentContext();
e.Handled = true; e.Handled = true;
} }
@@ -656,6 +763,7 @@ public partial class MainWindow
_desktopSwipeLastTimestamp = 0; _desktopSwipeLastTimestamp = 0;
if (wasDirectionLocked) if (wasDirectionLocked)
{ {
SetDesktopPagesHostSnapAnimationEnabled(enabled: true);
ApplyDesktopSurfaceOffset(); ApplyDesktopSurfaceOffset();
} }
} }
@@ -682,6 +790,8 @@ public partial class MainWindow
return false; return false;
} }
SetDesktopPagesHostSnapAnimationEnabled(enabled: true);
var deltaX = _desktopSwipeCurrentPoint.X - _desktopSwipeStartPoint.X; var deltaX = _desktopSwipeCurrentPoint.X - _desktopSwipeStartPoint.X;
var deltaY = _desktopSwipeCurrentPoint.Y - _desktopSwipeStartPoint.Y; var deltaY = _desktopSwipeCurrentPoint.Y - _desktopSwipeStartPoint.Y;
var absDeltaX = Math.Abs(deltaX); var absDeltaX = Math.Abs(deltaX);

View File

@@ -32,6 +32,11 @@ public partial class MainWindow
{ {
_ = sender; _ = sender;
if (_suppressOwnSettingsReloadCount > 0)
{
return;
}
if (e.Scope == SettingsScope.App && e.ChangedKeys is { Count: > 0 }) if (e.Scope == SettingsScope.App && e.ChangedKeys is { Count: > 0 })
{ {
var changedKeys = e.ChangedKeys.ToArray(); var changedKeys = e.ChangedKeys.ToArray();
@@ -382,6 +387,7 @@ public partial class MainWindow
private void PersistSettings() private void PersistSettings()
{ {
_persistSettingsRevision++;
if (_suppressSettingsPersistence) if (_suppressSettingsPersistence)
{ {
return; return;
@@ -389,6 +395,8 @@ public partial class MainWindow
try try
{ {
// Saving our own state should not trigger a full external reload cycle.
_suppressOwnSettingsReloadCount++;
_settingsService.SaveSnapshot(SettingsScope.App, BuildAppSettingsSnapshot()); _settingsService.SaveSnapshot(SettingsScope.App, BuildAppSettingsSnapshot());
_componentLayoutStore.SaveLayout(BuildDesktopLayoutSettingsSnapshot()); _componentLayoutStore.SaveLayout(BuildDesktopLayoutSettingsSnapshot());
_settingsService.SaveSnapshot(SettingsScope.Launcher, BuildLauncherSettingsSnapshot()); _settingsService.SaveSnapshot(SettingsScope.Launcher, BuildLauncherSettingsSnapshot());
@@ -397,11 +405,29 @@ public partial class MainWindow
{ {
AppLogger.Warn("SettingsRuntime", "Failed to persist settings.", ex); AppLogger.Warn("SettingsRuntime", "Failed to persist settings.", ex);
} }
finally
{
if (_suppressOwnSettingsReloadCount > 0)
{
_suppressOwnSettingsReloadCount--;
}
}
} }
private void SchedulePersistSettings(int delayMs = 200) private void SchedulePersistSettings(int delayMs = 200)
{ {
DispatcherTimer.RunOnce(PersistSettings, TimeSpan.FromMilliseconds(Math.Max(0, delayMs))); var revision = ++_persistSettingsRevision;
DispatcherTimer.RunOnce(
() =>
{
if (revision != _persistSettingsRevision)
{
return;
}
PersistSettings();
},
TimeSpan.FromMilliseconds(Math.Max(0, delayMs)));
} }
internal void ReloadFromPersistedSettings() internal void ReloadFromPersistedSettings()

View File

@@ -145,7 +145,9 @@
<TranslateTransform> <TranslateTransform>
<TranslateTransform.Transitions> <TranslateTransform.Transitions>
<Transitions> <Transitions>
<DoubleTransition Property="X" Duration="{StaticResource FluttermotionToken.Duration.Page}" /> <DoubleTransition Property="X"
Duration="{StaticResource FluttermotionToken.Duration.Page}"
Easing="0.22,1,0.36,1" />
</Transitions> </Transitions>
</TranslateTransform.Transitions> </TranslateTransform.Transitions>
</TranslateTransform> </TranslateTransform>

View File

@@ -153,6 +153,8 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
private bool _isWeatherPreviewInProgress; private bool _isWeatherPreviewInProgress;
private ClockDisplayFormat _clockDisplayFormat = ClockDisplayFormat.HourMinuteSecond; private ClockDisplayFormat _clockDisplayFormat = ClockDisplayFormat.HourMinuteSecond;
private bool _externalSettingsReloadPending; private bool _externalSettingsReloadPending;
private int _persistSettingsRevision;
private int _suppressOwnSettingsReloadCount;
private double CurrentDesktopPitch => _currentDesktopCellSize + _currentDesktopCellGap; private double CurrentDesktopPitch => _currentDesktopCellSize + _currentDesktopCellGap;
public MainWindow() public MainWindow()

View File

@@ -70,9 +70,11 @@
</Grid> </Grid>
<Grid ColumnDefinitions="Auto,*" <Grid ColumnDefinitions="Auto,*"
RowDefinitions="Auto,Auto"
ColumnSpacing="20" ColumnSpacing="20"
RowSpacing="16"> RowSpacing="16">
<StackPanel Grid.Column="0" <StackPanel Grid.Row="0"
Grid.Column="0"
Spacing="4"> Spacing="4">
<TextBlock Classes="update-kv-label" <TextBlock Classes="update-kv-label"
Text="{Binding CurrentVersionLabel}" /> Text="{Binding CurrentVersionLabel}" />
@@ -80,7 +82,8 @@
Text="{Binding CurrentVersionText}" /> Text="{Binding CurrentVersionText}" />
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" <StackPanel Grid.Row="0"
Grid.Column="1"
Spacing="4" Spacing="4"
IsVisible="{Binding IsLatestVersionVisible}"> IsVisible="{Binding IsLatestVersionVisible}">
<TextBlock Classes="update-kv-label" <TextBlock Classes="update-kv-label"
@@ -110,22 +113,26 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
<StackPanel Spacing="12"> <StackPanel Spacing="12"
HorizontalAlignment="Left">
<TextBlock Classes="settings-item-description" <TextBlock Classes="settings-item-description"
Text="{Binding UpdateStatus}" Text="{Binding UpdateStatus}"
TextWrapping="Wrap" TextWrapping="Wrap"
HorizontalAlignment="Left"
MaxWidth="500" /> MaxWidth="500" />
<ProgressBar Minimum="0" <ProgressBar Minimum="0"
Maximum="100" Maximum="100"
Value="{Binding DownloadProgressValue}" Value="{Binding DownloadProgressValue}"
IsVisible="{Binding IsDownloadProgressVisible}" IsVisible="{Binding IsDownloadProgressVisible}"
HorizontalAlignment="Stretch"
Margin="0,4,0,4" /> Margin="0,4,0,4" />
<TextBlock Classes="settings-item-description" <TextBlock Classes="settings-item-description"
IsVisible="{Binding IsDownloadProgressVisible}" IsVisible="{Binding IsDownloadProgressVisible}"
Text="{Binding DownloadProgressText}" Text="{Binding DownloadProgressText}"
TextWrapping="Wrap" TextWrapping="Wrap"
HorizontalAlignment="Left"
Margin="0,4,0,0" /> Margin="0,4,0,0" />
</StackPanel> </StackPanel>