fix(launcher): close LA4 review findings
This commit is contained in:
parent
d0a9c65d85
commit
10a712d66b
19 changed files with 1631 additions and 134 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using AcDream.Launcher.Core.Orchestration;
|
||||
using AcDream.Launcher.Core.Profiles;
|
||||
|
||||
|
|
@ -33,22 +34,28 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
"Choose and validate the retail DAT directory, build acdream.pak, "
|
||||
+ "and record the installed client. The installer transaction and "
|
||||
+ "progress body land in Campaign LA slice LA9.",
|
||||
"Installer shell ready — implementation arrives in LA9.");
|
||||
"Installer shell ready — implementation arrives in LA9.",
|
||||
() => CanInteract);
|
||||
UpdatePromptShell = new LauncherShellViewModel(
|
||||
"Client update",
|
||||
"Review a signed release manifest, verify the downloaded archive, "
|
||||
+ "and atomically switch the installed client version. The updater "
|
||||
+ "transaction lands in Campaign LA slice LA10.",
|
||||
"Updater shell ready — implementation arrives in LA10.");
|
||||
"Updater shell ready — implementation arrives in LA10.",
|
||||
() => CanInteract);
|
||||
|
||||
AddServerCommand = new RelayCommand(OpenAddServerDialog, () => !IsBusy);
|
||||
EditorDialog.PropertyChanged += OnModalPropertyChanged;
|
||||
FirstRunWizardShell.PropertyChanged += OnModalPropertyChanged;
|
||||
UpdatePromptShell.PropertyChanged += OnModalPropertyChanged;
|
||||
|
||||
AddServerCommand = new RelayCommand(OpenAddServerDialog, () => CanInteract);
|
||||
AddAccountCommand = new RelayCommand(OpenAddAccountDialog, CanAddAccount);
|
||||
AddCharacterCommand = new RelayCommand(OpenAddCharacterDialog, CanAddCharacter);
|
||||
EditSelectedCommand = new RelayCommand(OpenEditSelectedDialog, CanEditSelected);
|
||||
RemoveSelectedCommand = new RelayCommand(OpenRemoveSelectedDialog, CanEditSelected);
|
||||
SaveCharacterSettingsCommand = new RelayCommand(
|
||||
SaveCharacterSettings,
|
||||
() => IsCharacterSelected && !IsBusy);
|
||||
() => IsCharacterSelected && CanInteract);
|
||||
RefreshCharactersCommand = new AsyncRelayCommand(
|
||||
RefreshCharactersAsync,
|
||||
() => CanProbe);
|
||||
|
|
@ -58,15 +65,18 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
LaunchGuiSelectCommand = new AsyncRelayCommand(
|
||||
() => LaunchSelectedAsync(LaunchMode.GuiSelect),
|
||||
() => CanLaunchGuiSelect);
|
||||
LaunchAccountGuiSelectCommand = new AsyncRelayCommand(
|
||||
LaunchSelectedAccountGuiSelectAsync,
|
||||
() => CanLaunchAccountGuiSelect);
|
||||
LaunchHeadlessCommand = new AsyncRelayCommand(
|
||||
() => LaunchSelectedAsync(LaunchMode.Headless),
|
||||
() => CanLaunchHeadless);
|
||||
CancelOperationCommand = new RelayCommand(
|
||||
CancelOperation,
|
||||
() => IsBusy && _operationCancellation is not null);
|
||||
() => !IsModalOpen && IsBusy && _operationCancellation is not null);
|
||||
ClearFinishedSessionsCommand = new RelayCommand(
|
||||
_orchestrator.ClearFinishedSessions,
|
||||
() => Sessions.Any(session => !session.IsActive) && !IsBusy);
|
||||
() => Sessions.Any(session => !session.IsActive) && CanInteract);
|
||||
}
|
||||
|
||||
public ObservableCollection<LauncherTreeNodeViewModel> Servers { get; } = [];
|
||||
|
|
@ -99,6 +109,9 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
OnPropertyChanged(nameof(CanLaunchGui));
|
||||
OnPropertyChanged(nameof(CanLaunchGuiSelect));
|
||||
OnPropertyChanged(nameof(CanLaunchHeadless));
|
||||
OnPropertyChanged(nameof(CanLaunchAccountGuiSelect));
|
||||
OnPropertyChanged(nameof(ShowGuiLaunchDisabledReason));
|
||||
OnPropertyChanged(nameof(ShowHeadlessLaunchDisabledReason));
|
||||
NotifyCommandStates();
|
||||
}
|
||||
}
|
||||
|
|
@ -118,6 +131,13 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
|
||||
public bool HasError => !string.IsNullOrWhiteSpace(LastError);
|
||||
|
||||
public bool IsModalOpen =>
|
||||
EditorDialog.IsOpen
|
||||
|| FirstRunWizardShell.IsOpen
|
||||
|| UpdatePromptShell.IsOpen;
|
||||
|
||||
private bool CanInteract => !IsBusy && !IsModalOpen;
|
||||
|
||||
public string OperationStatus
|
||||
{
|
||||
get => _operationStatus;
|
||||
|
|
@ -172,7 +192,7 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
_snapshot?.Platform.GraphicalLaunchDisabledReason ?? string.Empty;
|
||||
|
||||
public bool CanProbe =>
|
||||
!IsBusy
|
||||
CanInteract
|
||||
&& TryGetSelectedAccount(out string server, out string account)
|
||||
&& _orchestrator.GetProbeCapability(server, account).IsAvailable;
|
||||
|
||||
|
|
@ -196,14 +216,49 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
|
||||
public bool CanLaunchHeadless => CanLaunch(LaunchMode.Headless);
|
||||
|
||||
public bool CanLaunchAccountGuiSelect =>
|
||||
CanInteract
|
||||
&& IsAccountSelected
|
||||
&& TryGetSelectedAccount(out string server, out string account)
|
||||
&& _orchestrator.GetAccountLaunchCapability(
|
||||
server,
|
||||
account,
|
||||
LaunchMode.GuiSelect).IsAvailable;
|
||||
|
||||
public string AccountGuiSelectDisabledReason
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!TryGetSelectedAccount(out string server, out string account))
|
||||
{
|
||||
return "Select an account.";
|
||||
}
|
||||
|
||||
return _orchestrator.GetAccountLaunchCapability(
|
||||
server,
|
||||
account,
|
||||
LaunchMode.GuiSelect).Reason
|
||||
?? "Character-select launch is available.";
|
||||
}
|
||||
}
|
||||
|
||||
public string GuiLaunchDisabledReason =>
|
||||
_orchestrator.GetLaunchCapability(LaunchMode.Gui).Reason
|
||||
GetSelectedAccountLaunchCapability(LaunchMode.Gui).Reason
|
||||
?? "Graphical launch is available.";
|
||||
|
||||
public string HeadlessLaunchDisabledReason =>
|
||||
_orchestrator.GetLaunchCapability(LaunchMode.Headless).Reason
|
||||
GetSelectedAccountLaunchCapability(LaunchMode.Headless).Reason
|
||||
?? "Headless launch is available.";
|
||||
|
||||
public bool ShowGuiLaunchDisabledReason =>
|
||||
IsCharacterSelected
|
||||
&& (!GetSelectedAccountLaunchCapability(LaunchMode.Gui).IsAvailable
|
||||
|| !GetSelectedAccountLaunchCapability(LaunchMode.GuiSelect).IsAvailable);
|
||||
|
||||
public bool ShowHeadlessLaunchDisabledReason =>
|
||||
IsCharacterSelected
|
||||
&& !GetSelectedAccountLaunchCapability(LaunchMode.Headless).IsAvailable;
|
||||
|
||||
public RelayCommand AddServerCommand { get; }
|
||||
|
||||
public RelayCommand AddAccountCommand { get; }
|
||||
|
|
@ -222,6 +277,8 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
|
||||
public AsyncRelayCommand LaunchGuiSelectCommand { get; }
|
||||
|
||||
public AsyncRelayCommand LaunchAccountGuiSelectCommand { get; }
|
||||
|
||||
public AsyncRelayCommand LaunchHeadlessCommand { get; }
|
||||
|
||||
public RelayCommand CancelOperationCommand { get; }
|
||||
|
|
@ -272,6 +329,9 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
_operationCancellation?.Dispose();
|
||||
_operationCancellation = null;
|
||||
_orchestrator.StateChanged -= OnOrchestratorStateChanged;
|
||||
EditorDialog.PropertyChanged -= OnModalPropertyChanged;
|
||||
FirstRunWizardShell.PropertyChanged -= OnModalPropertyChanged;
|
||||
UpdatePromptShell.PropertyChanged -= OnModalPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnOrchestratorStateChanged(object? sender, EventArgs e) =>
|
||||
|
|
@ -283,6 +343,41 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
}
|
||||
});
|
||||
|
||||
private void OnModalPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(ProfileEditorDialogViewModel.IsOpen)
|
||||
&& e.PropertyName != nameof(LauncherShellViewModel.IsOpen))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(IsModalOpen));
|
||||
OnPropertyChanged(nameof(CanProbe));
|
||||
OnPropertyChanged(nameof(CanLaunchGui));
|
||||
OnPropertyChanged(nameof(CanLaunchGuiSelect));
|
||||
OnPropertyChanged(nameof(CanLaunchHeadless));
|
||||
OnPropertyChanged(nameof(CanLaunchAccountGuiSelect));
|
||||
OnPropertyChanged(nameof(ShowGuiLaunchDisabledReason));
|
||||
OnPropertyChanged(nameof(ShowHeadlessLaunchDisabledReason));
|
||||
NotifyCommandStates();
|
||||
}
|
||||
|
||||
public void CloseActiveModal()
|
||||
{
|
||||
if (EditorDialog.IsOpen)
|
||||
{
|
||||
EditorDialog.Close();
|
||||
}
|
||||
else if (FirstRunWizardShell.IsOpen)
|
||||
{
|
||||
FirstRunWizardShell.IsOpen = false;
|
||||
}
|
||||
else if (UpdatePromptShell.IsOpen)
|
||||
{
|
||||
UpdatePromptShell.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshFromCore(SelectionKey? preferredSelection = null)
|
||||
{
|
||||
SelectionKey? previousSelection = preferredSelection ?? SelectionKey.From(SelectedNode);
|
||||
|
|
@ -298,7 +393,10 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
Sessions.Clear();
|
||||
foreach (LauncherSessionSnapshot session in snapshot.Sessions)
|
||||
{
|
||||
Sessions.Add(new LauncherSessionRowViewModel(session, StopSessionAsync));
|
||||
Sessions.Add(new LauncherSessionRowViewModel(
|
||||
session,
|
||||
StopSessionAsync,
|
||||
() => CanInteract));
|
||||
}
|
||||
|
||||
LauncherTreeNodeViewModel? restored = previousSelection is null
|
||||
|
|
@ -317,8 +415,12 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
OnPropertyChanged(nameof(CanLaunchGui));
|
||||
OnPropertyChanged(nameof(CanLaunchGuiSelect));
|
||||
OnPropertyChanged(nameof(CanLaunchHeadless));
|
||||
OnPropertyChanged(nameof(CanLaunchAccountGuiSelect));
|
||||
OnPropertyChanged(nameof(AccountGuiSelectDisabledReason));
|
||||
OnPropertyChanged(nameof(GuiLaunchDisabledReason));
|
||||
OnPropertyChanged(nameof(HeadlessLaunchDisabledReason));
|
||||
OnPropertyChanged(nameof(ShowGuiLaunchDisabledReason));
|
||||
OnPropertyChanged(nameof(ShowHeadlessLaunchDisabledReason));
|
||||
NotifyCommandStates();
|
||||
}
|
||||
|
||||
|
|
@ -353,6 +455,12 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
OnPropertyChanged(nameof(CanLaunchGui));
|
||||
OnPropertyChanged(nameof(CanLaunchGuiSelect));
|
||||
OnPropertyChanged(nameof(CanLaunchHeadless));
|
||||
OnPropertyChanged(nameof(CanLaunchAccountGuiSelect));
|
||||
OnPropertyChanged(nameof(AccountGuiSelectDisabledReason));
|
||||
OnPropertyChanged(nameof(GuiLaunchDisabledReason));
|
||||
OnPropertyChanged(nameof(HeadlessLaunchDisabledReason));
|
||||
OnPropertyChanged(nameof(ShowGuiLaunchDisabledReason));
|
||||
OnPropertyChanged(nameof(ShowHeadlessLaunchDisabledReason));
|
||||
NotifyCommandStates();
|
||||
}
|
||||
|
||||
|
|
@ -387,7 +495,7 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
null));
|
||||
});
|
||||
|
||||
private bool CanAddAccount() => !IsBusy && SelectedNode is not null;
|
||||
private bool CanAddAccount() => CanInteract && SelectedNode is not null;
|
||||
|
||||
private void OpenAddAccountDialog()
|
||||
{
|
||||
|
|
@ -411,7 +519,7 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
}
|
||||
|
||||
private bool CanAddCharacter() =>
|
||||
!IsBusy && TryGetSelectedAccount(out _, out _);
|
||||
CanInteract && TryGetSelectedAccount(out _, out _);
|
||||
|
||||
private void OpenAddCharacterDialog()
|
||||
{
|
||||
|
|
@ -441,7 +549,7 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
+ "let you configure a known character while the server is unavailable.");
|
||||
}
|
||||
|
||||
private bool CanEditSelected() => !IsBusy && SelectedNode is not null;
|
||||
private bool CanEditSelected() => CanInteract && SelectedNode is not null;
|
||||
|
||||
private void OpenEditSelectedDialog()
|
||||
{
|
||||
|
|
@ -642,6 +750,24 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
$"{mode} session started for {character.Name}.");
|
||||
}
|
||||
|
||||
private Task LaunchSelectedAccountGuiSelectAsync()
|
||||
{
|
||||
if (!TryGetSelectedAccount(out string serverName, out string accountName))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return RunOperationAsync(
|
||||
token => _orchestrator.LaunchAsync(
|
||||
serverName,
|
||||
accountName,
|
||||
null,
|
||||
LaunchMode.GuiSelect,
|
||||
token),
|
||||
$"Opening character select for {accountName}…",
|
||||
$"Character-select session started for {accountName}.");
|
||||
}
|
||||
|
||||
private async Task RunOperationAsync(
|
||||
Func<CancellationToken, Task<LauncherSessionSnapshot>> operation,
|
||||
string activeStatus,
|
||||
|
|
@ -728,9 +854,15 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
private void CancelOperation() => _operationCancellation?.Cancel();
|
||||
|
||||
private bool CanLaunch(LaunchMode mode) =>
|
||||
!IsBusy
|
||||
CanInteract
|
||||
&& IsCharacterSelected
|
||||
&& _orchestrator.GetLaunchCapability(mode).IsAvailable;
|
||||
&& TryGetSelectedAccount(out string server, out string account)
|
||||
&& _orchestrator.GetAccountLaunchCapability(server, account, mode).IsAvailable;
|
||||
|
||||
private LauncherCapability GetSelectedAccountLaunchCapability(LaunchMode mode) =>
|
||||
TryGetSelectedAccount(out string server, out string account)
|
||||
? _orchestrator.GetAccountLaunchCapability(server, account, mode)
|
||||
: _orchestrator.GetLaunchCapability(mode);
|
||||
|
||||
private bool TryGetSelectedAccount(out string serverName, out string accountName)
|
||||
{
|
||||
|
|
@ -843,9 +975,16 @@ public sealed class LauncherWindowViewModel : ObservableObject, IDisposable
|
|||
RefreshCharactersCommand.NotifyCanExecuteChanged();
|
||||
LaunchGuiCommand.NotifyCanExecuteChanged();
|
||||
LaunchGuiSelectCommand.NotifyCanExecuteChanged();
|
||||
LaunchAccountGuiSelectCommand.NotifyCanExecuteChanged();
|
||||
LaunchHeadlessCommand.NotifyCanExecuteChanged();
|
||||
CancelOperationCommand.NotifyCanExecuteChanged();
|
||||
ClearFinishedSessionsCommand.NotifyCanExecuteChanged();
|
||||
foreach (LauncherSessionRowViewModel session in Sessions)
|
||||
{
|
||||
session.NotifyCommandState();
|
||||
}
|
||||
FirstRunWizardShell.NotifyCommandStates();
|
||||
UpdatePromptShell.NotifyCommandStates();
|
||||
}
|
||||
|
||||
private readonly record struct SelectionKey(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue