Merge pull request 'master' (#3) from CWX/SPT-AKI-Installer:master into master

Reviewed-on: waffle.lord/SPT-AKI-Installer#3
This commit is contained in:
IsWaffle 2023-07-30 00:07:02 +00:00
commit 4f0eba1ac8
20 changed files with 454 additions and 83 deletions

View File

@ -1,4 +1,4 @@
<Application xmlns="https://github.com/avaloniaui"
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SPTInstaller"
x:Class="SPTInstaller.App">

View File

@ -5,7 +5,7 @@
>
<Design.PreviewWith>
<StackPanel Spacing="5" Background="{StaticResource AKI_Background_Dark}">
<Button Content="Blah"/>
<Button Content="Blah" Classes="outlinedTLCorner"/>
<TextBox Text="Some cool text here" Margin="5"/>
<TextBox Watermark="This is a watermark" Margin="5"/>
</StackPanel>
@ -64,6 +64,11 @@
<Setter Property="BorderThickness" Value="1"/>
</Style>
<!-- TextBlock Styles -->
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
</Style>
<!-- Label Styles -->
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Label.xaml -->
<Style Selector="Label">
@ -163,25 +168,41 @@
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
</Style>
<!-- Button Link Style -->
<Style Selector="Button.link">
<!-- Button outlined Style -->
<Style Selector="Button.outlinedTLCorner">
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
<Setter Property="BorderThickness" Value="2 2 0 0"/>
</Style>
<Style Selector="Button.link:pointerover /template/ ContentPresenter">
<Style Selector="Button.outlinedTLCorner:pointerover /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
<Setter Property="BorderThickness" Value="1 1 0 0"/>
</Style>
<Style Selector="Button.link:pressed /template/ ContentPresenter">
<Style Selector="Button.outlinedTLCorner:pressed /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<!-- PreCheck Path Styles -->
<Style Selector="Path.passed">
<Setter Property="Data" Value="{StaticResource CircledCheck}"/>
<Setter Property="Fill" Value="Green"/>
</Style>
<Style Selector="Path.failed">
<Setter Property="Data" Value="{StaticResource CircledX}"/>
<Setter Property="Fill" Value="Red"/>
</Style>
<Style Selector="Path.warning">
<Setter Property="Data" Value="{StaticResource CircledWarn}"/>
<Setter Property="Fill" Value="Goldenrod"/>
</Style>
</Styles>

View File

@ -32,7 +32,7 @@ public class InstallController
{
var result = await check.RunCheck();
Log.Information($"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}");
Log.Information($"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}\nDetail: {check.PreCheckDetails.ReplaceLineEndings(" ")}");
if (check.IsRequired)
{

View File

@ -0,0 +1,83 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SPTInstaller.CustomControls.DetailedPreCheckItem"
Background="Transparent" MinHeight="100">
<UserControl.Styles>
<Style Selector="Arc.running">
<Setter Property="Stroke" Value="DodgerBlue"/>
</Style>
<Style Selector="Arc">
<Setter Property="Stroke" Value="Gray"/>
<Setter Property="IsVisible" Value="{Binding IsPending, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Style.Animations>
<Animation Duration="0:0:1" RepeatCount="infinite">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="0"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="360"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</UserControl.Styles>
<Grid RowDefinitions="3,AUTO,3,*,30,3" ColumnDefinitions="3,*,AUTO,3" Margin="10">
<Border Grid.RowSpan="5" Grid.ColumnSpan="3"
Background="{StaticResource AKI_Background_Light}" CornerRadius="8"
BoxShadow="3 3 10 .1 black"
/>
<Border Grid.RowSpan="3" Grid.ColumnSpan="3"
Background="{StaticResource AKI_Brush_DarkGrayBlue}" CornerRadius="8 8 0 0"
/>
<Grid Grid.Row="1" Grid.Column="1" ColumnDefinitions="22, AUTO" Margin="3">
<Canvas Margin="0 3 0 0"
IsVisible="{Binding !IsPending, RelativeSource={RelativeSource AncestorType=UserControl}}">
<Ellipse Fill="White" Height="15" Width="15" Canvas.Top="3" Canvas.Left="3"
/>
<Path Name="iconPath" StrokeThickness="2"
Classes.passed="{Binding Passed, RelativeSource={RelativeSource AncestorType=UserControl}}"
>
<Classes.failed>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="IsRequired"/>
<Binding Path="!Passed"/>
</MultiBinding>
</Classes.failed>
<Classes.warning>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsRequired"/>
<Binding Path="!Passed"/>
</MultiBinding>
</Classes.warning>
</Path>
</Canvas>
<Arc StartAngle="280" SweepAngle="80" Margin="0 3 0 0" StrokeThickness="3"
Width="20" Height="20" VerticalAlignment="Top"
Classes.running="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
<Label Grid.Column="1"
Content="{Binding PreCheckName, RelativeSource={RelativeSource AncestorType=UserControl}}"
Classes.bold="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</Grid>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" TextWrapping="Wrap"
Margin="10"
Text="{Binding PreCheckDetails, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
<Button Grid.Row="4" Grid.Column="2"
CornerRadius="8 0 8 0" Classes="outlinedTLCorner"
Content="{Binding ActionButtonText, RelativeSource={RelativeSource AncestorType=UserControl}}"
Command="{Binding ActionButtonCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
IsVisible="{Binding ActionButtonIsVisible, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</Grid>
</UserControl>

View File

@ -0,0 +1,48 @@
using Avalonia;
using System.Windows.Input;
namespace SPTInstaller.CustomControls;
public partial class DetailedPreCheckItem : PreCheckItem
{
public DetailedPreCheckItem()
{
InitializeComponent();
}
public string PreCheckDetails
{
get => GetValue(PreCheckDetailsProperty);
set => SetValue(PreCheckDetailsProperty, value);
}
public static readonly StyledProperty<string> PreCheckDetailsProperty =
AvaloniaProperty.Register<DetailedPreCheckItem, string>(nameof(PreCheckDetails));
public bool ActionButtonIsVisible
{
get => GetValue(ActionButtonIsVisibleProperty);
set => SetValue(ActionButtonIsVisibleProperty, value);
}
public static readonly StyledProperty<bool> ActionButtonIsVisibleProperty =
AvaloniaProperty.Register<DetailedPreCheckItem, bool>(nameof(ActionButtonIsVisible));
public string ActionButtonText
{
get => GetValue(ActionButtonTextProperty);
set => SetValue(ActionButtonTextProperty, value);
}
public static readonly StyledProperty<string> ActionButtonTextProperty =
AvaloniaProperty.Register<DetailedPreCheckItem, string>(nameof(ActionButtonText));
public ICommand ActionButtonCommand
{
get => GetValue(ActionButtonCommandProperty);
set => SetValue(ActionButtonCommandProperty, value);
}
public static readonly StyledProperty<ICommand> ActionButtonCommandProperty =
AvaloniaProperty.Register<DetailedPreCheckItem, ICommand>(nameof(ActionButtonCommand));
}

View File

@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -6,7 +6,6 @@
x:Class="SPTInstaller.CustomControls.PreCheckItem">
<UserControl.Styles>
<Style Selector="Arc.running">
<Setter Property="Stroke" Value="DodgerBlue"/>
</Style>
@ -29,23 +28,6 @@
<Style Selector="Label.bold">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style Selector="Path.passed">
<Setter Property="Data" Value="{StaticResource CircledCheck}"/>
<Setter Property="Fill" Value="Green"/>
</Style>
<Style Selector="Path.failed">
<Setter Property="Data" Value="{StaticResource CircledX}"/>
<Setter Property="Fill" Value="Red"/>
<Setter Property="ToolTip.Tip" Value="A required dependency could not be found"/>
</Style>
<Style Selector="Path.warning">
<Setter Property="Data" Value="{StaticResource CircledWarn}"/>
<Setter Property="Fill" Value="Goldenrod"/>
<Setter Property="ToolTip.Tip" Value="This dependency could not be found"/>
</Style>
</UserControl.Styles>

View File

@ -1,29 +1,38 @@
using System.Linq;
using Serilog;
namespace SPTInstaller.Helpers;
public static class DirectorySizeHelper
{
public static bool CheckAvailableSize(string eftSourceDirPath, string installTargetDirPath)
// SizeSuffix implementation found here:
// https://stackoverflow.com/a/14488941
static readonly string[] SizeSuffixes =
{ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
public static string SizeSuffix(Int64 value, int decimalPlaces = 1)
{
try
{
var eftSourceDirectoryInfo = new DirectoryInfo(eftSourceDirPath);
var installTargetDirectoryInfo = new DirectoryInfo(installTargetDirPath);
var eftSourceDirSize = GetSizeOfDirectory(eftSourceDirectoryInfo);
var availableSize = DriveInfo.GetDrives().FirstOrDefault(d => d.Name == installTargetDirectoryInfo.Root.Name)?.AvailableFreeSpace ?? 0;
if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
if (value < 0) { return "-" + SizeSuffix(-value, decimalPlaces); }
if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }
return eftSourceDirSize < availableSize;
}
catch (Exception ex)
{
Log.Error(ex, "Error while checking available size");
// mag is 0 for bytes, 1 for KB, 2, for MB, etc.
int mag = (int)Math.Log(value, 1024);
return false;
// 1L << (mag * 10) == 2 ^ (10 * mag)
// [i.e. the number of bytes in the unit corresponding to mag]
decimal adjustedSize = (decimal)value / (1L << (mag * 10));
// make adjustment when the value is large enough that
// it would round up to 1000 or more
if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
{
mag += 1;
adjustedSize /= 1024;
}
return string.Format("{0:n" + decimalPlaces + "} {1}",
adjustedSize,
SizeSuffixes[mag]);
}
private static long GetSizeOfDirectory(DirectoryInfo sourceDir) => sourceDir.EnumerateFiles("*", SearchOption.AllDirectories).Sum(fi => fi.Length);
public static long GetSizeOfDirectory(DirectoryInfo sourceDir) => sourceDir.EnumerateFiles("*", SearchOption.AllDirectories).Sum(fi => fi.Length);
}

View File

@ -16,16 +16,7 @@ public class InitializationTask : InstallerTaskBase
public override async Task<IResult> TaskOperation()
{
SetStatus("Initializing", $"Target Install Path: {FileHelper.GetRedactedPath(_data.TargetInstallPath)}");
_data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
if (_data.OriginalGamePath == null)
{
return Result.FromError("EFT IS NOT INSTALLED!");
}
SetStatus(null, $"Installed EFT Game Path: {FileHelper.GetRedactedPath(_data.OriginalGamePath)}");
SetStatus("Initializing", $"Installed EFT Game Path: {FileHelper.GetRedactedPath(_data.OriginalGamePath)}");
var result = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath);

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Linq;
using System.Threading.Tasks;
using SPTInstaller.Helpers;
using SPTInstaller.Models;
@ -13,13 +14,35 @@ public class FreeSpacePreCheck : PreCheckBase
_internalData = internalData;
}
public override async Task<bool> CheckOperation()
public override async Task<PreCheckResult> CheckOperation()
{
if (_internalData.OriginalGamePath is null || _internalData.TargetInstallPath is null)
{
return false;
}
if (_internalData.OriginalGamePath is null)
return PreCheckResult.FromError("Could not find EFT game path");
return DirectorySizeHelper.CheckAvailableSize(_internalData.OriginalGamePath, _internalData.TargetInstallPath);
if (_internalData.TargetInstallPath is null)
return PreCheckResult.FromError("Could not find install target path");
try
{
var eftSourceDirectoryInfo = new DirectoryInfo(_internalData.OriginalGamePath);
var installTargetDirectoryInfo = new DirectoryInfo(_internalData.TargetInstallPath);
var eftSourceDirSize = DirectorySizeHelper.GetSizeOfDirectory(eftSourceDirectoryInfo);
var availableSize = DriveInfo.GetDrives().FirstOrDefault(d => d.Name.ToLower() == installTargetDirectoryInfo.Root.Name.ToLower())?.AvailableFreeSpace ?? 0;
var availableSpaceMessage = $"Available Space: {DirectorySizeHelper.SizeSuffix(availableSize, 2)}";
var requiredSpaceMessage = $"Space Required for EFT Client: {DirectorySizeHelper.SizeSuffix(eftSourceDirSize, 2)}";
if (eftSourceDirSize > availableSize)
{
return PreCheckResult.FromError($"Not enough free space on {installTargetDirectoryInfo.Root.Name} to install SPT\n\n{availableSpaceMessage}\n{requiredSpaceMessage}");
}
return PreCheckResult.FromSuccess($"There is enough space available on {installTargetDirectoryInfo.Root.Name} to install SPT.\n\n{availableSpaceMessage}\n{requiredSpaceMessage}");
}
catch (Exception ex)
{
return PreCheckResult.FromException(ex);
}
}
}

View File

@ -10,11 +10,24 @@ public class NetCore6PreCheck : PreCheckBase
{
}
public override async Task<bool> CheckOperation()
public override async Task<PreCheckResult> CheckOperation()
{
var minRequiredVersion = new Version("6.0.0");
string[] output;
var failedButtonText = "Download .Net Core 6 Desktop Runtime";
var failedButtonAction = () =>
{
Process.Start(new ProcessStartInfo
{
FileName = "cmd.exe",
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden,
ArgumentList = { "/C", "start", "https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-6.0.4-windows-x64-installer" }
});
};
try
{
var proc = Process.Start(new ProcessStartInfo()
@ -32,9 +45,11 @@ public class NetCore6PreCheck : PreCheckBase
catch (Exception ex)
{
// TODO: logging
return false;
return PreCheckResult.FromException(ex);
}
var highestFoundVersion = new Version("0.0.0");
foreach (var lineVersion in output)
{
if (lineVersion.StartsWith("Microsoft.WindowsDesktop.App") && lineVersion.Split(" ").Length > 1)
@ -43,14 +58,16 @@ public class NetCore6PreCheck : PreCheckBase
var foundVersion = new Version(stringVerion);
// not fully sure if we should only check for 6.x.x versions or if higher major versions are ok -waffle
// waffle: not fully sure if we should only check for 6.x.x versions or if higher major versions are ok
if (foundVersion >= minRequiredVersion)
{
return true;
return PreCheckResult.FromSuccess($".Net Core {minRequiredVersion} Desktop Runtime or higher is installed.\n\nInstalled Version: {foundVersion}");
}
highestFoundVersion = foundVersion > highestFoundVersion ? foundVersion : highestFoundVersion;
}
}
return false;
return PreCheckResult.FromError($".Net Core Desktop Runtime version {minRequiredVersion} or higher is required.\n\nHighest Version Found: {(highestFoundVersion > new Version("0.0.0") ? highestFoundVersion : "Not Found")}\n\nThis is required to play SPT, but you can install it later if and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
}
}

View File

@ -1,5 +1,6 @@
using Microsoft.Win32;
using SPTInstaller.Models;
using System.Diagnostics;
using System.Threading.Tasks;
namespace SPTInstaller.Installer_Tasks.PreChecks;
@ -10,7 +11,7 @@ public class NetFramework472PreCheck : PreCheckBase
{
}
public override async Task<bool> CheckOperation()
public override async Task<PreCheckResult> CheckOperation()
{
try
{
@ -18,27 +19,45 @@ public class NetFramework472PreCheck : PreCheckBase
var key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full");
var failedButtonText = "Download .Net Framework 4.7.2";
var failedButtonAction = () =>
{
Process.Start(new ProcessStartInfo
{
FileName = "cmd.exe",
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden,
ArgumentList = { "/C", "start", "https://dotnet.microsoft.com/download/dotnet-framework/thank-you/net472-developer-pack-offline-installer" }
});
};
if (key == null)
{
return false;
return PreCheckResult.FromError("Could not find .Net Framework on system.\n\nThis is required to play SPT, but you can install it later and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
}
var value = key.GetValue("Version");
if (value != null && value is string versionString)
if (value == null || value is not string versionString)
{
var installedVersion = new Version(versionString);
return installedVersion > minRequiredVersion;
return PreCheckResult.FromError("Something went wrong. This precheck failed for an unknown reason. :(");
}
return false;
var installedVersion = new Version(versionString);
if (installedVersion < minRequiredVersion)
{
return PreCheckResult.FromError($".Net Framework {versionString} is installed, but {minRequiredVersion} or higher is required.\n\nYou can install it later and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
}
return PreCheckResult.FromSuccess($".Net Framework {minRequiredVersion} or higher is installed.\n\nInstalled Version: {installedVersion}");
}
catch (Exception ex)
{
// TODO: log exceptions
return false;
return PreCheckResult.FromException(ex);
}
}
}

View File

@ -12,5 +12,7 @@ public interface IPreCheck
public bool Passed { get; }
public string PreCheckDetails { get; }
public Task<IResult> RunCheck();
}

View File

@ -1,6 +1,7 @@
using ReactiveUI;
using SPTInstaller.Interfaces;
using System.Threading.Tasks;
using System.Windows.Input;
namespace SPTInstaller.Models;
@ -48,6 +49,34 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
set => this.RaiseAndSetIfChanged(ref _isRunning, value);
}
private string _preCheckDetails;
public string PreCheckDetails
{
get => _preCheckDetails;
set => this.RaiseAndSetIfChanged(ref _preCheckDetails, value);
}
private bool _actionButtonIsVisible;
public bool ActionButtonIsVisible
{
get => _actionButtonIsVisible;
set => this.RaiseAndSetIfChanged(ref _actionButtonIsVisible, value);
}
private string _actionButtonText;
public string ActionButtonText
{
get => _actionButtonText;
set => this.RaiseAndSetIfChanged(ref _actionButtonText, value);
}
private ICommand _actionButtonCommand;
public ICommand ActionButtonCommand
{
get => _actionButtonCommand;
set => this.RaiseAndSetIfChanged(ref _actionButtonCommand, value);
}
/// <summary>
/// Base class for pre-checks to run before installation
/// </summary>
@ -63,12 +92,23 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
public async Task<IResult> RunCheck()
{
IsRunning = true;
Passed = await CheckOperation();
var result = await CheckOperation();
Passed = result.Succeeded;
PreCheckDetails = !string.IsNullOrWhiteSpace(result.Message)
? result.Message
: (result.Succeeded ? "Pre-Check succeeded, but no details were provided" : "Pre-Check failed, but no details were provided");
ActionButtonText = result.ActionButtonText;
ActionButtonCommand = result.ButtonPressedCommand;
ActionButtonIsVisible = result.ActionButtonIsVisible;
IsRunning = false;
IsPending = false;
return Passed ? Result.FromSuccess() : Result.FromError($"PreCheck Failed: {Name}");
}
public abstract Task<bool> CheckOperation();
public abstract Task<PreCheckResult> CheckOperation();
}

View File

@ -0,0 +1,37 @@
using ReactiveUI;
using SPTInstaller.Interfaces;
using System.Windows.Input;
namespace SPTInstaller.Models;
public class PreCheckResult : IResult
{
public bool Succeeded { get; private set; }
public string Message { get; private set; }
public bool ActionButtonIsVisible { get; private set; }
public string ActionButtonText { get; private set; }
public ICommand ButtonPressedCommand { get; private set; }
protected PreCheckResult(string message, bool succeeded, string actionButtonText, Action? buttonPressedAction)
{
Message = message;
Succeeded = succeeded;
ActionButtonText = actionButtonText;
ActionButtonIsVisible = buttonPressedAction != null && !string.IsNullOrWhiteSpace(actionButtonText);
buttonPressedAction ??= () => { };
ButtonPressedCommand = ReactiveCommand.Create(buttonPressedAction);
}
public static PreCheckResult FromSuccess(string message = "") => new PreCheckResult(message, true, "", null);
public static PreCheckResult FromError(string message, string actionButtonText = "", Action? actionButtonPressedAction = null) => new PreCheckResult(message, false, actionButtonText, actionButtonPressedAction);
public static PreCheckResult FromException(Exception ex, string actionButtonText = "", Action? actionButtonPressedAction = null) => new PreCheckResult($"An exception was thrown during this precheck\n\nException:\n{ex.Message}\n\nStacktrace:\n{ex.StackTrace}", false, actionButtonText, actionButtonPressedAction);
}

View File

@ -9,8 +9,8 @@
<PackageIcon>icon.ico</PackageIcon>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<Configurations>Debug;Release;TEST</Configurations>
<AssemblyVersion>2.3</AssemblyVersion>
<FileVersion>2.3</FileVersion>
<AssemblyVersion>2.5</AssemblyVersion>
<FileVersion>2.5</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -0,0 +1,9 @@
using ReactiveUI;
namespace SPTInstaller.ViewModels;
public class DetailedPreChecksViewModel : PreChecksViewModel
{
public DetailedPreChecksViewModel(IScreen host) : base(host)
{
}
}

View File

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using System.Windows.Input;
using ReactiveUI;
using Serilog;
using SPTInstaller.Controllers;
using SPTInstaller.Helpers;
using SPTInstaller.Models;
@ -15,6 +16,8 @@ public class PreChecksViewModel : ViewModelBase
public ObservableCollection<PreCheckBase> PreChecks { get; set; } = new(ServiceHelper.GetAll<PreCheckBase>());
public ICommand StartInstallCommand { get; set; }
public ICommand ShowDetailedViewCommand { get; set; }
public string InstallPath
{
get => _installPath;
@ -39,10 +42,21 @@ public class PreChecksViewModel : ViewModelBase
}
data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
if (data.OriginalGamePath == null)
{
NavigateTo(new MessageViewModel(HostScreen, Result.FromError("Could not find EFT install.\n\nDo you own and have the game installed?")));
}
data.TargetInstallPath = Environment.CurrentDirectory;
InstallPath = data.TargetInstallPath;
StartInstallCommand = ReactiveCommand.Create(() => NavigateTo(new InstallViewModel(HostScreen)));
ShowDetailedViewCommand = ReactiveCommand.Create(() =>
{
Log.Logger.Information("Opening Detailed PreCheck View");
NavigateTo(new DetailedPreChecksViewModel(HostScreen));
});
Task.Run(async () =>
{

View File

@ -0,0 +1,61 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cc="using:SPTInstaller.CustomControls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SPTInstaller.Views.DetailedPreChecksView">
<Grid RowDefinitions="10,AUTO,AUTO,AUTO,*,10" ColumnDefinitions="10,AUTO,*,AUTO,10">
<Label Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center"
Content="SPT will be installed into:"
FontSize="16"
FontWeight="SemiBold"
/>
<TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3"
Foreground="DodgerBlue" FontWeight="SemiBold" FontSize="16"
Text="{Binding InstallPath}" TextWrapping="Wrap"
Margin="5"
/>
<Button Grid.Row="1" Grid.RowSpan="3" Grid.Column="3"
Content="Start Install" Padding="20 10"
VerticalAlignment="Top"
FontSize="15" FontWeight="SemiBold"
Classes="yellow"
IsEnabled="{Binding AllowInstall}"
Command="{Binding StartInstallCommand}"
/>
<ScrollViewer Grid.Row="4" Grid.ColumnSpan="5">
<ItemsControl Items="{Binding PreChecks}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" Margin="10 0"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<cc:DetailedPreCheckItem PreCheckName="{Binding Name}"
IsRunning="{Binding IsRunning}"
IsPending="{Binding IsPending}"
IsRequired="{Binding IsRequired}"
Passed="{Binding Passed}"
PreCheckDetails="{Binding PreCheckDetails}"
ActionButtonCommand="{Binding ActionButtonCommand}"
ActionButtonText="{Binding ActionButtonText}"
ActionButtonIsVisible="{Binding ActionButtonIsVisible}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Border Grid.Row="3" Grid.ColumnSpan="5"
BorderThickness="1" BorderBrush="Black" Height="2" Background="Black"
BoxShadow="0 3 10 2 black"
/>
</Grid>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia.ReactiveUI;
using SPTInstaller.ViewModels;
namespace SPTInstaller.Views;
public partial class DetailedPreChecksView : ReactiveUserControl<DetailedPreChecksViewModel>
{
public DetailedPreChecksView()
{
InitializeComponent();
}
}

View File

@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -6,7 +6,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SPTInstaller.Views.PreChecksView">
<Grid ColumnDefinitions="10,*,AUTO,*,10"
RowDefinitions="10,*,AUTO,AUTO,AUTO,*,10">
RowDefinitions="10,*,AUTO,AUTO,AUTO,AUTO,*,10">
<StackPanel Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="2" HorizontalAlignment="Center">
<Label Content="SPT will be installed into this folder:"
HorizontalAlignment="Center"
@ -44,5 +44,9 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Column="1" Grid.Row="5" Grid.ColumnSpan="3" HorizontalAlignment="Center"
Content="Detailed View"
Command="{Binding ShowDetailedViewCommand}"
/>
</Grid>
</UserControl>