Ava UI: Add Notifications and Cleanup (#4275)
* Ava UI: Add Notifications and Cleanup * Revert notifications on ErrorDialog * remove unused code from game list views * Fix cast
This commit is contained in:
parent
8474d52778
commit
a47824f961
8 changed files with 270 additions and 329 deletions
|
@ -1,4 +1,5 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Notifications;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
using LibHac.Account;
|
using LibHac.Account;
|
||||||
|
@ -12,7 +13,6 @@ using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
@ -44,14 +44,11 @@ namespace Ryujinx.Ava.Common
|
||||||
_accountManager = accountManager;
|
_accountManager = accountManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryFindSaveData(string titleName, ulong titleId,
|
private static bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
|
||||||
BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
|
|
||||||
{
|
{
|
||||||
saveDataId = default;
|
saveDataId = default;
|
||||||
|
|
||||||
Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo,
|
Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter);
|
||||||
SaveDataSpaceId.User, in filter);
|
|
||||||
|
|
||||||
if (ResultFs.TargetNotFound.Includes(result))
|
if (ResultFs.TargetNotFound.Includes(result))
|
||||||
{
|
{
|
||||||
ref ApplicationControlProperty control = ref controlHolder.Value;
|
ref ApplicationControlProperty control = ref controlHolder.Value;
|
||||||
|
@ -68,17 +65,15 @@ namespace Ryujinx.Ava.Common
|
||||||
control.UserAccountSaveDataSize = 0x4000;
|
control.UserAccountSaveDataSize = 0x4000;
|
||||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
control.UserAccountSaveDataJournalSize = 0x4000;
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Application,
|
Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||||
|
|
||||||
result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user);
|
result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user);
|
||||||
|
|
||||||
if (result.IsFailure())
|
if (result.IsFailure())
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageCreateSaveErrorMessage, result.ToStringWithName()));
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageCreateSaveErrorMessage, result.ToStringWithName()));
|
||||||
});
|
});
|
||||||
|
@ -97,7 +92,7 @@ namespace Ryujinx.Ava.Common
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageFindSaveErrorMessage, result.ToStringWithName()));
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageFindSaveErrorMessage, result.ToStringWithName()));
|
||||||
});
|
});
|
||||||
|
@ -105,8 +100,7 @@ namespace Ryujinx.Ava.Common
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenSaveDir(in SaveDataFilter saveDataFilter, ulong titleId,
|
public static void OpenSaveDir(in SaveDataFilter saveDataFilter, ulong titleId, BlitStruct<ApplicationControlProperty> controlData, string titleName)
|
||||||
BlitStruct<ApplicationControlProperty> controlData, string titleName)
|
|
||||||
{
|
{
|
||||||
if (!TryFindSaveData(titleName, titleId, controlData, in saveDataFilter, out ulong saveDataId))
|
if (!TryFindSaveData(titleName, titleId, controlData, in saveDataFilter, out ulong saveDataId))
|
||||||
{
|
{
|
||||||
|
@ -147,14 +141,15 @@ namespace Ryujinx.Ava.Common
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task ExtractSection(NcaSectionType ncaSectionType, string titleFilePath,
|
public static async Task ExtractSection(NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
|
||||||
int programIndex = 0)
|
|
||||||
{
|
{
|
||||||
OpenFolderDialog folderDialog = new() { Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle] };
|
OpenFolderDialog folderDialog = new()
|
||||||
|
{
|
||||||
|
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
|
||||||
|
};
|
||||||
|
|
||||||
string destination = await folderDialog.ShowAsync(_owner);
|
string destination = await folderDialog.ShowAsync(_owner);
|
||||||
|
var cancellationToken = new CancellationTokenSource();
|
||||||
var cancellationToken = new CancellationTokenSource();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(destination))
|
if (!string.IsNullOrWhiteSpace(destination))
|
||||||
{
|
{
|
||||||
|
@ -175,132 +170,121 @@ namespace Ryujinx.Ava.Common
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Thread.Sleep(1000);
|
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
using (FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read))
|
Nca mainNca = null;
|
||||||
|
Nca patchNca = null;
|
||||||
|
|
||||||
|
string extension = Path.GetExtension(titleFilePath).ToLower();
|
||||||
|
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
||||||
{
|
{
|
||||||
Nca mainNca = null;
|
PartitionFileSystem pfs;
|
||||||
Nca patchNca = null;
|
|
||||||
|
|
||||||
string extension = Path.GetExtension(titleFilePath).ToLower();
|
if (extension == ".xci")
|
||||||
|
|
||||||
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
|
||||||
{
|
{
|
||||||
PartitionFileSystem pfs;
|
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pfs = new PartitionFileSystem(file.AsStorage());
|
||||||
|
}
|
||||||
|
|
||||||
if (extension == ".xci")
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||||
|
if (nca.Header.ContentType == NcaContentType.Program)
|
||||||
{
|
{
|
||||||
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pfs = new PartitionFileSystem(file.AsStorage());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.Program)
|
|
||||||
{
|
{
|
||||||
int dataIndex =
|
patchNca = nca;
|
||||||
Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
}
|
||||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
else
|
||||||
{
|
{
|
||||||
patchNca = nca;
|
mainNca = nca;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mainNca = nca;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (extension == ".nca")
|
}
|
||||||
{
|
else if (extension == ".nca")
|
||||||
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
{
|
||||||
}
|
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
||||||
|
}
|
||||||
|
|
||||||
if (mainNca == null)
|
if (mainNca == null)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA was not present in the selected file");
|
||||||
|
|
||||||
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application,
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
|
||||||
"Extraction failure. The main NCA was not present in the selected file");
|
});
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||||
|
if (updatePatchNca != null)
|
||||||
|
{
|
||||||
|
patchNca = updatePatchNca;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IFileSystem ncaFileSystem = patchNca != null
|
||||||
|
? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
|
||||||
|
: mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
|
||||||
|
|
||||||
|
FileSystemClient fsClient = _horizonClient.Fs;
|
||||||
|
|
||||||
|
string source = DateTime.Now.ToFileTime().ToString()[10..];
|
||||||
|
string output = DateTime.Now.ToFileTime().ToString()[10..];
|
||||||
|
|
||||||
|
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
|
||||||
|
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
|
||||||
|
|
||||||
|
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref());
|
||||||
|
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref());
|
||||||
|
|
||||||
|
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
|
||||||
|
|
||||||
|
if (!canceled)
|
||||||
|
{
|
||||||
|
if (resultCode.Value.IsFailure())
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
|
Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}");
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem,
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
|
||||||
if (updatePatchNca != null)
|
|
||||||
{
|
|
||||||
patchNca = updatePatchNca;
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IFileSystem ncaFileSystem = patchNca != null
|
|
||||||
? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
|
|
||||||
: mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
|
|
||||||
|
|
||||||
FileSystemClient fsClient = _horizonClient.Fs;
|
|
||||||
|
|
||||||
string source = DateTime.Now.ToFileTime().ToString()[10..];
|
|
||||||
string output = DateTime.Now.ToFileTime().ToString()[10..];
|
|
||||||
|
|
||||||
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
|
|
||||||
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
|
|
||||||
|
|
||||||
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref());
|
|
||||||
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref());
|
|
||||||
|
|
||||||
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
|
|
||||||
|
|
||||||
if (!canceled)
|
|
||||||
{
|
|
||||||
if (resultCode.Value.IsFailure())
|
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application,
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
|
||||||
$"LibHac returned error code: {resultCode.Value.ErrorCode}");
|
});
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (resultCode.Value.IsSuccess())
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateInfoDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage],
|
|
||||||
"",
|
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
|
||||||
"",
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (resultCode.Value.IsSuccess())
|
||||||
fsClient.Unmount(source.ToU8Span());
|
|
||||||
fsClient.Unmount(output.ToU8Span());
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
NotificationHelper.Show(
|
||||||
});
|
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
|
||||||
|
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
|
||||||
|
NotificationType.Information);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fsClient.Unmount(source.ToU8Span());
|
||||||
|
fsClient.Unmount(output.ToU8Span());
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"{ex.Message}");
|
||||||
|
|
||||||
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
<MenuFlyout x:Key="GameContextMenu">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding ToggleFavorite}"
|
Command="{Binding ToggleFavorite}"
|
||||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||||
|
@ -22,14 +22,17 @@
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenUserSaveDirectory}"
|
Command="{Binding OpenUserSaveDirectory}"
|
||||||
|
IsEnabled="{Binding EnabledUserSaveDirectory}"
|
||||||
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenDeviceSaveDirectory}"
|
Command="{Binding OpenDeviceSaveDirectory}"
|
||||||
|
IsEnabled="{Binding EnabledDeviceSaveDirectory}"
|
||||||
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
|
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenBcatSaveDirectory}"
|
Command="{Binding OpenBcatSaveDirectory}"
|
||||||
|
IsEnabled="{Binding EnabledBcatSaveDirectory}"
|
||||||
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
|
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
using Avalonia.Collections;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using LibHac.Common;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
|
@ -13,16 +11,25 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
public partial class GameGridView : UserControl
|
public partial class GameGridView : UserControl
|
||||||
{
|
{
|
||||||
private ApplicationData _selectedApplication;
|
|
||||||
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
|
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
|
||||||
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
|
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
|
||||||
|
|
||||||
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
|
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
|
||||||
{
|
{
|
||||||
add { AddHandler(ApplicationOpenedEvent, value); }
|
add { AddHandler(ApplicationOpenedEvent, value); }
|
||||||
remove { RemoveHandler(ApplicationOpenedEvent, value); }
|
remove { RemoveHandler(ApplicationOpenedEvent, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GameGridView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
|
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
if (sender is ListBox listBox)
|
if (sender is ListBox listBox)
|
||||||
|
@ -38,46 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
if (sender is ListBox listBox)
|
if (sender is ListBox listBox)
|
||||||
{
|
{
|
||||||
_selectedApplication = listBox.SelectedItem as ApplicationData;
|
(DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData;
|
||||||
|
|
||||||
(DataContext as MainWindowViewModel).GridSelectedApplication = _selectedApplication;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApplicationData SelectedApplication => _selectedApplication;
|
|
||||||
|
|
||||||
public GameGridView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
|
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MenuBase_OnMenuOpened(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
var selection = SelectedApplication;
|
|
||||||
|
|
||||||
if (selection != null)
|
|
||||||
{
|
|
||||||
if (sender is ContextMenu menu)
|
|
||||||
{
|
|
||||||
bool canHaveUserSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
|
||||||
bool canHaveDeviceSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.DeviceSaveDataSize > 0;
|
|
||||||
bool canHaveBcatSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
|
||||||
|
|
||||||
((menu.Items as AvaloniaList<object>)[2] as MenuItem).IsEnabled = canHaveUserSave;
|
|
||||||
((menu.Items as AvaloniaList<object>)[3] as MenuItem).IsEnabled = canHaveDeviceSave;
|
|
||||||
((menu.Items as AvaloniaList<object>)[4] as MenuItem).IsEnabled = canHaveBcatSave;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
<MenuFlyout x:Key="GameContextMenu">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding ToggleFavorite}"
|
Command="{Binding ToggleFavorite}"
|
||||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||||
|
@ -21,14 +21,17 @@
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenUserSaveDirectory}"
|
Command="{Binding OpenUserSaveDirectory}"
|
||||||
|
IsEnabled="{Binding EnabledUserSaveDirectory}"
|
||||||
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenDeviceSaveDirectory}"
|
Command="{Binding OpenDeviceSaveDirectory}"
|
||||||
|
IsEnabled="{Binding EnabledDeviceSaveDirectory}"
|
||||||
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
|
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenBcatSaveDirectory}"
|
Command="{Binding OpenBcatSaveDirectory}"
|
||||||
|
IsEnabled="{Binding EnabledBcatSaveDirectory}"
|
||||||
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
|
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
using Avalonia.Collections;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using LibHac.Common;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
|
@ -13,16 +11,25 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
public partial class GameListView : UserControl
|
public partial class GameListView : UserControl
|
||||||
{
|
{
|
||||||
private ApplicationData _selectedApplication;
|
|
||||||
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
|
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
|
||||||
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
|
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
|
||||||
|
|
||||||
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
|
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
|
||||||
{
|
{
|
||||||
add { AddHandler(ApplicationOpenedEvent, value); }
|
add { AddHandler(ApplicationOpenedEvent, value); }
|
||||||
remove { RemoveHandler(ApplicationOpenedEvent, value); }
|
remove { RemoveHandler(ApplicationOpenedEvent, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GameListView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
|
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
if (sender is ListBox listBox)
|
if (sender is ListBox listBox)
|
||||||
|
@ -38,46 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
if (sender is ListBox listBox)
|
if (sender is ListBox listBox)
|
||||||
{
|
{
|
||||||
_selectedApplication = listBox.SelectedItem as ApplicationData;
|
(DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData;
|
||||||
|
|
||||||
(DataContext as MainWindowViewModel).ListSelectedApplication = _selectedApplication;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApplicationData SelectedApplication => _selectedApplication;
|
|
||||||
|
|
||||||
public GameListView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
|
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MenuBase_OnMenuOpened(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
var selection = SelectedApplication;
|
|
||||||
|
|
||||||
if (selection != null)
|
|
||||||
{
|
|
||||||
if (sender is ContextMenu menu)
|
|
||||||
{
|
|
||||||
bool canHaveUserSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
|
||||||
bool canHaveDeviceSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.DeviceSaveDataSize > 0;
|
|
||||||
bool canHaveBcatSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
|
||||||
|
|
||||||
((menu.Items as AvaloniaList<object>)[2] as MenuItem).IsEnabled = canHaveUserSave;
|
|
||||||
((menu.Items as AvaloniaList<object>)[3] as MenuItem).IsEnabled = canHaveDeviceSave;
|
|
||||||
((menu.Items as AvaloniaList<object>)[4] as MenuItem).IsEnabled = canHaveBcatSave;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
65
Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
Normal file
65
Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Notifications;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
|
{
|
||||||
|
public static class NotificationHelper
|
||||||
|
{
|
||||||
|
private const int MaxNotifications = 4;
|
||||||
|
private const int NotificationDelayInMs = 5000;
|
||||||
|
|
||||||
|
private static WindowNotificationManager _notificationManager;
|
||||||
|
|
||||||
|
private static readonly ManualResetEvent _templateAppliedEvent = new(false);
|
||||||
|
private static readonly BlockingCollection<Notification> _notifications = new();
|
||||||
|
|
||||||
|
public static void SetNotificationManager(Window host)
|
||||||
|
{
|
||||||
|
_notificationManager = new WindowNotificationManager(host)
|
||||||
|
{
|
||||||
|
Position = NotificationPosition.BottomRight,
|
||||||
|
MaxItems = MaxNotifications,
|
||||||
|
Margin = new Thickness(0, 0, 15, 40)
|
||||||
|
};
|
||||||
|
|
||||||
|
_notificationManager.TemplateApplied += (sender, args) =>
|
||||||
|
{
|
||||||
|
_templateAppliedEvent.Set();
|
||||||
|
};
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
_templateAppliedEvent.WaitOne();
|
||||||
|
|
||||||
|
foreach (var notification in _notifications.GetConsumingEnumerable())
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
_notificationManager.Show(notification);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.Delay(NotificationDelayInMs / MaxNotifications);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)
|
||||||
|
{
|
||||||
|
var delay = waitingExit ? TimeSpan.FromMilliseconds(0) : TimeSpan.FromMilliseconds(NotificationDelayInMs);
|
||||||
|
|
||||||
|
_notifications.Add(new Notification(title, text, type, delay, onClick, onClose));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ShowError(string message)
|
||||||
|
{
|
||||||
|
Show(LocaleManager.Instance[LocaleKeys.DialogErrorTitle], $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}", NotificationType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using LibHac.Bcat;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
|
@ -344,6 +344,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool EnabledUserSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||||
|
|
||||||
|
public bool EnabledDeviceSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||||
|
|
||||||
|
public bool EnabledBcatSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||||
|
|
||||||
public string LoadHeading
|
public string LoadHeading
|
||||||
{
|
{
|
||||||
get => _loadHeading;
|
get => _loadHeading;
|
||||||
|
@ -735,19 +741,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
switch (ConfigurationState.Instance.Ui.GridSize)
|
return ConfigurationState.Instance.Ui.GridSize.Value switch
|
||||||
{
|
{
|
||||||
case 1:
|
1 => 78,
|
||||||
return 78;
|
2 => 100,
|
||||||
case 2:
|
3 => 120,
|
||||||
return 100;
|
4 => 140,
|
||||||
case 3:
|
_ => 16,
|
||||||
return 120;
|
};
|
||||||
case 4:
|
|
||||||
return 140;
|
|
||||||
default:
|
|
||||||
return 16;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,19 +756,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
switch (ConfigurationState.Instance.Ui.GridSize)
|
return ConfigurationState.Instance.Ui.GridSize.Value switch
|
||||||
{
|
{
|
||||||
case 1:
|
1 => 120,
|
||||||
return 120;
|
2 => ShowNames ? 210 : 150,
|
||||||
case 2:
|
3 => ShowNames ? 240 : 180,
|
||||||
return ShowNames ? 210 : 150;
|
4 => ShowNames ? 280 : 220,
|
||||||
case 3:
|
_ => 16,
|
||||||
return ShowNames ? 240 : 180;
|
};
|
||||||
case 4:
|
|
||||||
return ShowNames ? 280 : 220;
|
|
||||||
default:
|
|
||||||
return 16;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1091,35 +1087,27 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSaveDirectory(in SaveDataFilter filter, ApplicationData data, ulong titleId)
|
|
||||||
{
|
|
||||||
ApplicationHelper.OpenSaveDir(in filter, titleId, data.ControlHolder, data.TitleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void ExtractLogo()
|
private async void ExtractLogo()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, selection.Path);
|
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, SelectedApplication.Path, SelectedApplication.TitleName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ExtractRomFs()
|
private async void ExtractRomFs()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
await ApplicationHelper.ExtractSection(NcaSectionType.Data, selection.Path);
|
await ApplicationHelper.ExtractSection(NcaSectionType.Data, SelectedApplication.Path, SelectedApplication.TitleName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ExtractExeFs()
|
private async void ExtractExeFs()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
await ApplicationHelper.ExtractSection(NcaSectionType.Code, selection.Path);
|
await ApplicationHelper.ExtractSection(NcaSectionType.Code, SelectedApplication.Path, SelectedApplication.TitleName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1487,56 +1475,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenDeviceSaveDirectory()
|
|
||||||
{
|
|
||||||
ApplicationData selection = SelectedApplication;
|
|
||||||
if (selection != null)
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
|
||||||
{
|
|
||||||
async void Action()
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(Action);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
|
||||||
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenBcatSaveDirectory()
|
|
||||||
{
|
|
||||||
ApplicationData selection = SelectedApplication;
|
|
||||||
if (selection != null)
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
|
||||||
{
|
|
||||||
async void Action()
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(Action);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
|
||||||
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToggleFavorite()
|
public void ToggleFavorite()
|
||||||
{
|
{
|
||||||
ApplicationData selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
@ -1555,37 +1493,45 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
public void OpenUserSaveDirectory()
|
public void OpenUserSaveDirectory()
|
||||||
{
|
{
|
||||||
ApplicationData selection = SelectedApplication;
|
OpenSaveDirectory(SaveDataType.Account, userId: new UserId((ulong)AccountManager.LastOpenedUser.UserId.High, (ulong)AccountManager.LastOpenedUser.UserId.Low));
|
||||||
if (selection != null)
|
}
|
||||||
|
|
||||||
|
public void OpenDeviceSaveDirectory()
|
||||||
|
{
|
||||||
|
OpenSaveDirectory(SaveDataType.Device, userId: default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenBcatSaveDirectory()
|
||||||
|
{
|
||||||
|
OpenSaveDirectory(SaveDataType.Bcat, userId: default);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSaveDirectory(SaveDataType saveDataType, UserId userId)
|
||||||
|
{
|
||||||
|
if (SelectedApplication != null)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
if (!ulong.TryParse(SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
||||||
{
|
{
|
||||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
async void Action()
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
||||||
{
|
});
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(Action);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
|
||||||
}
|
|
||||||
|
|
||||||
UserId userId = new((ulong)AccountManager.LastOpenedUser.UserId.High, (ulong)AccountManager.LastOpenedUser.UserId.Low);
|
ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, SelectedApplication.ControlHolder, SelectedApplication.TitleName);
|
||||||
SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
|
|
||||||
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenModsDirectory()
|
public void OpenModsDirectory()
|
||||||
{
|
{
|
||||||
ApplicationData selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
string modsBasePath = VirtualFileSystem.ModLoader.GetModsBasePath();
|
string modsBasePath = VirtualFileSystem.ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
|
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, SelectedApplication.TitleId);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
@ -1593,12 +1539,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
public void OpenSdModsDirectory()
|
public void OpenSdModsDirectory()
|
||||||
{
|
{
|
||||||
ApplicationData selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
|
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
string sdModsBasePath = VirtualFileSystem.ModLoader.GetSdModsBasePath();
|
string sdModsBasePath = VirtualFileSystem.ModLoader.GetSdModsBasePath();
|
||||||
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
|
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, SelectedApplication.TitleId);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
|
@ -1614,25 +1558,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
public async void OpenDownloadableContentManager()
|
public async void OpenDownloadableContentManager()
|
||||||
{
|
{
|
||||||
ApplicationData selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
|
||||||
{
|
|
||||||
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(desktop.MainWindow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void OpenCheatManager()
|
public async void OpenCheatManager()
|
||||||
{
|
{
|
||||||
ApplicationData selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
await new CheatWindow(VirtualFileSystem, SelectedApplication.TitleId, SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
|
||||||
{
|
|
||||||
await new CheatWindow(VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(desktop.MainWindow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,8 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||||
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||||
ViewModel.ReloadGameList += ReloadGameList;
|
ViewModel.ReloadGameList += ReloadGameList;
|
||||||
|
|
||||||
|
NotificationHelper.SetNotificationManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void IsActiveChanged(bool obj)
|
private void IsActiveChanged(bool obj)
|
||||||
|
|
Reference in a new issue