mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-06-17 09:49:14 +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
205 lines
6.7 KiB
C#
205 lines
6.7 KiB
C#
using Avalonia.Collections;
|
|
using Avalonia.Platform.Storage;
|
|
using Avalonia.Threading;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
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.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using static Ryujinx.Ava.Utilities.StorageProviderExtensions;
|
|
|
|
namespace Ryujinx.Ava.UI.ViewModels
|
|
{
|
|
public record TitleUpdateViewModelNoUpdate;
|
|
|
|
public partial class TitleUpdateViewModel : BaseModel
|
|
{
|
|
private ApplicationLibrary ApplicationLibrary { get; }
|
|
private ApplicationData ApplicationData { get; }
|
|
|
|
[ObservableProperty]
|
|
public partial AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = [];
|
|
|
|
[ObservableProperty]
|
|
public partial AvaloniaList<object> Views { get; set; } = [];
|
|
|
|
[ObservableProperty]
|
|
public partial object SelectedUpdate { get; set; } = new TitleUpdateViewModelNoUpdate();
|
|
|
|
[ObservableProperty]
|
|
public partial bool ShowBundledContentNotice { get; set; }
|
|
|
|
private readonly IStorageProvider _storageProvider;
|
|
|
|
public TitleUpdateViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData)
|
|
{
|
|
ApplicationLibrary = applicationLibrary;
|
|
|
|
ApplicationData = applicationData;
|
|
|
|
_storageProvider = RyujinxApp.MainWindow.StorageProvider;
|
|
|
|
LoadUpdates();
|
|
}
|
|
|
|
private void LoadUpdates()
|
|
{
|
|
(TitleUpdateModel TitleUpdate, bool IsSelected)[] updates = ApplicationLibrary.FindUpdateConfigurationFor(ApplicationData.Id);
|
|
|
|
bool hasBundledContent = false;
|
|
SelectedUpdate = new TitleUpdateViewModelNoUpdate();
|
|
foreach ((TitleUpdateModel update, bool isSelected) in updates)
|
|
{
|
|
TitleUpdates.Add(update);
|
|
hasBundledContent = hasBundledContent || update.IsBundled;
|
|
|
|
if (isSelected)
|
|
{
|
|
SelectedUpdate = update;
|
|
}
|
|
}
|
|
|
|
ShowBundledContentNotice = hasBundledContent;
|
|
|
|
SortUpdates();
|
|
}
|
|
|
|
public void SortUpdates()
|
|
{
|
|
IOrderedEnumerable<TitleUpdateModel> sortedUpdates = TitleUpdates.OrderByDescending(update => update.Version);
|
|
|
|
// NOTE(jpr): this works around a bug where calling Views.Clear also clears SelectedUpdate for
|
|
// some reason. so we save the item here and restore it after
|
|
object selected = SelectedUpdate;
|
|
|
|
Views.Clear();
|
|
Views.Add(new TitleUpdateViewModelNoUpdate());
|
|
Views.AddRange(sortedUpdates);
|
|
|
|
SelectedUpdate = selected;
|
|
|
|
if (SelectedUpdate is TitleUpdateViewModelNoUpdate)
|
|
{
|
|
SelectedUpdate = Views[0];
|
|
}
|
|
// this is mainly to handle a scenario where the user removes the selected update
|
|
else if (!TitleUpdates.Contains((TitleUpdateModel)SelectedUpdate))
|
|
{
|
|
SelectedUpdate = Views.Count > 1 ? Views[1] : Views[0];
|
|
}
|
|
}
|
|
|
|
private bool AddUpdate(string path, out int numUpdatesAdded)
|
|
{
|
|
numUpdatesAdded = 0;
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!ApplicationLibrary.TryGetTitleUpdatesFromFile(path, out List<TitleUpdateModel> updates))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
List<TitleUpdateModel> updatesForThisGame = updates.Where(it => it.TitleIdBase == ApplicationData.Id).ToList();
|
|
if (updatesForThisGame.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (TitleUpdateModel update in updatesForThisGame)
|
|
{
|
|
if (!TitleUpdates.Contains(update))
|
|
{
|
|
TitleUpdates.Add(update);
|
|
SelectedUpdate = update;
|
|
|
|
numUpdatesAdded++;
|
|
}
|
|
}
|
|
|
|
if (numUpdatesAdded > 0)
|
|
{
|
|
SortUpdates();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void RemoveUpdate(TitleUpdateModel update)
|
|
{
|
|
if (!update.IsBundled)
|
|
{
|
|
TitleUpdates.Remove(update);
|
|
}
|
|
else if (update == SelectedUpdate as TitleUpdateModel)
|
|
{
|
|
SelectedUpdate = new TitleUpdateViewModelNoUpdate();
|
|
}
|
|
|
|
SortUpdates();
|
|
}
|
|
|
|
public async Task Add()
|
|
{
|
|
IReadOnlyList<IStorageFile> result = await CoreDumpable(() => _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
|
{
|
|
AllowMultiple = true,
|
|
FileTypeFilter = new List<FilePickerFileType>
|
|
{
|
|
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
|
{
|
|
Patterns = ["*.nsp"],
|
|
AppleUniformTypeIdentifiers = ["com.ryujinx.nsp"],
|
|
MimeTypes = ["application/x-nx-nsp"],
|
|
},
|
|
},
|
|
}));
|
|
|
|
int totalUpdatesAdded = 0;
|
|
foreach (IStorageFile file in result)
|
|
{
|
|
if (!AddUpdate(file.Path.LocalPath, out int newUpdatesAdded))
|
|
{
|
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
|
}
|
|
|
|
totalUpdatesAdded += newUpdatesAdded;
|
|
}
|
|
|
|
if (totalUpdatesAdded > 0)
|
|
{
|
|
await ShowNewUpdatesAddedDialog(totalUpdatesAdded);
|
|
}
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
List<(TitleUpdateModel it, bool)> updates = TitleUpdates.Select(it => (it, it == SelectedUpdate as TitleUpdateModel)).ToList();
|
|
ApplicationLibrary.SaveTitleUpdatesForGame(ApplicationData, updates);
|
|
}
|
|
|
|
private Task<UserResult> ShowNewUpdatesAddedDialog(int numAdded)
|
|
{
|
|
string msg = string.Format(LocaleManager.Instance[LocaleKeys.Dialog_ContentLoading_UpdatesAddedMessage], 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
|
|
));
|
|
}
|
|
}
|
|
}
|