Loading GarrigueGamesLauncher/ViewModels/DiscoverGamePageViewModel.cs +19 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)) Loading @@ -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) Loading @@ -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 = []; Loading GarrigueGamesLauncher/ViewModels/DiscoverPageViewModel.cs +30 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 }); Loading @@ -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) { Loading @@ -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) { Loading @@ -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); Loading @@ -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; Loading GarrigueGamesLauncher/ViewModels/GameCardViewModel.cs +39 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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, Loading Loading @@ -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, Loading @@ -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); Loading @@ -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"); Loading Loading @@ -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 = []; Loading @@ -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 Loading Loading @@ -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(); Loading GarrigueGamesLauncher/ViewModels/LibraryGamePageViewModel.cs +23 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)}"; Loading @@ -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; Loading @@ -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?"; Loading GarrigueGamesLauncher/ViewModels/LibraryPageViewModel.cs +52 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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; Loading @@ -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() { Loading @@ -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) { Loading @@ -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(); Loading @@ -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; Loading @@ -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) { Loading @@ -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 Loading
GarrigueGamesLauncher/ViewModels/DiscoverGamePageViewModel.cs +19 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)) Loading @@ -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) Loading @@ -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 = []; Loading
GarrigueGamesLauncher/ViewModels/DiscoverPageViewModel.cs +30 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 }); Loading @@ -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) { Loading @@ -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) { Loading @@ -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); Loading @@ -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; Loading
GarrigueGamesLauncher/ViewModels/GameCardViewModel.cs +39 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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, Loading Loading @@ -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, Loading @@ -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); Loading @@ -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"); Loading Loading @@ -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 = []; Loading @@ -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 Loading Loading @@ -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(); Loading
GarrigueGamesLauncher/ViewModels/LibraryGamePageViewModel.cs +23 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)}"; Loading @@ -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; Loading @@ -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?"; Loading
GarrigueGamesLauncher/ViewModels/LibraryPageViewModel.cs +52 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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; Loading @@ -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() { Loading @@ -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) { Loading @@ -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(); Loading @@ -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; Loading @@ -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) { Loading @@ -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