Compare commits

...

10 Commits

Author SHA1 Message Date
Evan Husted
c5091f499e docs: compat: The House of the Dead: Remake Playable 2025-01-11 00:38:32 -06:00
Evan Husted
41c8fd8194 misc: chore: lol this field was misspelled 2025-01-10 23:23:53 -06:00
Evan Husted
d4a7ee25ea misc: chore: use ObservableProperty on input view models 2025-01-10 23:23:05 -06:00
Evan Husted
3141c560fb misc: chore: remove sender parameter from LdnGameData receieved event 2025-01-10 23:15:55 -06:00
Evan Husted
de341b285b misc: use ObservableProperty on HotkeyConfig fields 2025-01-10 23:15:37 -06:00
Evan Husted
cc95e80ee9 misc: chore: Move converters into a directory in Helpers. Namespace unchanged 2025-01-10 20:24:53 -06:00
Evan Husted
d75ce52bd4 UI: Show play time in one time unit, maxing out at hours. 2025-01-10 20:23:47 -06:00
Evan Husted
4a4ea557de UI: compat: show last updated date on entry hover 2025-01-10 01:43:34 -06:00
Evan Husted
33f42adb11 Merge remote-tracking branch 'origin/master' 2025-01-09 22:09:01 -06:00
Evan Husted
cca429d46a misc: chore: restore not enable 2025-01-09 21:42:54 -06:00
29 changed files with 136 additions and 334 deletions

View File

@@ -42,7 +42,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Gommon" Version="2.7.0.1" /> <PackageVersion Include="Gommon" Version="2.7.0.2" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.6.0" /> <PackageVersion Include="Sep" Version="0.6.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />

View File

@@ -3420,3 +3420,4 @@
0100936018EB4000,"牧場物語 Welcomeワンダフルライフ",status-ingame;crash,ingame,2023-04-25 19:43:52.000 0100936018EB4000,"牧場物語 Welcomeワンダフルライフ",status-ingame;crash,ingame,2023-04-25 19:43:52.000
0100F4401940A000,"超探偵事件簿 レインコード (Master Detective Archives: Rain Code)",status-ingame;crash,ingame,2024-02-12 20:58:31.000 0100F4401940A000,"超探偵事件簿 レインコード (Master Detective Archives: Rain Code)",status-ingame;crash,ingame,2024-02-12 20:58:31.000
010064801A01C000,"超次元ゲイム ネプテューヌ GameMaker R:Evolution",status-nothing;crash,nothing,2023-10-30 22:37:40.000 010064801A01C000,"超次元ゲイム ネプテューヌ GameMaker R:Evolution",status-nothing;crash,nothing,2023-10-30 22:37:40.000
010088401495E000,"The House of the Dead: Remake",status-playable,playable,2025-01-11 00:36:01
1 title_id game_name labels status last_updated
3420 0100936018EB4000 牧場物語 Welcome!ワンダフルライフ status-ingame;crash ingame 2023-04-25 19:43:52.000
3421 0100F4401940A000 超探偵事件簿 レインコード (Master Detective Archives: Rain Code) status-ingame;crash ingame 2024-02-12 20:58:31.000
3422 010064801A01C000 超次元ゲイム ネプテューヌ GameMaker R:Evolution status-nothing;crash nothing 2023-10-30 22:37:40.000
3423 010088401495E000 The House of the Dead: Remake status-playable playable 2025-01-11 00:36:01

View File

@@ -22597,6 +22597,31 @@
"zh_TW": "" "zh_TW": ""
} }
}, },
{
"ID": "CompatibilityListLastUpdated",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Last updated: {0}",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "CompatibilityListWarning", "ID": "CompatibilityListWarning",
"Translations": { "Translations": {

View File

@@ -133,12 +133,13 @@
Spacing="5"> Spacing="5">
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding TimePlayedString}" Text="{Binding LastPlayedString}"
TextAlignment="End" TextAlignment="End"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding LastPlayedString}" Text="{Binding TimePlayedString}"
IsVisible="{Binding HasPlayedPreviously}"
TextAlignment="End" TextAlignment="End"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock

View File

@@ -1,152 +1,53 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
namespace Ryujinx.Ava.UI.Models.Input namespace Ryujinx.Ava.UI.Models.Input
{ {
public class HotkeyConfig : BaseModel public partial class HotkeyConfig : BaseModel
{ {
private Key _toggleVSyncMode; [ObservableProperty] private Key _toggleVSyncMode;
public Key ToggleVSyncMode
{
get => _toggleVSyncMode;
set
{
_toggleVSyncMode = value;
OnPropertyChanged();
}
}
private Key _screenshot; [ObservableProperty] private Key _screenshot;
public Key Screenshot
{
get => _screenshot;
set
{
_screenshot = value;
OnPropertyChanged();
}
}
private Key _showUI; [ObservableProperty] private Key _showUI;
public Key ShowUI
{
get => _showUI;
set
{
_showUI = value;
OnPropertyChanged();
}
}
private Key _pause; [ObservableProperty] private Key _pause;
public Key Pause
{
get => _pause;
set
{
_pause = value;
OnPropertyChanged();
}
}
private Key _toggleMute; [ObservableProperty] private Key _toggleMute;
public Key ToggleMute
{
get => _toggleMute;
set
{
_toggleMute = value;
OnPropertyChanged();
}
}
private Key _resScaleUp; [ObservableProperty] private Key _resScaleUp;
public Key ResScaleUp
{
get => _resScaleUp;
set
{
_resScaleUp = value;
OnPropertyChanged();
}
}
private Key _resScaleDown; [ObservableProperty] private Key _resScaleDown;
public Key ResScaleDown
{
get => _resScaleDown;
set
{
_resScaleDown = value;
OnPropertyChanged();
}
}
private Key _volumeUp; [ObservableProperty] private Key _volumeUp;
public Key VolumeUp
{
get => _volumeUp;
set
{
_volumeUp = value;
OnPropertyChanged();
}
}
private Key _volumeDown; [ObservableProperty] private Key _volumeDown;
public Key VolumeDown
{
get => _volumeDown;
set
{
_volumeDown = value;
OnPropertyChanged();
}
}
private Key _customVSyncIntervalIncrement; [ObservableProperty] private Key _customVSyncIntervalIncrement;
public Key CustomVSyncIntervalIncrement
{
get => _customVSyncIntervalIncrement;
set
{
_customVSyncIntervalIncrement = value;
OnPropertyChanged();
}
}
private Key _customVSyncIntervalDecrement; [ObservableProperty] private Key _customVSyncIntervalDecrement;
public Key CustomVSyncIntervalDecrement
{
get => _customVSyncIntervalDecrement;
set
{
_customVSyncIntervalDecrement = value;
OnPropertyChanged();
}
}
public HotkeyConfig(KeyboardHotkeys config) public HotkeyConfig(KeyboardHotkeys config)
{ {
if (config != null) if (config == null)
{ return;
ToggleVSyncMode = config.ToggleVSyncMode;
Screenshot = config.Screenshot; ToggleVSyncMode = config.ToggleVSyncMode;
ShowUI = config.ShowUI; Screenshot = config.Screenshot;
Pause = config.Pause; ShowUI = config.ShowUI;
ToggleMute = config.ToggleMute; Pause = config.Pause;
ResScaleUp = config.ResScaleUp; ToggleMute = config.ToggleMute;
ResScaleDown = config.ResScaleDown; ResScaleUp = config.ResScaleUp;
VolumeUp = config.VolumeUp; ResScaleDown = config.ResScaleDown;
VolumeDown = config.VolumeDown; VolumeUp = config.VolumeUp;
CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement; VolumeDown = config.VolumeDown;
CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement; CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement;
} CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement;
} }
public KeyboardHotkeys GetConfig() public KeyboardHotkeys GetConfig() =>
{ new()
var config = new KeyboardHotkeys
{ {
ToggleVSyncMode = ToggleVSyncMode, ToggleVSyncMode = ToggleVSyncMode,
Screenshot = Screenshot, Screenshot = Screenshot,
@@ -160,8 +61,5 @@ namespace Ryujinx.Ava.UI.Models.Input
CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement, CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement,
CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement, CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement,
}; };
return config;
}
} }
} }

View File

@@ -1,21 +1,13 @@
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Ava.UI.Views.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public class ControllerInputViewModel : BaseModel public partial class ControllerInputViewModel : BaseModel
{ {
private GamepadInputConfig _config; [ObservableProperty] private GamepadInputConfig _config;
public GamepadInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private bool _isLeft; private bool _isLeft;
public bool IsLeft public bool IsLeft
@@ -43,16 +35,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool HasSides => IsLeft ^ IsRight; public bool HasSides => IsLeft ^ IsRight;
private SvgImage _image; [ObservableProperty] private SvgImage _image;
public SvgImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
public readonly InputViewModel ParentModel; public readonly InputViewModel ParentModel;

View File

@@ -1,9 +1,8 @@
using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input; using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
@@ -32,7 +31,7 @@ using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public class InputViewModel : BaseModel, IDisposable public partial class InputViewModel : BaseModel, IDisposable
{ {
private const string Disabled = "disabled"; private const string Disabled = "disabled";
private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon.svg"; private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon.svg";
@@ -48,8 +47,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private int _controller; private int _controller;
private string _controllerImage; private string _controllerImage;
private int _device; private int _device;
private object _configViewModel; [ObservableProperty] private object _configViewModel;
private string _profileName; [ObservableProperty] private string _profileName;
private bool _isLoaded; private bool _isLoaded;
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@@ -73,17 +72,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsModified { get; set; } public bool IsModified { get; set; }
public event Action NotifyChangesEvent; public event Action NotifyChangesEvent;
public object ConfigViewModel
{
get => _configViewModel;
set
{
_configViewModel = value;
OnPropertyChanged();
}
}
public PlayerIndex PlayerIdChoose public PlayerIndex PlayerIdChoose
{ {
get => _playerIdChoose; get => _playerIdChoose;
@@ -200,16 +188,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
public string ProfileName
{
get => _profileName; set
{
_profileName = value;
OnPropertyChanged();
}
}
public int Device public int Device
{ {
get => _device; get => _device;

View File

@@ -1,20 +1,12 @@
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public class KeyboardInputViewModel : BaseModel public partial class KeyboardInputViewModel : BaseModel
{ {
private KeyboardInputConfig _config; [ObservableProperty] private KeyboardInputConfig _config;
public KeyboardInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private bool _isLeft; private bool _isLeft;
public bool IsLeft public bool IsLeft
@@ -42,16 +34,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool HasSides => IsLeft ^ IsRight; public bool HasSides => IsLeft ^ IsRight;
private SvgImage _image; [ObservableProperty] private SvgImage _image;
public SvgImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
public readonly InputViewModel ParentModel; public readonly InputViewModel ParentModel;

View File

@@ -1,93 +1,23 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public class MotionInputViewModel : BaseModel public partial class MotionInputViewModel : BaseModel
{ {
private int _slot; [ObservableProperty] private int _slot;
public int Slot
{
get => _slot;
set
{
_slot = value;
OnPropertyChanged();
}
}
private int _altSlot; [ObservableProperty] private int _altSlot;
public int AltSlot
{
get => _altSlot;
set
{
_altSlot = value;
OnPropertyChanged();
}
}
private string _dsuServerHost; [ObservableProperty] private string _dsuServerHost;
public string DsuServerHost
{
get => _dsuServerHost;
set
{
_dsuServerHost = value;
OnPropertyChanged();
}
}
private int _dsuServerPort; [ObservableProperty] private int _dsuServerPort;
public int DsuServerPort
{
get => _dsuServerPort;
set
{
_dsuServerPort = value;
OnPropertyChanged();
}
}
private bool _mirrorInput; [ObservableProperty] private bool _mirrorInput;
public bool MirrorInput
{
get => _mirrorInput;
set
{
_mirrorInput = value;
OnPropertyChanged();
}
}
private int _sensitivity; [ObservableProperty] private int _sensitivity;
public int Sensitivity
{
get => _sensitivity;
set
{
_sensitivity = value;
OnPropertyChanged();
}
}
private double _gryoDeadzone; [ObservableProperty] private double _gyroDeadzone;
public double GyroDeadzone
{
get => _gryoDeadzone;
set
{
_gryoDeadzone = value;
OnPropertyChanged();
}
}
private bool _enableCemuHookMotion; [ObservableProperty] private bool _enableCemuHookMotion;
public bool EnableCemuHookMotion
{
get => _enableCemuHookMotion;
set
{
_enableCemuHookMotion = value;
OnPropertyChanged();
}
}
} }
} }

View File

@@ -1,27 +1,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
public class RumbleInputViewModel : BaseModel public partial class RumbleInputViewModel : BaseModel
{ {
private float _strongRumble; [ObservableProperty] private float _strongRumble;
public float StrongRumble
{
get => _strongRumble;
set
{
_strongRumble = value;
OnPropertyChanged();
}
}
private float _weakRumble; [ObservableProperty] private float _weakRumble;
public float WeakRumble
{
get => _weakRumble;
set
{
_weakRumble = value;
OnPropertyChanged();
}
}
} }
} }

View File

@@ -743,7 +743,7 @@ namespace Ryujinx.Ava.UI.ViewModels
.Sort(GetComparer()) .Sort(GetComparer())
#pragma warning disable MVVMTK0034 #pragma warning disable MVVMTK0034
.Bind(out _appsObservableList) .Bind(out _appsObservableList)
#pragma warning enable MVVMTK0034 #pragma warning restore MVVMTK0034
.AsObservableList(); .AsObservableList();
OnPropertyChanged(nameof(AppsObservableList)); OnPropertyChanged(nameof(AppsObservableList));

View File

@@ -164,7 +164,7 @@ namespace Ryujinx.Ava.UI.Windows
}); });
} }
private void ApplicationLibrary_LdnGameDataReceived(object sender, LdnGameDataReceivedEventArgs e) private void ApplicationLibrary_LdnGameDataReceived(LdnGameDataReceivedEventArgs e)
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
@@ -408,13 +408,10 @@ namespace Ryujinx.Ava.UI.Windows
{ {
StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged; StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
ApplicationGrid.DataContext = ApplicationList.DataContext = ViewModel;
ApplicationGrid.ApplicationOpened += Application_Opened; ApplicationGrid.ApplicationOpened += Application_Opened;
ApplicationGrid.DataContext = ViewModel;
ApplicationList.ApplicationOpened += Application_Opened; ApplicationList.ApplicationOpened += Application_Opened;
ApplicationList.DataContext = ViewModel;
} }
private void SetWindowSizePosition() private void SetWindowSizePosition()

View File

@@ -1,4 +1,6 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@@ -23,6 +25,11 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent(); InitializeComponent();
Load(); Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
} }
public SettingsWindow() public SettingsWindow()

View File

@@ -37,6 +37,8 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed); public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
public bool HasPlayedPreviously => TimePlayedString != string.Empty;
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n"); public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n");
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize); public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);

View File

@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com"; public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
public Language DesiredLanguage { get; set; } public Language DesiredLanguage { get; set; }
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated; public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
public event EventHandler<LdnGameDataReceivedEventArgs> LdnGameDataReceived; public event Action<LdnGameDataReceivedEventArgs> LdnGameDataReceived;
public readonly IObservableCache<ApplicationData, ulong> Applications; public readonly IObservableCache<ApplicationData, ulong> Applications;
public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates; public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
@@ -779,7 +779,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
using HttpClient httpClient = new HttpClient(); using HttpClient httpClient = new HttpClient();
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games"); string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData); ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData);
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
{ {
LdnData = ldnGameDataArray LdnData = ldnGameDataArray
}); });
@@ -787,7 +787,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
catch (Exception ex) catch (Exception ex)
{ {
Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}"); Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}");
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
{ {
LdnData = Array.Empty<LdnGameData>() LdnData = Array.Empty<LdnGameData>()
}); });
@@ -795,7 +795,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
} }
else else
{ {
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
{ {
LdnData = Array.Empty<LdnGameData>() LdnData = Array.Empty<LdnGameData>()
}); });

View File

@@ -1,4 +1,5 @@
using Gommon; using Gommon;
using Humanizer;
using nietras.SeparatedValues; using nietras.SeparatedValues;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@@ -83,6 +84,9 @@ namespace Ryujinx.Ava.Utilities.Compat
public LocaleKeys? Status { get; } public LocaleKeys? Status { get; }
public DateTime LastUpdated { get; } public DateTime LastUpdated { get; }
public string LocalizedLastUpdated =>
LocaleManager.FormatDynamicValue(LocaleKeys.CompatibilityListLastUpdated, LastUpdated.Humanize());
public string LocalizedStatus => LocaleManager.Instance[Status!.Value]; public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
public string FormattedTitleId => TitleId public string FormattedTitleId => TitleId
.OrElse(new string(' ', 16)); .OrElse(new string(' ', 16));
@@ -97,7 +101,9 @@ namespace Ryujinx.Ava.Utilities.Compat
StringBuilder sb = new("CompatibilityEntry: {"); StringBuilder sb = new("CompatibilityEntry: {");
sb.Append($"{nameof(GameName)}=\"{GameName}\", "); sb.Append($"{nameof(GameName)}=\"{GameName}\", ");
sb.Append($"{nameof(TitleId)}={TitleId}, "); sb.Append($"{nameof(TitleId)}={TitleId}, ");
sb.Append($"{nameof(Labels)}=\"{Labels}\", "); sb.Append($"{nameof(Labels)}={
Labels.FormatCollection(it => $"\"{it}\"", separator: ", ", prefix: "[", suffix: "]")
}, ");
sb.Append($"{nameof(Status)}=\"{Status}\", "); sb.Append($"{nameof(Status)}=\"{Status}\", ");
sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\""); sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"");
sb.Append('}'); sb.Append('}');

View File

@@ -44,8 +44,11 @@
ItemsSource="{Binding CurrentEntries}"> ItemsSource="{Binding CurrentEntries}">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:CompatibilityEntry}"> <DataTemplate DataType="{x:Type local:CompatibilityEntry}">
<Grid Width="750" ColumnDefinitions="Auto,Auto,Auto,*" <Grid Width="750"
Margin="5"> Margin="5"
ColumnDefinitions="Auto,Auto,Auto,*"
Background="Transparent"
ToolTip.Tip="{Binding LocalizedLastUpdated}">
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
Text="{Binding GameName}" Text="{Binding GameName}"
Width="320" Width="320"

View File

@@ -1,3 +1,5 @@
using Humanizer;
using Humanizer.Localisation;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using System; using System;
using System.Globalization; using System.Globalization;
@@ -31,7 +33,7 @@ namespace Ryujinx.Ava.Utilities
Gigabytes = 9, Gigabytes = 9,
Terabytes = 10, Terabytes = 10,
Petabytes = 11, Petabytes = 11,
Exabytes = 12, Exabytes = 12
} }
private const double SizeBase10 = 1000; private const double SizeBase10 = 1000;
@@ -48,22 +50,24 @@ namespace Ryujinx.Ava.Utilities
public static string FormatTimeSpan(TimeSpan? timeSpan) public static string FormatTimeSpan(TimeSpan? timeSpan)
{ {
if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1) if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1)
{ return string.Empty;
// Game was never played
return TimeSpan.Zero.ToString("c", CultureInfo.InvariantCulture);
}
if (timeSpan.Value.TotalDays < 1) if (timeSpan.Value.TotalSeconds < 60)
{ return timeSpan.Value.Humanize(1,
// Game was played for less than a day countEmptyUnits: false,
return timeSpan.Value.ToString("c", CultureInfo.InvariantCulture); maxUnit: TimeUnit.Second,
} minUnit: TimeUnit.Second);
// Game was played for more than a day if (timeSpan.Value.TotalMinutes < 60)
TimeSpan onlyTime = timeSpan.Value.Subtract(TimeSpan.FromDays(timeSpan.Value.Days)); return timeSpan.Value.Humanize(1,
string onlyTimeString = onlyTime.ToString("c", CultureInfo.InvariantCulture); countEmptyUnits: false,
maxUnit: TimeUnit.Minute,
minUnit: TimeUnit.Minute);
return $"{timeSpan.Value.Days}d, {onlyTimeString}"; return timeSpan.Value.Humanize(1,
countEmptyUnits: false,
maxUnit: TimeUnit.Hour,
minUnit: TimeUnit.Hour);
} }
/// <summary> /// <summary>