mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-05-08 14:25:47 +00:00
feature: UI: LDN Games Viewer
This window can be accessed via "Help" menu in the title bar. This menu's data is synced with the in-app-list LDN game data, and that has been modified to hide unjoinable games (in-progress and/or private (needing a passphrase)). You can still see these games in the list.
This commit is contained in:
@@ -145,8 +145,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OnPropertyChanged();
|
||||
|
||||
OnPropertyChanged(nameof(CurrentEntries));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Svg.Skia;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
|
||||
207
src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs
Normal file
207
src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Gommon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Ryujinx.Ava.Systems.AppLibrary;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public partial class LdnGamesListViewModel : BaseModel, IDisposable
|
||||
{
|
||||
public MainWindowViewModel Mwvm { get; }
|
||||
|
||||
private readonly HttpClient _refreshClient;
|
||||
|
||||
private (int PlayerCount, int Name) _sorting;
|
||||
|
||||
private IEnumerable<LdnGameModel> _visibleEntries;
|
||||
|
||||
private string[] _ownedGameTitleIds = [];
|
||||
|
||||
private Func<LdnGameModel, object> _sortKeySelector = x => x.Title.Name; // Default sort by Title name
|
||||
|
||||
public IEnumerable<LdnGameModel> VisibleEntries => ApplyFilters();
|
||||
|
||||
private IEnumerable<LdnGameModel> ApplyFilters()
|
||||
{
|
||||
if (_visibleEntries is null)
|
||||
{
|
||||
_visibleEntries = Mwvm.LdnModels;
|
||||
SortApply();
|
||||
}
|
||||
|
||||
var filtered = _visibleEntries;
|
||||
|
||||
if (OnlyShowForOwnedGames)
|
||||
filtered = filtered.Where(x => _ownedGameTitleIds.ContainsIgnoreCase(x.Title.Id));
|
||||
|
||||
if (OnlyShowPublicGames)
|
||||
filtered = filtered.Where(x => x.IsPublic);
|
||||
|
||||
if (OnlyShowJoinableGames)
|
||||
filtered = filtered.Where(x => x.IsJoinable);
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public LdnGamesListViewModel()
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Mwvm = RyujinxApp.MainWindow.ViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppCountUpdated(object _, ApplicationCountUpdatedEventArgs __)
|
||||
=> _ownedGameTitleIds = Mwvm.ApplicationLibrary.Applications.Keys.Select(x => x.ToString("X16")).ToArray();
|
||||
|
||||
public LdnGamesListViewModel(MainWindowViewModel mwvm)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Mwvm = mwvm;
|
||||
_visibleEntries = Mwvm.LdnModels;
|
||||
_refreshClient = new HttpClient();
|
||||
AppCountUpdated(null, null);
|
||||
Mwvm.ApplicationLibrary.ApplicationCountUpdated += AppCountUpdated;
|
||||
Mwvm.PropertyChanged += Mwvm_OnPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
_visibleEntries = null;
|
||||
_refreshClient.Dispose();
|
||||
Mwvm.ApplicationLibrary.ApplicationCountUpdated -= AppCountUpdated;
|
||||
Mwvm.PropertyChanged -= Mwvm_OnPropertyChanged;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Mwvm_OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is nameof(MainWindowViewModel.LdnModels))
|
||||
OnPropertyChanged(nameof(VisibleEntries));
|
||||
}
|
||||
|
||||
[ObservableProperty] private bool _isRefreshing;
|
||||
private bool _onlyShowForOwnedGames;
|
||||
private bool _onlyShowPublicGames = true;
|
||||
private bool _onlyShowJoinableGames = true;
|
||||
|
||||
public async Task RefreshAsync()
|
||||
{
|
||||
IsRefreshing = true;
|
||||
|
||||
await Mwvm.ApplicationLibrary.RefreshLdn();
|
||||
|
||||
IsRefreshing = false;
|
||||
|
||||
OnPropertyChanged(nameof(VisibleEntries));
|
||||
}
|
||||
|
||||
public bool OnlyShowForOwnedGames
|
||||
{
|
||||
get => _onlyShowForOwnedGames;
|
||||
set
|
||||
{
|
||||
OnPropertyChanging();
|
||||
OnPropertyChanging(nameof(VisibleEntries));
|
||||
_onlyShowForOwnedGames = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(VisibleEntries));
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnlyShowPublicGames
|
||||
{
|
||||
get => _onlyShowPublicGames;
|
||||
set
|
||||
{
|
||||
OnPropertyChanging();
|
||||
OnPropertyChanging(nameof(VisibleEntries));
|
||||
_onlyShowPublicGames = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(VisibleEntries));
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnlyShowJoinableGames
|
||||
{
|
||||
get => _onlyShowJoinableGames;
|
||||
set
|
||||
{
|
||||
OnPropertyChanging();
|
||||
OnPropertyChanging(nameof(VisibleEntries));
|
||||
_onlyShowJoinableGames = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(VisibleEntries));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void NameSorting(int nameSort = 0)
|
||||
{
|
||||
_sorting.Name = nameSort;
|
||||
SortApply();
|
||||
}
|
||||
|
||||
public void StatusSorting(int statusSort = 0)
|
||||
{
|
||||
_sorting.PlayerCount = statusSort;
|
||||
SortApply();
|
||||
}
|
||||
|
||||
public void Search(string searchTerm)
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
SetEntries(Mwvm.LdnModels);
|
||||
SortApply();
|
||||
return;
|
||||
}
|
||||
|
||||
SetEntries(Mwvm.LdnModels.Where(x =>
|
||||
x.Title.Name.ContainsIgnoreCase(searchTerm)
|
||||
|| x.Title.Id.ContainsIgnoreCase(searchTerm)));
|
||||
|
||||
SortApply();
|
||||
}
|
||||
|
||||
private void SetEntries(IEnumerable<LdnGameModel> entries)
|
||||
{
|
||||
_visibleEntries = entries.ToList();
|
||||
OnPropertyChanged(nameof(VisibleEntries));
|
||||
}
|
||||
|
||||
private void SortApply()
|
||||
{
|
||||
try
|
||||
{
|
||||
_visibleEntries = (_sorting switch
|
||||
{
|
||||
(0, 0) => _visibleEntries.OrderBy(x => _sortKeySelector(x) ?? string.Empty), // A - Z
|
||||
(0, 1) => _visibleEntries.OrderByDescending(x => _sortKeySelector(x) ?? string.Empty), // Z - A
|
||||
(1, 0) => _visibleEntries.OrderBy(x => x.PlayerCount).ThenBy(x => x.Title.Name, StringComparer.OrdinalIgnoreCase), // Player count low - high, then A - Z
|
||||
(1, 1) => _visibleEntries.OrderBy(x => x.PlayerCount).ThenByDescending(x => x.Title.Name, StringComparer.OrdinalIgnoreCase), // Player count high - low, then A - Z
|
||||
(2, 0) => _visibleEntries.OrderByDescending(x => x.PlayerCount).ThenBy(x => x.Title.Name, StringComparer.OrdinalIgnoreCase), // Player count low - high, then Z - A
|
||||
(2, 1) => _visibleEntries.OrderByDescending(x => x.PlayerCount).ThenByDescending(x => x.Title.Name, StringComparer.OrdinalIgnoreCase), // Player count high - low, then Z - A
|
||||
_ => _visibleEntries.OrderBy(x => x.PlayerCount)
|
||||
}).ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(VisibleEntries));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.Helper;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.UI;
|
||||
@@ -57,7 +58,6 @@ using Key = Ryujinx.Input.Key;
|
||||
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
|
||||
using Path = System.IO.Path;
|
||||
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
|
||||
using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
@@ -111,6 +111,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
[ObservableProperty] private bool _isSubMenuOpen;
|
||||
[ObservableProperty] private ApplicationContextMenu _listAppContextMenu;
|
||||
[ObservableProperty] private ApplicationContextMenu _gridAppContextMenu;
|
||||
[ObservableProperty] private bool _isRyuLdnEnabled;
|
||||
[ObservableProperty] private bool _updateAvailable;
|
||||
|
||||
public static AsyncRelayCommand UpdateCommand { get; } = Commands.Create(async () =>
|
||||
@@ -142,7 +143,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private ApplicationData _gridSelectedApplication;
|
||||
|
||||
// Key is Title ID
|
||||
public SafeDictionary<string, LdnGameData.Array> LdnData = [];
|
||||
/// <summary>
|
||||
/// At any given time, this dictionary contains the filtered data from <see cref="_ldnModels"/>.
|
||||
/// Filtered in this case meaning installed games only.
|
||||
/// </summary>
|
||||
public SafeDictionary<string, LdnGameModel.Array> UsableLdnData = [];
|
||||
|
||||
private LdnGameModel[] _ldnModels;
|
||||
|
||||
public LdnGameModel[] LdnModels
|
||||
{
|
||||
get => _ldnModels;
|
||||
set
|
||||
{
|
||||
_ldnModels = value;
|
||||
LocaleManager.Associate(LocaleKeys.LdnGameListTitle, value.Length);
|
||||
LocaleManager.Associate(LocaleKeys.LdnGameListSearchBoxWatermark, value.Length);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public MainWindow Window { get; init; }
|
||||
|
||||
@@ -165,11 +184,28 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
LoadConfigurableHotKeys();
|
||||
|
||||
IsRyuLdnEnabled = ConfigurationState.Instance.Multiplayer.Mode.Value is MultiplayerMode.LdnRyu;
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Event += OnLdnModeChanged;
|
||||
|
||||
Volume = ConfigurationState.Instance.System.AudioVolume;
|
||||
CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
|
||||
}
|
||||
}
|
||||
|
||||
~MainWindowViewModel()
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Event -= OnLdnModeChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLdnModeChanged(object sender, ReactiveEventArgs<MultiplayerMode> e) =>
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
IsRyuLdnEnabled = e.NewValue is MultiplayerMode.LdnRyu;
|
||||
});
|
||||
|
||||
public void Initialize(
|
||||
ContentManager contentManager,
|
||||
IStorageProvider storageProvider,
|
||||
@@ -313,11 +349,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
if (ts.HasValue)
|
||||
{
|
||||
var formattedPlayTime = ValueFormatUtils.FormatTimeSpan(ts.Value);
|
||||
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, formattedPlayTime);
|
||||
LocaleManager.Associate(LocaleKeys.GameListLabelTotalTimePlayed, formattedPlayTime);
|
||||
ShowTotalTimePlayed = formattedPlayTime != string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ShowTotalTimePlayed = ts.HasValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using DynamicData.Binding;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
Reference in New Issue
Block a user