fix.在线安装器,启动器

This commit is contained in:
lincube
2026-06-05 11:08:11 +08:00
parent bb4e90ea8d
commit 8c88e305ee
42 changed files with 1507 additions and 393 deletions

View File

@@ -24,6 +24,7 @@
<SolidColorBrush x:Key="InstallerSurfaceAltBrush" Color="#F7F7F7" />
<SolidColorBrush x:Key="InstallerSubtleFillBrush" Color="#F5F5F5" />
<SolidColorBrush x:Key="InstallerSubtleFillHoverBrush" Color="#EFEFEF" />
<SolidColorBrush x:Key="InstallerSubtleFillPressedBrush" Color="#E5E5E5" />
<SolidColorBrush x:Key="InstallerBorderBrush" Color="#14000000" />
<SolidColorBrush x:Key="InstallerStrongBorderBrush" Color="#29000000" />
<SolidColorBrush x:Key="InstallerTextPrimaryBrush" Color="#1A1A1A" />
@@ -47,6 +48,7 @@
<SolidColorBrush x:Key="InstallerSurfaceAltBrush" Color="#252525" />
<SolidColorBrush x:Key="InstallerSubtleFillBrush" Color="#333333" />
<SolidColorBrush x:Key="InstallerSubtleFillHoverBrush" Color="#3A3A3A" />
<SolidColorBrush x:Key="InstallerSubtleFillPressedBrush" Color="#444444" />
<SolidColorBrush x:Key="InstallerBorderBrush" Color="#24FFFFFF" />
<SolidColorBrush x:Key="InstallerStrongBorderBrush" Color="#3DFFFFFF" />
<SolidColorBrush x:Key="InstallerTextPrimaryBrush" Color="#F3F3F3" />

View File

@@ -2,6 +2,8 @@ namespace LanDesktopPLONDS.Installer.Services;
public static class InstallerPathGuard
{
public const string ApplicationDirectoryName = "LanMountainDesktop";
public static string GetDefaultInstallPath()
{
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
@@ -12,7 +14,29 @@ public static class InstallerPathGuard
"Programs");
}
return Path.Combine(programFiles, "LanMountainDesktop");
return Path.Combine(programFiles, ApplicationDirectoryName);
}
public static string GetInstallPathForSelectedFolder(string selectedFolder)
{
if (string.IsNullOrWhiteSpace(selectedFolder))
{
throw new ArgumentException("Selected folder is required.", nameof(selectedFolder));
}
var fullPath = Path.GetFullPath(selectedFolder.Trim());
var root = Path.GetPathRoot(fullPath);
var trimmedPath = fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var trimmedRoot = root?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var basePath = string.Equals(trimmedPath, trimmedRoot, StringComparison.OrdinalIgnoreCase)
? fullPath
: trimmedPath;
var selectedName = Path.GetFileName(trimmedPath);
var installPath = string.Equals(selectedName, ApplicationDirectoryName, StringComparison.OrdinalIgnoreCase)
? trimmedPath
: Path.Combine(basePath, ApplicationDirectoryName);
return NormalizeInstallPath(installPath);
}
public static string NormalizeInstallPath(string path)

View File

@@ -15,43 +15,9 @@ public sealed partial class InstallerStepViewModel(
[ObservableProperty]
private bool _isSelected;
[ObservableProperty]
private bool _isCompleted;
public InstallerStepId StepId { get; } = stepId;
public string Title { get; } = title;
public Icon Icon { get; } = icon;
public bool IsLocked => !IsUnlocked;
public Icon DisplayIcon => IsLocked
? Icon.LockClosed
: IsCompleted
? Icon.CheckmarkCircle
: Icon;
public bool IsAvailable => IsUnlocked && !IsSelected && !IsCompleted;
partial void OnIsUnlockedChanged(bool value)
{
_ = value;
OnPropertyChanged(nameof(IsLocked));
OnPropertyChanged(nameof(IsAvailable));
OnPropertyChanged(nameof(DisplayIcon));
}
partial void OnIsSelectedChanged(bool value)
{
_ = value;
OnPropertyChanged(nameof(IsAvailable));
}
partial void OnIsCompletedChanged(bool value)
{
_ = value;
OnPropertyChanged(nameof(DisplayIcon));
OnPropertyChanged(nameof(IsAvailable));
}
}

View File

@@ -15,7 +15,6 @@ public sealed partial class MainWindowViewModel : ObservableObject
private readonly IPrivacyDeviceIdentityProvider _privacyIdentity;
private readonly InstallerPrivacyConsentStore _privacyConsentStore;
private CancellationTokenSource? _installCts;
private bool _isNavigatingInternally;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(NextCommand))]
@@ -72,9 +71,6 @@ public sealed partial class MainWindowViewModel : ObservableObject
[ObservableProperty]
private bool _createStartupShortcut;
[ObservableProperty]
private InstallerStepViewModel? _selectedStep;
public MainWindowViewModel(
IOnlineInstallService installService,
IPrivacyDeviceIdentityProvider privacyIdentity,
@@ -92,7 +88,6 @@ public sealed partial class MainWindowViewModel : ObservableObject
new InstallerStepViewModel(InstallerStepId.Complete, "完成安装", Icon.Circle)
];
SyncSteps();
SelectedStep = Steps[0];
DeviceIdPreview = _privacyIdentity.GetOrCreateDeviceId();
PrivacyConfirmed = _privacyConsentStore.HasConfirmed(DeviceIdPreview);
}
@@ -173,22 +168,6 @@ public sealed partial class MainWindowViewModel : ObservableObject
OnPropertyChanged(nameof(CanStartInstall));
}
partial void OnSelectedStepChanged(InstallerStepViewModel? value)
{
if (_isNavigatingInternally || value is null)
{
return;
}
if (!IsInstalling && value.StepId <= MaxUnlockedStep)
{
CurrentStep = value.StepId;
return;
}
SyncSteps();
}
[RelayCommand(CanExecute = nameof(CanGoNext))]
private async Task NextAsync()
{
@@ -231,6 +210,17 @@ public sealed partial class MainWindowViewModel : ObservableObject
}
}
[RelayCommand]
private void SelectStep(InstallerStepViewModel? step)
{
if (step is null || IsInstalling || step.StepId > MaxUnlockedStep)
{
return;
}
CurrentStep = step.StepId;
}
[RelayCommand]
private async Task BrowseAsync()
{
@@ -245,7 +235,7 @@ public sealed partial class MainWindowViewModel : ObservableObject
var selected = await BrowseRequested(InstallPath);
if (!string.IsNullOrWhiteSpace(selected))
{
InstallPath = selected;
InstallPath = InstallerPathGuard.GetInstallPathForSelectedFolder(selected);
}
}
catch (Exception ex)
@@ -348,23 +338,10 @@ public sealed partial class MainWindowViewModel : ObservableObject
private void SyncSteps()
{
_isNavigatingInternally = true;
try
foreach (var step in Steps)
{
foreach (var step in Steps)
{
step.IsUnlocked = step.StepId <= MaxUnlockedStep;
step.IsSelected = step.StepId == CurrentStep;
step.IsCompleted = step.StepId < CurrentStep;
if (step.StepId == CurrentStep && !ReferenceEquals(SelectedStep, step))
{
SelectedStep = step;
}
}
}
finally
{
_isNavigatingInternally = false;
step.IsUnlocked = step.StepId <= MaxUnlockedStep;
step.IsSelected = step.StepId == CurrentStep;
}
}

View File

@@ -9,8 +9,10 @@
MinWidth="900"
MinHeight="620"
CanResize="True"
x:Name="Root"
Title="{Binding WindowTitle}"
Background="{DynamicResource InstallerWindowBackgroundBrush}"
Background="Transparent"
TransparencyLevelHint="Mica, AcrylicBlur, None"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="48"
WindowDecorations="None">
@@ -23,40 +25,54 @@
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="LineHeight" Value="20" />
</Style>
<Style Selector="ListBox.step-list">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="ListBox.step-list ListBoxItem">
<Style Selector="Button.step-nav-item">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter>
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusMd}" />
<Setter Property="Margin" Value="0,0,0,4" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0,0,0,3" />
<Setter Property="Padding" Value="0" />
<Setter Property="MinHeight" Value="48" />
<Setter Property="MinHeight" Value="40" />
</Style>
<Style Selector="ListBox.step-list ListBoxItem:pointerover">
<Style Selector="Button.step-nav-item:pointerover">
<Setter Property="Background" Value="{DynamicResource InstallerSubtleFillHoverBrush}" />
</Style>
<Style Selector="ListBox.step-list ListBoxItem:selected">
<Setter Property="Background" Value="{DynamicResource InstallerSubtleFillBrush}" />
<Style Selector="Button.step-nav-item:pressed">
<Setter Property="Background" Value="{DynamicResource InstallerSubtleFillPressedBrush}" />
</Style>
<Style Selector="ListBox.step-list ListBoxItem:disabled">
<Style Selector="Button.step-nav-item:disabled">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="Button.step-nav-item:disabled TextBlock.step-title">
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
</Style>
<Style Selector="Button.step-nav-item:disabled fi|FluentIcon">
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
</Style>
<Style Selector="Border.step-nav-selected-fill">
<Setter Property="Background" Value="{DynamicResource InstallerSubtleFillBrush}" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusMd}" />
</Style>
<Style Selector="TextBlock.step-title">
<Setter Property="FontSize" Value="13" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
</Style>
<Style Selector="Border.step-icon-host">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="Background" Value="{DynamicResource InstallerSubtleFillBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource InstallerBorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusSm}" />
<Setter Property="Foreground" Value="{DynamicResource InstallerTextSecondaryBrush}" />
</Style>
<Style Selector="Border.info-panel">
<Setter Property="Background" Value="{DynamicResource InstallerSurfaceAltBrush}" />
@@ -95,13 +111,14 @@
</Style>
</Window.Styles>
<Grid x:Name="RootGrid"
RowDefinitions="48,*"
Background="{DynamicResource InstallerWindowBackgroundBrush}">
<Border Background="{DynamicResource InstallerWindowBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusXl}"
ClipToBounds="True">
<Grid x:Name="RootGrid"
RowDefinitions="48,*"
Background="Transparent">
<Border Grid.Row="0"
Background="{DynamicResource InstallerPaneBackgroundBrush}"
BorderBrush="{DynamicResource InstallerBorderBrush}"
BorderThickness="0,0,0,1"
Background="{DynamicResource InstallerWindowBackgroundBrush}"
PointerPressed="OnTitleBarPointerPressed">
<Grid ColumnDefinitions="Auto,*,Auto">
<StackPanel Orientation="Horizontal"
@@ -147,12 +164,12 @@
</Border>
<Grid Grid.Row="1"
ColumnDefinitions="260,*">
ColumnDefinitions="260,10,*"
Margin="10,0,10,10">
<Border Grid.Column="0"
Background="{DynamicResource InstallerPaneBackgroundBrush}"
BorderBrush="{DynamicResource InstallerBorderBrush}"
BorderThickness="0,0,1,0"
Padding="24,28">
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
Padding="22,24">
<Grid RowDefinitions="Auto,*,Auto">
<StackPanel Spacing="8">
<TextBlock Text="阑山桌面"
@@ -162,71 +179,50 @@
Classes="caption-text" />
</StackPanel>
<ListBox Grid.Row="1"
Classes="step-list"
Margin="0,28,0,0"
ItemsSource="{Binding Steps}"
SelectedItem="{Binding SelectedStep, Mode=TwoWay}">
<ListBox.ItemTemplate>
<ItemsControl Grid.Row="1"
Margin="0,28,0,0"
ItemsSource="{Binding Steps}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:InstallerStepViewModel">
<Grid ColumnDefinitions="4,Auto,*"
ColumnSpacing="12"
IsEnabled="{Binding IsUnlocked}"
Margin="8,6">
<Border Background="{DynamicResource InstallerAccentBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMicro}"
IsVisible="{Binding IsSelected}" />
<Border Grid.Column="1"
Classes="step-icon-host">
<Grid>
<fi:FluentIcon Icon="{Binding DisplayIcon}"
IconVariant="Regular"
Foreground="{DynamicResource InstallerTextTertiaryBrush}"
FontSize="16"
IsVisible="{Binding IsLocked}" />
<fi:FluentIcon Icon="{Binding DisplayIcon}"
IconVariant="Regular"
Foreground="{DynamicResource InstallerSuccessBrush}"
FontSize="16"
IsVisible="{Binding IsCompleted}" />
<fi:FluentIcon Icon="{Binding DisplayIcon}"
IconVariant="Regular"
Foreground="{DynamicResource InstallerAccentBrush}"
FontSize="16"
IsVisible="{Binding IsSelected}" />
<fi:FluentIcon Icon="{Binding DisplayIcon}"
IconVariant="Regular"
Foreground="{DynamicResource InstallerTextPrimaryBrush}"
FontSize="16"
IsVisible="{Binding IsAvailable}" />
<Button Classes="step-nav-item"
Command="{Binding #Root.DataContext.SelectStepCommand}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsUnlocked}">
<Grid MinHeight="40">
<Border Classes="step-nav-selected-fill"
IsVisible="{Binding IsSelected}" />
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="10"
Margin="10,0">
<Grid Width="18"
VerticalAlignment="Center">
<fi:FluentIcon Icon="{Binding Icon}"
IconVariant="Regular"
Foreground="{DynamicResource InstallerTextSecondaryBrush}"
FontSize="17"
IsVisible="{Binding !IsSelected}" />
<fi:FluentIcon Icon="{Binding Icon}"
IconVariant="Filled"
Foreground="{DynamicResource InstallerTextPrimaryBrush}"
FontSize="17"
IsVisible="{Binding IsSelected}" />
</Grid>
<Grid Grid.Column="1"
VerticalAlignment="Center">
<TextBlock Classes="step-title"
Text="{Binding Title}"
IsVisible="{Binding !IsSelected}" />
<TextBlock Classes="step-title"
Text="{Binding Title}"
Foreground="{DynamicResource InstallerTextPrimaryBrush}"
IsVisible="{Binding IsSelected}" />
</Grid>
</Grid>
</Border>
<Grid Grid.Column="2">
<TextBlock Classes="step-title"
Text="{Binding Title}"
Foreground="{DynamicResource InstallerTextTertiaryBrush}"
VerticalAlignment="Center"
IsVisible="{Binding IsLocked}" />
<TextBlock Classes="step-title"
Text="{Binding Title}"
Foreground="{DynamicResource InstallerSuccessBrush}"
VerticalAlignment="Center"
IsVisible="{Binding IsCompleted}" />
<TextBlock Classes="step-title"
Text="{Binding Title}"
Foreground="{DynamicResource InstallerAccentBrush}"
VerticalAlignment="Center"
IsVisible="{Binding IsSelected}" />
<TextBlock Classes="step-title"
Text="{Binding Title}"
Foreground="{DynamicResource InstallerTextPrimaryBrush}"
VerticalAlignment="Center"
IsVisible="{Binding IsAvailable}" />
</Grid>
</Grid>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Grid.Row="2"
Classes="caption-text"
@@ -234,9 +230,12 @@
</Grid>
</Border>
<Grid Grid.Column="1"
RowDefinitions="*,Auto"
Background="{DynamicResource InstallerContentBackgroundBrush}">
<Border Grid.Column="2"
Background="{DynamicResource InstallerContentBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
ClipToBounds="True">
<Grid RowDefinitions="*,Auto"
Background="Transparent">
<ScrollViewer Grid.Row="0"
Padding="36,34,42,24"
VerticalScrollBarVisibility="Auto">
@@ -488,9 +487,7 @@
</ScrollViewer>
<Border Grid.Row="1"
Background="{DynamicResource InstallerContentBackgroundBrush}"
BorderBrush="{DynamicResource InstallerBorderBrush}"
BorderThickness="0,1,0,0"
Background="Transparent"
Padding="36,16,42,18">
<Grid ColumnDefinitions="*,Auto,Auto"
ColumnSpacing="8">
@@ -531,7 +528,9 @@
</Button>
</Grid>
</Border>
</Grid>
</Grid>
</Border>
</Grid>
</Grid>
</Grid>
</Border>
</Window>