Initial cleanup

This commit is contained in:
_Neo_ 2025-12-29 12:36:37 +02:00
parent 5a8f6fa46d
commit 3620c76cc1
3 changed files with 126 additions and 166 deletions

View file

@ -8,25 +8,17 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
Margin="0"
MinWidth="500" MinWidth="500"
Padding="0"
mc:Ignorable="d" mc:Ignorable="d"
Focusable="True" Focusable="True"
x:DataType="models:TempProfile"> x:DataType="models:TempProfile">
<Grid Margin="0,10,0,0" ColumnDefinitions="Auto,*" RowDefinitions="*,Auto"> <Grid Margin="0,10,0,0" ColumnDefinitions="Auto,*" RowDefinitions="*,Auto">
<StackPanel <StackPanel Spacing="10">
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Text="{ext:Locale UserProfilesName}" /> <TextBlock Text="{ext:Locale UserProfilesName}" />
<TextBox <TextBox
Name="NameBox" Name="NameBox"
Margin="0,0,0,5" Margin="0,0,0,5"
Width="300" Width="300"
HorizontalAlignment="Stretch"
MaxLength="{Binding MaxProfileNameLength}" MaxLength="{Binding MaxProfileNameLength}"
Watermark="{ext:Locale ProfileNameSelectionWatermark}" Watermark="{ext:Locale ProfileNameSelectionWatermark}"
Text="{Binding Name}" /> Text="{Binding Name}" />
@ -34,16 +26,10 @@
<TextBox <TextBox
Name="IdLabel" Name="IdLabel"
Width="300" Width="300"
HorizontalAlignment="Stretch"
IsReadOnly="True" IsReadOnly="True"
Text="{Binding UserIdString}" /> Text="{Binding UserIdString}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel Grid.Column="1" HorizontalAlignment="Right">
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Orientation="Vertical">
<Border <Border
Name="ImageBox" Name="ImageBox"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}" BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
@ -53,33 +39,27 @@
FontSize="70" FontSize="70"
Width="120" Width="120"
Height="120" Height="120"
Margin="0"
Foreground="{DynamicResource AppListHoverBackgroundColor}" Foreground="{DynamicResource AppListHoverBackgroundColor}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Symbol="Camera" /> Symbol="Camera" />
<Image <Image
Name="ProfileImage" Name="ProfileImage"
Width="120" Width="120"
Height="120" Height="120"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" /> Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
<Border <Border
Margin="2" Margin="2"
Height="27" Height="27"
Width="27" Width="27"
CornerRadius="17" CornerRadius="17"
HorizontalAlignment="Right"
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalAlignment="Right"
Background="{DynamicResource ThemeContentBackgroundColor}"> Background="{DynamicResource ThemeContentBackgroundColor}">
<Button <Button
Name="ProfileImageButton" Name="ProfileImageButton"
MaxHeight="27" MaxHeight="27"
MaxWidth="27" MaxWidth="27"
MinHeight="27"
MinWidth="27" MinWidth="27"
MinHeight="27"
CornerRadius="17" CornerRadius="17"
Padding="0"> Padding="0">
<ui:SymbolIcon Symbol="Edit" /> <ui:SymbolIcon Symbol="Edit" />
@ -102,35 +82,25 @@
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.Row="1" Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
HorizontalAlignment="Left"
Orientation="Horizontal" Orientation="Horizontal"
Margin="0,30,0,0" Margin="0,30,0,0"
Spacing="10"> Spacing="10">
<Button <Button MinWidth="50" Width="50" Click="BackButton_Click">
Width="50"
MinWidth="50"
Click="BackButton_Click">
<ui:SymbolIcon Symbol="Back" /> <ui:SymbolIcon Symbol="Back" />
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.Row="1" Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Orientation="Horizontal" Orientation="Horizontal"
Margin="0,30,0,0" Margin="0,30,0,0"
Spacing="10"> Spacing="10">
<Button <Button Name="DeleteButton" Click="DeleteButton_Click">
Name="DeleteButton"
Click="DeleteButton_Click">
<TextBlock Text="{ext:Locale UserProfilesDelete}" /> <TextBlock Text="{ext:Locale UserProfilesDelete}" />
</Button> </Button>
<Button <Button Name="SaveButton" Click="SaveButton_Click">
Name="SaveButton"
Click="SaveButton_Click">
<TextBlock Text="{ext:Locale Save}" /> <TextBlock Text="{ext:Locale Save}" />
</Button> </Button>
</StackPanel> </StackPanel>

View file

@ -1,6 +1,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -32,84 +33,65 @@ namespace Ryujinx.Ava.UI.Views.User
public UserEditorView() public UserEditorView()
{ {
InitializeComponent(); InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) => AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
} }
private void NavigatedTo(NavigationEventArgs arg) private void NavigatedTo(NavigationEventArgs arg)
{ {
if (Program.PreviewerDetached) if (!Program.PreviewerDetached)
return;
if (arg.NavigationMode == NavigationMode.New)
{ {
switch (arg.NavigationMode) (NavigationDialogHost parent, UserProfile profile, bool isNewUser) =
{ ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
case NavigationMode.New:
(NavigationDialogHost parent, UserProfile profile, bool isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_isNewUser = isNewUser;
_profile = profile;
ViewModel = new TempProfile(_profile);
_tempProfile = ViewModel; // <-- this is critical
_parent = parent; _parent = parent;
_profile = profile;
_isNewUser = isNewUser;
_contentManager = _parent.ContentManager; DataValidationErrors.ClearErrors(NameBox);
ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null; DataValidationErrors.ClearErrors(ImageBox);
ImageBox.Bind(Border.BorderBrushProperty, new DynamicResourceExtension("AppListHoverBackgroundColor"));
break; ViewModel = new TempProfile(_profile);
} _tempProfile = ViewModel;
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " + _contentManager = _parent.ContentManager;
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleNewUser] : UserEditorTitle)}"; ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null;
IdLabel.IsVisible = _profile != null;
IdText.IsVisible = _profile != null;
if (!_isNewUser && IsDeletable)
{
DeleteButton.IsVisible = true;
}
else
{
DeleteButton.IsVisible = false;
}
} }
((ContentDialog)_parent.Parent).Title =
$"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleNewUser] : UserEditorTitle)}";
bool hasProfile = _profile != null;
IdLabel.IsVisible = hasProfile;
IdText.IsVisible = hasProfile;
DeleteButton.IsVisible = !_isNewUser && IsDeletable;
} }
private async void BackButton_Click(object sender, RoutedEventArgs e) private async void BackButton_Click(object sender, RoutedEventArgs e)
{ {
if (_isNewUser) bool hasUnsavedChanges =
_isNewUser
? (ViewModel.Name != string.Empty || ViewModel.Image != null)
: (_profile.Name != ViewModel.Name || _profile.Image != ViewModel.Image);
if (hasUnsavedChanges)
{ {
if (ViewModel.Name != string.Empty || ViewModel.Image != null) bool confirm = await ContentDialogHelper.CreateChoiceDialog(
{ LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
if (await ContentDialogHelper.CreateChoiceDialog( LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle], LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]);
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage])) if (confirm)
{
_parent?.GoBack();
}
}
else
{
_parent?.GoBack(); _parent?.GoBack();
}
} }
else else
{ {
if (_profile.Name != ViewModel.Name || _profile.Image != ViewModel.Image) _parent?.GoBack();
{
if (await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
{
_parent?.GoBack();
}
}
else
{
_parent?.GoBack();
}
} }
} }
@ -122,19 +104,27 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
DataValidationErrors.ClearErrors(NameBox); DataValidationErrors.ClearErrors(NameBox);
DataValidationErrors.ClearErrors(ImageBox); DataValidationErrors.ClearErrors(ImageBox);
ImageBox.Bind(Border.BorderBrushProperty, new DynamicResourceExtension("AppListHoverBackgroundColor"));
if (string.IsNullOrWhiteSpace(ViewModel.Name)) bool nameEmpty = string.IsNullOrWhiteSpace(ViewModel.Name);
bool imageMissing = ViewModel.Image == null;
if (nameEmpty && imageMissing)
{
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
DataValidationErrors.SetError(ImageBox, new DataValidationException(""));
ImageBox.BorderBrush = Brush.Parse("#ff99a4");
return;
}
else if (nameEmpty)
{ {
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError])); DataValidationErrors.SetError(NameBox,new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
return; return;
} }
else if (imageMissing)
if (ViewModel.Image == null)
{ {
DataValidationErrors.SetError(ImageBox, null); DataValidationErrors.SetError(ImageBox, new DataValidationException(""));
ImageBox.BorderBrush = Brushes.Red; ImageBox.BorderBrush = Brush.Parse("#ff99a4");
return; return;
} }
@ -143,6 +133,7 @@ namespace Ryujinx.Ava.UI.Views.User
_profile.Name = ViewModel.Name; _profile.Name = ViewModel.Name;
_profile.Image = ViewModel.Image; _profile.Image = ViewModel.Image;
_profile.UpdateState(); _profile.UpdateState();
_parent.AccountManager.SetUserName(_profile.UserId, _profile.Name); _parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
_parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image); _parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
} }
@ -158,7 +149,7 @@ namespace Ryujinx.Ava.UI.Views.User
_parent?.GoBack(); _parent?.GoBack();
} }
private async void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e) private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
{ {
if (ViewModel.FirmwareFound) if (ViewModel.FirmwareFound)
{ {
@ -168,7 +159,8 @@ namespace Ryujinx.Ava.UI.Views.User
private async void Import_OnClick(object sender, RoutedEventArgs e) private async void Import_OnClick(object sender, RoutedEventArgs e)
{ {
var result = await ((Window)this.GetVisualRoot()!).StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions var window = (Window)this.GetVisualRoot()!;
var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{ {
Title = LocaleManager.Instance[LocaleKeys.LoadSupportedImageFormatDialogTitle], Title = LocaleManager.Instance[LocaleKeys.LoadSupportedImageFormatDialogTitle],
AllowMultiple = false, AllowMultiple = false,
@ -183,10 +175,7 @@ namespace Ryujinx.Ava.UI.Views.User
}, },
}); });
if (result.Count == 0) if (result.Count == 0 || DataContext is not TempProfile temp)
return;
if (DataContext is not TempProfile temp)
return; return;
temp.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath)); temp.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath));
@ -198,7 +187,6 @@ namespace Ryujinx.Ava.UI.Views.User
private static byte[] ProcessProfileImage(byte[] buffer) private static byte[] ProcessProfileImage(byte[] buffer)
{ {
using SKBitmap bitmap = SKBitmap.Decode(buffer); using SKBitmap bitmap = SKBitmap.Decode(buffer);
SKBitmap resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High); SKBitmap resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
using MemoryStream streamJpg = new(); using MemoryStream streamJpg = new();
@ -207,7 +195,6 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
using SKImage image = SKImage.FromBitmap(resizedBitmap); using SKImage image = SKImage.FromBitmap(resizedBitmap);
using SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100); using SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100);
dataJpeg.SaveTo(streamJpg); dataJpeg.SaveTo(streamJpg);
} }

View file

@ -1,4 +1,5 @@
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation; using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
@ -8,6 +9,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using SkiaSharp; using SkiaSharp;
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.User namespace Ryujinx.Ava.UI.Views.User
{ {
@ -15,49 +17,51 @@ namespace Ryujinx.Ava.UI.Views.User
{ {
private NavigationDialogHost _parent; private NavigationDialogHost _parent;
private TempProfile _profile; private TempProfile _profile;
public ContentManager ContentManager { get; private set; }
public UserFirmwareAvatarSelectorView()
{
InitializeComponent();
ViewModel = new UserFirmwareAvatarSelectorViewModel();
DataContext = ViewModel;
AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
}
public UserFirmwareAvatarSelectorView(ContentManager contentManager) public UserFirmwareAvatarSelectorView(ContentManager contentManager)
{ {
ContentManager = contentManager; ContentManager = contentManager;
InitializeComponent(); InitializeComponent();
ViewModel = new UserFirmwareAvatarSelectorViewModel();
DataContext = ViewModel;
AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct); AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
} }
public UserFirmwareAvatarSelectorView()
{
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg) private void NavigatedTo(NavigationEventArgs arg)
{ {
if (Program.PreviewerDetached) if (!Program.PreviewerDetached)
return;
if (arg.NavigationMode != NavigationMode.New)
return;
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
ContentManager = _parent.ContentManager;
((ContentDialog)_parent.Parent).Title =
$"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
$"{LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}";
_ = Task.Run(() =>
{ {
if (arg.NavigationMode == NavigationMode.New) bool found = ContentManager.GetCurrentFirmwareVersion() != null;
Dispatcher.UIThread.Post(() =>
{ {
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter; ViewModel.FirmwareFound = found;
ContentManager = _parent.ContentManager; });
});
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}";
if (Program.PreviewerDetached)
{
ViewModel = new UserFirmwareAvatarSelectorViewModel();
ViewModel.FirmwareFound = ContentManager.GetCurrentFirmwareVersion() != null;
}
DataContext = ViewModel;
}
}
} }
public ContentManager ContentManager { get; private set; }
private void GoBack(object sender, RoutedEventArgs e) private void GoBack(object sender, RoutedEventArgs e)
{ {
_parent.GoBack(); _parent.GoBack();
@ -65,32 +69,31 @@ namespace Ryujinx.Ava.UI.Views.User
private void ChooseButton_OnClick(object sender, RoutedEventArgs e) private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
{ {
if (ViewModel.SelectedImage != null) if (ViewModel.SelectedImage == null)
return;
using MemoryStream streamJpg = new();
using SKBitmap bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
using SKBitmap newBitmap = new(bitmap.Width, bitmap.Height);
using (SKCanvas canvas = new(newBitmap))
{ {
using MemoryStream streamJpg = new(); canvas.Clear(new SKColor(
using SKBitmap bitmap = SKBitmap.Decode(ViewModel.SelectedImage); ViewModel.BackgroundColor.R,
using SKBitmap newBitmap = new(bitmap.Width, bitmap.Height); ViewModel.BackgroundColor.G,
ViewModel.BackgroundColor.B,
using (SKCanvas canvas = new(newBitmap)) ViewModel.BackgroundColor.A));
{ canvas.DrawBitmap(bitmap, 0, 0);
canvas.Clear(new SKColor(
ViewModel.BackgroundColor.R,
ViewModel.BackgroundColor.G,
ViewModel.BackgroundColor.B,
ViewModel.BackgroundColor.A));
canvas.DrawBitmap(bitmap, 0, 0);
}
using (SKImage image = SKImage.FromBitmap(newBitmap))
using (SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
{
dataJpeg.SaveTo(streamJpg);
}
_profile.Image = streamJpg.ToArray();
_parent.GoBack();
} }
using (SKImage image = SKImage.FromBitmap(newBitmap))
using (SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
{
dataJpeg.SaveTo(streamJpg);
}
_profile.Image = streamJpg.ToArray();
_parent.GoBack();
} }
} }
} }