Skip to content

Commit

Permalink
feat Migrate GameInfo Page to MVVM
Browse files Browse the repository at this point in the history
  • Loading branch information
Foxtrot47 committed Jan 18, 2025
1 parent b470c11 commit a15967c
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 257 deletions.
1 change: 1 addition & 0 deletions Crimson/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public App()
services.AddTransient<SettingsViewModel>();
services.AddTransient<DownloadsViewModel>();
services.AddTransient<LibraryViewModel>();
services.AddTransient<GameInfoViewModel>();
}).
Build();
}
Expand Down
9 changes: 9 additions & 0 deletions Crimson/Interfaces/INavigationAware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;

namespace Crimson.Interfaces;
public interface INavigationAware
{
Task OnNavigatedTo(object parameter);
void OnNavigatedFrom();
}

303 changes: 303 additions & 0 deletions Crimson/ViewModels/GameInfoViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
using Crimson.Core;
using Crimson.Interfaces;
using Crimson.Models;
using Crimson.Utils;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Media.Imaging;
using Serilog;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Crimson.ViewModels;

/// <summary>
/// Page for Showing Details of individual game and allowing play
/// download and other options
/// </summary>
public partial class GameInfoViewModel : ObservableObject, INavigationAware
{
private readonly Windows.System.DispatcherQueue _dispatcherQueue;
private readonly InstallManager _installer;
private readonly LibraryManager _libraryManager;
private readonly Storage _storage;
private readonly ILogger _log;

[ObservableProperty]
private Game _game;

[ObservableProperty]
private bool _isInstalled;

[ObservableProperty]
private string _primaryActionButtonText;

[ObservableProperty]
private string _primaryActionButtonGlyph;

[ObservableProperty]
private bool _isPrimaryActionEnabled = true;

[ObservableProperty]
private bool _isProgressRingVisible;

[ObservableProperty]
private bool _isProgressRingIndeterminate;

[ObservableProperty]
private double _progressValue;

[ObservableProperty]
private BitmapImage _titleImage;

[ObservableProperty]
private string _confirmInstallAppName;

[ObservableProperty]
private BitmapImage _confirmInstallAppImage;

[ObservableProperty]
private string _installLocation;

[ObservableProperty]
private int _confirmInstallDialogWidth;

[ObservableProperty]
private string _confirmInstallDownloadSizeText;

[ObservableProperty]
private string _confirmInstallSizeText;

// Event for showing install dialog
public event Func<Task> ShowInstallDialogRequested;
public event Action CloseInstallDialogRequested;

// Event for requesting folder picker from view
public event Func<Task<string>> FolderPickerRequested;

public GameInfoViewModel(ILogger logger,
InstallManager installer,
LibraryManager libraryManager,
Storage storage)
{
_dispatcherQueue = Windows.System.DispatcherQueue.GetForCurrentThread();
_log = logger;
_installer = installer;
_libraryManager = libraryManager;
_storage = storage;

_libraryManager.GameStatusUpdated += CheckGameStatus;
_installer.InstallationStatusChanged += HandleInstallationStatusChanged;
_installer.InstallProgressUpdate += HandleInstallationStatusChanged;
}

public async Task OnNavigatedTo(object parameter)

Check warning on line 95 in Crimson/ViewModels/GameInfoViewModel.cs

View workflow job for this annotation

GitHub Actions / build (Release, x64)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
if (parameter is not string appName) return;

Game = _libraryManager.GetGameInfo((string)appName);
var gameImage = Game.Metadata.KeyImages.FirstOrDefault(image => image.Type == "DieselGameBox");
TitleImage = gameImage != null ? new BitmapImage(new Uri(gameImage.Url)) : null;

CheckGameStatus(Game);

// Unregister event handlers on start
UnregisterEventHandlers();

_libraryManager.GameStatusUpdated += CheckGameStatus;
_installer.InstallationStatusChanged += HandleInstallationStatusChanged;
_installer.InstallProgressUpdate += HandleInstallationStatusChanged;

}

private void UnregisterEventHandlers()
{
_libraryManager.GameStatusUpdated -= CheckGameStatus;
_installer.InstallationStatusChanged -= HandleInstallationStatusChanged;
_installer.InstallProgressUpdate -= HandleInstallationStatusChanged;
}

[RelayCommand]
private async Task PrimaryActionAsync()
{
try
{
_log.Information("GameInfoPage: Primary Action Button Clicked for {Game}", Game.AppTitle);
if (Game == null) return;
if (Game.InstallStatus == InstallState.Installed)
{
_log.Information("GameInfoPage: Starting Game {Game}", Game.AppTitle);
await _libraryManager.LaunchApp(Game.AppName);
return;
}
IsPrimaryActionEnabled = false;
ConfirmInstallAppName = Game.AppTitle;
ConfirmInstallAppImage = Game.Metadata.KeyImages.FirstOrDefault(i => i.Type == "DieselGameBox") != null ? new BitmapImage(new Uri(Game.Metadata.KeyImages.FirstOrDefault(i => i.Type == "DieselGameBoxTall").Url)) : null;
InstallLocation = $"C:\\Games\\{Game.AppTitle}";
ConfirmInstallDialogWidth = 4000;

var (totalDownloadSizeMb, totalWriteSizeMb) = await _installer.GetGameDownloadInstallSizes(Game.AppName);
ConfirmInstallDownloadSizeText = Util.ConvertMiBToGiBOrMiB(totalDownloadSizeMb);
ConfirmInstallSizeText = Util.ConvertMiBToGiBOrMiB(totalWriteSizeMb);

if (ShowInstallDialogRequested != null)
{
await ShowInstallDialogRequested.Invoke();
}
}
catch (Exception ex)
{
_log.Error(ex.ToString());
IsProgressRingVisible = false;
IsPrimaryActionEnabled = true;
}
}

[RelayCommand]
private async Task ChangeInstallLocation()
{
if (FolderPickerRequested != null)
{
var selectedPath = await FolderPickerRequested.Invoke();
if (!string.IsNullOrEmpty(selectedPath))
{
InstallLocation = $"{selectedPath}\\{Game.AppTitle}";
}
}
}

/// <summary>
/// Handing Installation State Change.
/// <br/>
/// This function is never run on UI Thread.
/// <br/>
/// So always make sure to use Dispatcher Queue to update UI thread
/// </summary>
/// <param name="installItem"></param>
private void HandleInstallationStatusChanged(InstallItem installItem)
{
try
{
if (installItem == null || installItem.AppName != Game.AppName) return;
_dispatcherQueue.TryEnqueue(() =>
{
_log.Information("GameInfoPage: Installation Status Changed for {Game}", installItem.AppName);
switch (installItem.Status)
{
case ActionStatus.Processing:
IsProgressRingIndeterminate = false;
ProgressValue = Convert.ToDouble(installItem.ProgressPercentage);
IsProgressRingVisible = true;
IsPrimaryActionEnabled = false;
PrimaryActionButtonText = $"{installItem.ProgressPercentage}%";
break;
case ActionStatus.Pending:
PrimaryActionButtonText = "Pending...";
IsProgressRingVisible = true;
IsProgressRingIndeterminate = true;
break;

case ActionStatus.Cancelling:
PrimaryActionButtonText = "Cancelling...";
IsProgressRingVisible = true;
IsProgressRingIndeterminate = true;
break;
}
});
}
catch (Exception ex)
{
_log.Error(ex.ToString());
}
}

private void CheckGameStatus(Game updatedGame)
{
if (updatedGame == null || updatedGame.AppName != Game.AppName) return;
_log.Information("GameInfoPage: Game Status Changed for {Game}", updatedGame.AppTitle);
Game = updatedGame;

_dispatcherQueue.TryEnqueue(() =>
{
PrimaryActionButtonGlyph = "";
IsProgressRingVisible = false;
IsPrimaryActionEnabled = true;

switch (Game.InstallStatus)
{
case InstallState.NotInstalled:
PrimaryActionButtonText = "Install";
PrimaryActionButtonGlyph = "\uE896";
IsInstalled = false;
break;
case InstallState.Installed:
PrimaryActionButtonText = "Play";
PrimaryActionButtonGlyph = "\uE768";
IsInstalled = true;
break;
case InstallState.NeedUpdate:
PrimaryActionButtonText = "Update";
PrimaryActionButtonGlyph = "\uE777";
IsInstalled = true;
break;
case InstallState.Broken:
PrimaryActionButtonText = "Repair";
PrimaryActionButtonGlyph = "\uE90F";
IsInstalled = true;
break;
}
});
}

public void OnNavigatedFrom()
{
UnregisterEventHandlers();

}

[RelayCommand]
private void CloseInstallDialog()
{
CloseInstallDialogRequested?.Invoke();
}


[RelayCommand]
private void ConfirmInstall()
{
CloseInstallDialogRequested?.Invoke();
IsPrimaryActionEnabled = false;
PrimaryActionButtonText = "Pending...";
IsProgressRingVisible = true;
IsProgressRingIndeterminate = true;
PrimaryActionButtonGlyph = "";
_installer.AddToQueue(new InstallItem(Game.AppName, ActionType.Install, InstallLocation));
_log.Information("GameInfoViewModel: Added {Game} to Installation Queue", Game.AppTitle);
}

[RelayCommand]
private void Uninstall()
{
if (Game == null || Game.InstallStatus == InstallState.NotInstalled) return;

_storage.InstalledGamesDictionary.TryGetValue(Game.AppName, out var installedGame);

if (installedGame == null)
{
_log.Information("ProcessNext: Attempting to uninstall not installed game");
return;
}

_installer.AddToQueue(new InstallItem(Game.AppName, ActionType.Uninstall, installedGame.InstallPath));
_log.Information("GameInfoPage: Added {Game} to Installation Queue", Game.AppTitle);
}

public void Cleanup()
{
_libraryManager.GameStatusUpdated -= CheckGameStatus;
_installer.InstallationStatusChanged -= HandleInstallationStatusChanged;
_installer.InstallProgressUpdate -= HandleInstallationStatusChanged;
}
}

Loading

0 comments on commit a15967c

Please sign in to comment.