mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-06-26 14:19:04 +00:00
I fixed the remaining Linux file picker issues after testing on Steam Deck. User profile images, mod manager, title manager, and DLC directory were still using the old file picker methods and not the helper methods. I could only apply the helper method to user profiles, but I came up with a workaround for the others. The reason for the draft PR: I'd ideally like to fix the other three at the helper level, so maybe @greem can help with that since since he wrote the initial implementation. Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/24
269 lines
8.7 KiB
C#
269 lines
8.7 KiB
C#
using Avalonia.Collections;
|
|
using Avalonia.Platform.Storage;
|
|
using Avalonia.Threading;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using DynamicData;
|
|
using FluentAvalonia.UI.Controls;
|
|
using Ryujinx.Ava.Common.Locale;
|
|
using Ryujinx.Ava.Common.Models;
|
|
using Ryujinx.Ava.Systems.AppLibrary;
|
|
using Ryujinx.Ava.UI.Helpers;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using static Ryujinx.Ava.Utilities.StorageProviderExtensions;
|
|
|
|
namespace Ryujinx.Ava.UI.ViewModels
|
|
{
|
|
public partial class DownloadableContentManagerViewModel : BaseModel
|
|
{
|
|
private readonly ApplicationLibrary _applicationLibrary;
|
|
[ObservableProperty]
|
|
public partial AvaloniaList<DownloadableContentModel> SelectedDownloadableContents { get; set; } = [];
|
|
|
|
[ObservableProperty]
|
|
public partial AvaloniaList<DownloadableContentModel> Views { get; set; } = [];
|
|
|
|
[ObservableProperty]
|
|
public partial bool ShowBundledContentNotice { get; set; } = false;
|
|
|
|
private string _search;
|
|
private readonly ApplicationData _applicationData;
|
|
private readonly IStorageProvider _storageProvider;
|
|
|
|
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
|
{
|
|
get;
|
|
set
|
|
{
|
|
field = value;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(UpdateCount));
|
|
Sort();
|
|
}
|
|
} = [];
|
|
|
|
public string Search
|
|
{
|
|
get => _search;
|
|
set
|
|
{
|
|
_search = value;
|
|
OnPropertyChanged();
|
|
Sort();
|
|
}
|
|
}
|
|
|
|
public string UpdateCount => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
|
|
|
public DownloadableContentManagerViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData)
|
|
{
|
|
_applicationLibrary = applicationLibrary;
|
|
|
|
_applicationData = applicationData;
|
|
|
|
_storageProvider = RyujinxApp.MainWindow.StorageProvider;
|
|
|
|
LoadDownloadableContents();
|
|
}
|
|
|
|
private void LoadDownloadableContents()
|
|
{
|
|
(DownloadableContentModel Dlc, bool IsEnabled)[] dlcs = _applicationLibrary.FindDlcConfigurationFor(_applicationData.Id);
|
|
|
|
bool hasBundledContent = false;
|
|
foreach ((DownloadableContentModel dlc, bool isEnabled) in dlcs)
|
|
{
|
|
DownloadableContents.Add(dlc);
|
|
hasBundledContent = hasBundledContent || dlc.IsBundled;
|
|
|
|
if (isEnabled)
|
|
{
|
|
SelectedDownloadableContents.Add(dlc);
|
|
}
|
|
|
|
OnPropertyChanged(nameof(UpdateCount));
|
|
}
|
|
|
|
ShowBundledContentNotice = hasBundledContent;
|
|
|
|
Sort();
|
|
}
|
|
|
|
public void Sort()
|
|
{
|
|
DownloadableContents
|
|
// Sort bundled last
|
|
.OrderBy(it => it.IsBundled ? 0 : 1)
|
|
.ThenBy(it => it.TitleId)
|
|
.AsObservableChangeSet()
|
|
.Filter(Filter)
|
|
.Bind(out ReadOnlyObservableCollection<DownloadableContentModel> view).AsObservableList();
|
|
|
|
// NOTE(jpr): this works around a bug where calling _views.Clear also clears SelectedDownloadableContents for
|
|
// some reason. so we save the items here and add them back after
|
|
DownloadableContentModel[] items = SelectedDownloadableContents.ToArray();
|
|
|
|
Views.Clear();
|
|
Views.AddRange(view);
|
|
|
|
foreach (DownloadableContentModel item in items)
|
|
{
|
|
SelectedDownloadableContents.ReplaceOrAdd(item, item);
|
|
}
|
|
|
|
OnPropertyChanged(nameof(Views));
|
|
}
|
|
|
|
private bool Filter<T>(T arg)
|
|
{
|
|
if (arg is DownloadableContentModel content)
|
|
{
|
|
return string.IsNullOrWhiteSpace(_search) || content.FileName.Contains(_search, System.StringComparison.OrdinalIgnoreCase) || content.TitleIdStr.Contains(_search, System.StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public async void Add()
|
|
{
|
|
IReadOnlyList<IStorageFile> result = await CoreDumpable(() => _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
|
{
|
|
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
|
AllowMultiple = true,
|
|
FileTypeFilter = new List<FilePickerFileType>
|
|
{
|
|
new("NSP")
|
|
{
|
|
Patterns = ["*.nsp"],
|
|
AppleUniformTypeIdentifiers = ["com.ryujinx.nsp"],
|
|
MimeTypes = ["application/x-nx-nsp"],
|
|
},
|
|
},
|
|
}));
|
|
|
|
int totalDlcAdded = 0;
|
|
foreach (IStorageFile file in result)
|
|
{
|
|
if (!AddDownloadableContent(file.Path.LocalPath, out int newDlcAdded))
|
|
{
|
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
|
}
|
|
|
|
totalDlcAdded += newDlcAdded;
|
|
}
|
|
|
|
if (totalDlcAdded > 0)
|
|
{
|
|
await ShowNewDlcAddedDialog(totalDlcAdded);
|
|
}
|
|
}
|
|
|
|
private bool AddDownloadableContent(string path, out int numDlcAdded)
|
|
{
|
|
numDlcAdded = 0;
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!_applicationLibrary.TryGetDownloadableContentFromFile(path, out List<DownloadableContentModel> dlcs) || dlcs.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
List<DownloadableContentModel> dlcsForThisGame = dlcs.Where(it => it.TitleIdBase == _applicationData.IdBase).ToList();
|
|
if (dlcsForThisGame.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (DownloadableContentModel dlc in dlcsForThisGame)
|
|
{
|
|
if (!DownloadableContents.Contains(dlc))
|
|
{
|
|
DownloadableContents.Add(dlc);
|
|
SelectedDownloadableContents.ReplaceOrAdd(dlc, dlc);
|
|
|
|
numDlcAdded++;
|
|
}
|
|
}
|
|
|
|
if (numDlcAdded > 0)
|
|
{
|
|
OnPropertyChanged(nameof(UpdateCount));
|
|
Sort();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Remove(DownloadableContentModel model)
|
|
{
|
|
SelectedDownloadableContents.Remove(model);
|
|
|
|
if (!model.IsBundled)
|
|
{
|
|
DownloadableContents.Remove(model);
|
|
OnPropertyChanged(nameof(UpdateCount));
|
|
Sort();
|
|
}
|
|
}
|
|
|
|
public void RemoveAll()
|
|
{
|
|
SelectedDownloadableContents.Clear();
|
|
DownloadableContents.RemoveMany(DownloadableContents.Where(it => !it.IsBundled));
|
|
|
|
OnPropertyChanged(nameof(UpdateCount));
|
|
Sort();
|
|
}
|
|
|
|
public void EnableAll()
|
|
{
|
|
SelectedDownloadableContents.Clear();
|
|
SelectedDownloadableContents.AddRange(DownloadableContents);
|
|
}
|
|
|
|
public void DisableAll()
|
|
{
|
|
SelectedDownloadableContents.Clear();
|
|
}
|
|
|
|
public void Enable(DownloadableContentModel model)
|
|
{
|
|
SelectedDownloadableContents.ReplaceOrAdd(model, model);
|
|
}
|
|
|
|
public void Disable(DownloadableContentModel model)
|
|
{
|
|
SelectedDownloadableContents.Remove(model);
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
List<(DownloadableContentModel it, bool)> dlcs = DownloadableContents.Select(it => (it, SelectedDownloadableContents.Contains(it))).ToList();
|
|
_applicationLibrary.SaveDownloadableContentsForGame(_applicationData, dlcs);
|
|
}
|
|
|
|
private Task ShowNewDlcAddedDialog(int numAdded)
|
|
{
|
|
string msg = string.Format(LocaleManager.Instance[LocaleKeys.Dialog_ContentLoading_DLCAddedMessage], numAdded);
|
|
return Dispatcher.UIThread.InvokeAsync(async () =>
|
|
{
|
|
await ContentDialogHelper.ShowTextDialog(
|
|
LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
|
|
msg,
|
|
string.Empty,
|
|
string.Empty,
|
|
string.Empty,
|
|
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
|
(int)Symbol.Checkmark);
|
|
});
|
|
}
|
|
}
|
|
}
|