267 lines
8.6 KiB
C#
267 lines
8.6 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.UI.Helpers;
|
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Ryujinx.Ava.UI.ViewModels
|
|
{
|
|
public partial class DownloadableContentManagerViewModel : BaseModel
|
|
{
|
|
private readonly ApplicationLibrary _applicationLibrary;
|
|
private AvaloniaList<DownloadableContentModel> _downloadableContents = [];
|
|
[ObservableProperty] private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = [];
|
|
[ObservableProperty] private AvaloniaList<DownloadableContentModel> _views = [];
|
|
[ObservableProperty] private bool _showBundledContentNotice = false;
|
|
|
|
private string _search;
|
|
private readonly ApplicationData _applicationData;
|
|
private readonly IStorageProvider _storageProvider;
|
|
|
|
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
|
{
|
|
get => _downloadableContents;
|
|
set
|
|
{
|
|
_downloadableContents = value;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(UpdateCount));
|
|
Sort();
|
|
}
|
|
}
|
|
|
|
public string Search
|
|
{
|
|
get => _search;
|
|
set
|
|
{
|
|
_search = value;
|
|
OnPropertyChanged();
|
|
Sort();
|
|
}
|
|
}
|
|
|
|
public string UpdateCount
|
|
{
|
|
get => 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.ToLower().Contains(_search.ToLower()) || content.TitleIdStr.ToLower().Contains(_search.ToLower());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public async void Add()
|
|
{
|
|
IReadOnlyList<IStorageFile> result = await _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.DlcWindowDlcAddedMessage], 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);
|
|
});
|
|
}
|
|
}
|
|
}
|