Commit 1fdeec2e authored by Martin Tvarožek's avatar Martin Tvarožek
Browse files

docs: add VM docs

parent c8349dd4
Loading
Loading
Loading
Loading
+19 −2
Original line number Diff line number Diff line
@@ -11,6 +11,10 @@ using Pivo.Client;

namespace GarrigueGamesLauncher.ViewModels;

/// <summary>
/// ViewModel for the Discover Game Page.
/// Handles initiating game downloads and displaying game data.
/// </summary>
public partial class DiscoverGamePageViewModel : ViewModelBase
{
    private readonly IGameManagerService _gameManagerService;
@@ -74,6 +78,11 @@ public partial class DiscoverGamePageViewModel : ViewModelBase
        _popupService = popupService;
    }
    
    /// <summary>
    /// Attempts to download the currently selected game.
    /// Checks if the game is already in the library and if it has downloadable files.
    /// </summary>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task DownloadGame()
    {
        if (await _dbService.IsGameInLibraryAsync(ReleaseDetails.Id))
@@ -95,6 +104,9 @@ public partial class DiscoverGamePageViewModel : ViewModelBase
        _ = _popupService.AddAlertAsync(alertMessage);
    }
    
    /// <summary>
    /// Sets properties such as texts and genre tags based on the release details from KAFE.
    /// </summary>
    private void SetReleaseDataProperties()
    {
        if (_releaseDetails.Downloads.Count > 0)
@@ -117,6 +129,11 @@ public partial class DiscoverGamePageViewModel : ViewModelBase
        }
    }
    
    /// <summary>
    /// Fetches gallery images for the game release at the specified resolution.
    /// </summary>
    /// <param name="r">The desired image resolution.</param>
    /// <returns>A list of tuples containing images and their corresponding IDs.</returns>
    private List<(IImage, string)> GetGalleryImages(Resolution r)
    {
        List<(IImage, string)> images = [];
+30 −5
Original line number Diff line number Diff line
@@ -11,6 +11,10 @@ using GarrigueGamesLauncher.Models;

namespace GarrigueGamesLauncher.ViewModels;

/// <summary>
/// ViewModel for the Discover page, allowing users to search and filter games from KAFE.
/// Manages search results and pagination.
/// </summary>
public partial class DiscoverPageViewModel : ViewModelBase {
    private readonly HistoryRouter<ViewModelBase> _router;
    private readonly IDatabaseService _dbService;
@@ -25,7 +29,6 @@ public partial class DiscoverPageViewModel : ViewModelBase {
    [ObservableProperty] private bool _showNoInternetText = false;
    
    public static ObservableCollection<GenreTagCheckboxItem> Genres { get; set; } = [];
    public static ObservableCollection<GenreTagCheckboxItem> OperatingSystems { get; set; } = [];
    public static ObservableCollection<GameCardViewModel> SearchResults { get; set; } = [];

    private HashSet<string> _installedGames;
@@ -45,7 +48,6 @@ public partial class DiscoverPageViewModel : ViewModelBase {
        
        SearchResults.Clear();
        Genres.Clear();
        OperatingSystems.Clear();
        
        foreach (var tag in kafeService.GenreTags) {
            Genres.Add(new GenreTagCheckboxItem { Tag = tag, IsChecked = false });
@@ -54,6 +56,10 @@ public partial class DiscoverPageViewModel : ViewModelBase {
        _installedGames = _dbService.GetAllGamesAsync().Result.Select(x => x.GameId).ToHashSet();
    }
    
    /// <summary>
    /// Updates the current query text from the <see cref="TextBox"/> input.
    /// </summary>
    /// <param name="box">The search input box.</param>
    [RelayCommand]
    private void UpdateQuery(TextBox? box)
    {
@@ -62,6 +68,10 @@ public partial class DiscoverPageViewModel : ViewModelBase {
        HasQuery = Query != "";
    }
    
    /// <summary>
    /// Clears the search box and resets the query state.
    /// </summary>
    /// <param name="box">The search input box.</param>
    [RelayCommand]
    private void ClearSearchBox(TextBox? box)
    {
@@ -70,6 +80,10 @@ public partial class DiscoverPageViewModel : ViewModelBase {
        UpdateQuery(box);
    }
    
    /// <summary>
    /// Performs a search in KAFE using the current query and selected genres.
    /// Updates <see cref="SearchResults"/> and visibility flags for results and errors.
    /// </summary>
    public async Task SearchKafe()
    {
        var selectedGenres = Genres.Where(x => x.IsChecked).Select(x => x.Tag.Id);
@@ -81,12 +95,23 @@ public partial class DiscoverPageViewModel : ViewModelBase {
        if (!result) ShowNoInternetText = true;
    }
    
    /// <summary>
    /// Loads more search results from KAFE using the last used query.
    /// </summary>
    public async Task LoadMore()
    {
        var result = await AddSearchResults(_lastUsedQuery.TextQuery, _lastUsedQuery.Genres, ++_searchIndex);
        if (!result) ShowNoInternetText = true;
    }
    
    /// <summary>
    /// Adds search results for a specific query and genre selection at the given page index.
    /// Updates <see cref="SearchResults"/> and visibility flags.
    /// </summary>
    /// <param name="query">Search text query.</param>
    /// <param name="selectedGenres">List of selected genre IDs.</param>
    /// <param name="idx">Page index for pagination.</param>
    /// <returns>True if results were successfully retrieved; false if an error occurred (e.g. no internet connection).</returns>
    private async Task<bool> AddSearchResults(string query, IEnumerable<string> selectedGenres, int idx)
    {
        ShowLoadMoreButton = ShowNoResultsText = ShowNoInternetText = false;
+39 −4
Original line number Diff line number Diff line
@@ -12,6 +12,11 @@ using GarrigueGamesLauncher.Models;
using Pivo.Client;

namespace GarrigueGamesLauncher.ViewModels;

/// <summary>
/// ViewModel representing a single game card, used on Library and Discover pages.
/// Handles navigation to detailed game pages and displaying and fetching game metadata.
/// </summary>
public partial class GameCardViewModel : ViewModelBase
{
    private readonly HistoryRouter<ViewModelBase> _router;
@@ -36,6 +41,10 @@ public partial class GameCardViewModel : ViewModelBase
    
    public bool IsValid = true;
    
    /// <summary>
    /// Constructs a game card for a locally installed game using a stub.
    /// Used on the Library Page.
    /// </summary>
    public GameCardViewModel(
        GameStubDto stub,
        HistoryRouter<ViewModelBase> router,
@@ -65,6 +74,10 @@ public partial class GameCardViewModel : ViewModelBase
        }
    }
    
    /// <summary>
    /// Constructs a game card for a game fetched from KAFE.
    /// Used on the Discover Page.
    /// </summary>
    public GameCardViewModel(
        GameItem item, 
        HistoryRouter<ViewModelBase> router, 
@@ -89,6 +102,11 @@ public partial class GameCardViewModel : ViewModelBase
        _ = InitializeGameDetails(item);
    }
    
    /// <summary>
    /// Asynchronously fetches game release information from KAFE.
    /// Populates <see cref="GameDetails"/> and tag names for tooltips.
    /// </summary>
    /// <param name="item">KAFE game item.</param>
    private async Task InitializeGameDetails(GameItem item)
    {
        var detail = await _kafeService!.GetReleaseDetailsAsync(item.LatestReleaseId);
@@ -110,6 +128,11 @@ public partial class GameCardViewModel : ViewModelBase
        TagNames = detail.Tags.Select(x => x.Name.Iv).ToList();
    }
    
    /// <summary>
    /// Loads local game data from storage.
    /// </summary>
    /// <param name="stub">Game stub with directory info.</param>
    /// <returns>A <see cref="Game"/> object if successful; otherwise, <c>null</c>.</returns>
    private Game? FetchDataFromStorage(GameStubDto stub) 
    {
        string jsonPath = Path.Join(stub.DirPath, "data.json");
@@ -142,6 +165,12 @@ public partial class GameCardViewModel : ViewModelBase
        };
    }
    
    /// <summary>
    /// Loads gallery images from local paths.
    /// </summary>
    /// <param name="paths">A collection of relative paths to images.</param>
    /// <param name="dirPath">Base directory path.</param>
    /// <returns>A collection of <see cref="IImage"/> objects.</returns>
    private ICollection<IImage> GetLocalGalleryImages(ICollection<string> paths, string dirPath)
    {
        List<IImage> result = [];
@@ -164,6 +193,9 @@ public partial class GameCardViewModel : ViewModelBase
        return result;
    }
    
    /// <summary>
    /// Navigates to the appropriate detailed game page, either Library or Discover.
    /// </summary>
    public async Task GoToPage()
    {
        // Library Game Page
@@ -201,6 +233,9 @@ public partial class GameCardViewModel : ViewModelBase
        ViewLocator.MainVm.UpdateNavigationState();
    }
    
    /// <summary>
    /// Updates the tag names displayed on the game card from the KafeService cache.
    /// </summary>
    public void UpdateTagNames()
    {
        TagNames = GameDetails.Tags.Select(_kafeService!.GetTagName).OfType<string>().ToList();
+23 −3
Original line number Diff line number Diff line
@@ -8,6 +8,11 @@ using GarrigueGamesLauncher.Models;

namespace GarrigueGamesLauncher.ViewModels;


/// <summary>
/// ViewModel for displaying detailed information about a game in the user's library.
/// Handles launching, uninstalling, and displaying game details and statistics.
/// </summary>
public partial class LibraryGamePageViewModel : ViewModelBase
{
    private readonly HistoryRouter<ViewModelBase> _router;
@@ -53,6 +58,10 @@ public partial class LibraryGamePageViewModel : ViewModelBase
        PresentationMode = settings.Settings.PresentationMode;
    }
    
    /// <summary>
    /// Updates the displayed game metadata.
    /// </summary>
    /// <param name="game">The <see cref="Game"/> object to source data from.</param>
    private void UpdateDisplayedGameData(Game game)
    {
        if (game.Authors.Count > 0) AuthorsString = $"by {string.Join(", ", game.Authors)}";
@@ -61,6 +70,10 @@ public partial class LibraryGamePageViewModel : ViewModelBase
        TagNames = game.Tags.Select(_kafeService!.GetTagName).OfType<string>().ToList();
    }
    
    /// <summary>
    /// Retrieves and sets the current view and play statistics for a game.
    /// </summary>
    /// <param name="gameId">The ID of the game.</param>
    private void SetGameStats(string gameId)
    {
        var stats = _gameManagerService.GetGameStatsAsync(gameId).Result;
@@ -68,12 +81,19 @@ public partial class LibraryGamePageViewModel : ViewModelBase
        PlayCountStat = stats.PlayCount;
    }
    
    /// <summary>
    /// Asynchronously launches the game and updates its play count.
    /// </summary>
    public async Task LaunchGame()
    {
        await _gameManagerService.LaunchGameAsync(GameDetails.FullExePath, GameDetails.GameId);
        SetGameStats(GameDetails.GameId);
    } 
    
    /// <summary>
    /// Asynchronously uninstalls the game after confirmation and ensuring it is not running.
    /// Provides alerts about success or failure.
    /// </summary>
    public async Task UninstallGame()
    {
        var msg = $"{GameDetails.Name} and all of its data will be removed. Are you sure?";
+52 −3
Original line number Diff line number Diff line
@@ -12,6 +12,12 @@ using GarrigueGamesLauncher.Models;

namespace GarrigueGamesLauncher.ViewModels;

/// <summary>
/// ViewModel for displaying the user's Library page, which is also the first
/// page of the launcher.
/// Fetches and displays both all games and last played games.
/// Supports filtering by genre tags and search queries.
/// </summary>
public partial class LibraryPageViewModel : ViewModelBase, IDisposable
{
    private readonly HistoryRouter<ViewModelBase> _router;
@@ -56,6 +62,10 @@ public partial class LibraryPageViewModel : ViewModelBase, IDisposable
        DisplayGames = DisplayedAllGames.Count > 0;
    }
    
    /// <summary>
    /// Updates the available genre tags for filtering based on the currently displayed games.
    /// Additionally, updates tags in individual game cards.
    /// </summary>
    public void UpdateGenreTags()
    {
        Genres.Clear();
@@ -70,8 +80,19 @@ public partial class LibraryPageViewModel : ViewModelBase, IDisposable
        foreach (var game in LastPlayedGames) game.UpdateTagNames();
    }
    
    /// <summary>
    /// Event handler for game import or launch events.
    /// Triggers a refresh of the library data by calling <see cref="FetchData"/> asynchronously.
    /// </summary>
    /// <param name="sender">The event sender (unused).</param>
    /// <param name="e">Event arguments (unused).</param>
    private void FetchDataHandler(object? sender, EventArgs e) => _ = FetchData();
    
    /// <summary>
    /// Fetches all games and last played games from the database, while filtering
    /// out invalid titles.
    /// Populates <see cref="DisplayedAllGames"/> and <see cref="LastPlayedGames"/>.
    /// </summary>
    public async Task FetchData()
    {
        if (_isFetching) return;
@@ -98,6 +119,9 @@ public partial class LibraryPageViewModel : ViewModelBase, IDisposable
        _isFetching = false;
    }
    
    /// <summary>
    /// Filters displayed games based on selected genre tags.
    /// </summary>
    [RelayCommand]
    private async Task FilterGamesByTags()
    {
@@ -109,6 +133,10 @@ public partial class LibraryPageViewModel : ViewModelBase, IDisposable
        FilterGames();
    }
    
    /// <summary>
    /// Filters displayed games based on search query text.
    /// </summary>
    /// <param name="box">TextBox containing the query.</param>
    [RelayCommand]
    private void FilterGamesByQuery(TextBox? box)
    {
@@ -119,6 +147,10 @@ public partial class LibraryPageViewModel : ViewModelBase, IDisposable
        FilterGames();
    }
    
    /// <summary>
    /// General filtering function, which is called from both
    /// <see cref="FilterGamesByQuery"/> and <see cref="FilterGamesByTags"/>.
    /// </summary>
    private void FilterGames()
    {
        DisplayedAllGames.Clear();
@@ -133,6 +165,9 @@ public partial class LibraryPageViewModel : ViewModelBase, IDisposable
        SetProperties();
    }
    
    /// <summary>
    /// Updates ViewModel properties that depend on the current state of displayed and last played games.
    /// </summary>
    private void SetProperties()
    {
        DisplayLastPlayedGames = LastPlayedGames.Count > 0 && Query == "" && _selectedGenreIds.Count == 0;
@@ -140,11 +175,22 @@ public partial class LibraryPageViewModel : ViewModelBase, IDisposable
        _availableGenreIds = DisplayedAllGames.Select(x => x.GameDetails.Tags).SelectMany(x => x).ToHashSet();
    }
    
    /// <summary>
    /// Determines whether a given <see cref="GameCardViewModel"/> is valid for display.
    /// A card is considered valid if it is marked as valid, has a non-empty game ID, and is not in the list of invalid game IDs.
    /// </summary>
    /// <param name="card">The game card to validate.</param>
    /// <returns>True if the card is valid; otherwise, false.</returns>
    private bool IsCardValid(GameCardViewModel card) => 
        card.IsValid && 
        card.GameDetails.GameId != "" &&
        !_gameManagerService.InvalidGameIds.Contains(card.GameDetails.GameId);
    
    
    /// <summary>
    /// Clears the search box and re-applies filtering.
    /// </summary>
    /// <param name="box">TextBox to clear.</param>
    [RelayCommand]
    private void ClearSearchBox(TextBox? box)
    {
@@ -153,6 +199,9 @@ public partial class LibraryPageViewModel : ViewModelBase, IDisposable
        FilterGamesByQuery(box);
    }
    
    /// <summary>
    /// Unsubscribes from events when the ViewModel is disposed.
    /// </summary>
    public void Dispose()
    {
        _gameImporterService.GameImported -= FetchDataHandler;
Loading