Merge pull request #7 from GalaxyViewer/dev

 Add Localization, Cleanup
This commit is contained in:
Galaxy Littlepaws
2024-07-16 19:30:45 -04:00
committed by GitHub
36 changed files with 2578 additions and 284 deletions
@@ -4,7 +4,7 @@
<TargetFramework>net8.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ApplicationId>com.CompanyName.GalaxyViewer</ApplicationId>
<ApplicationId>com.GalaxyViewer.GalaxyViewer</ApplicationId>
<ApplicationVersion>1</ApplicationVersion>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<AndroidPackageFormat>apk</AndroidPackageFormat>
+3 -3
View File
@@ -8,11 +8,11 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GalaxyViewer.Desktop", "GalaxyViewer.Desktop\GalaxyViewer.Desktop.csproj", "{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}"
EndProject
# Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GalaxyViewer.Browser", "GalaxyViewer.Browser\GalaxyViewer.Browser.csproj", "{1C1A049E-235C-4CD0-B6FA-D53AC418F4DA}"
EndProject
# EndProject
# Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GalaxyViewer.iOS", "GalaxyViewer.iOS\GalaxyViewer.iOS.csproj", "{EBD9022F-BC83-4846-9A11-6F7F3772DC64}"
# EndProject
# Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GalaxyViewer.Android", "GalaxyViewer.Android\GalaxyViewer.Android.csproj", "{7AD1DAC8-7FBE-49D5-8614-7321233DB82E}"
# EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GalaxyViewer.Android", "GalaxyViewer.Android\GalaxyViewer.Android.csproj", "{7AD1DAC8-7FBE-49D5-8614-7321233DB82E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3DA99C4E-89E3-4049-9C22-0A7EC60D83D8}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
+2
View File
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=metaverse/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+2
View File
@@ -10,6 +10,8 @@
</Application.DataTemplates>
<Application.Styles>
<StyleInclude Source="avares://GalaxyViewer/Styles.axaml"></StyleInclude>
<StyleInclude Source="avares://AvaloniaInside.Shell/Default.axaml"></StyleInclude>
<FluentTheme />
</Application.Styles>
</Application>
+20 -15
View File
@@ -1,19 +1,21 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Serilog;
using GalaxyViewer.ViewModels;
using GalaxyViewer.Views;
using GalaxyViewer.Services;
using Serilog;
namespace GalaxyViewer;
public partial class App : Application
public class App : Application
{
private readonly PreferencesManager _preferencesManager = new PreferencesManager();
public App()
{
// Initialize Serilog here
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("logs/error.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
@@ -26,19 +28,22 @@ public partial class App : Application
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
_preferencesManager.LoadPreferencesAsync();
switch (ApplicationLifetime)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainViewModel()
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = new MainViewModel()
};
case IClassicDesktopStyleApplicationLifetime desktop:
desktop.MainWindow = new MainWindow
{
DataContext = new MainViewModel()
};
break;
case ISingleViewApplicationLifetime singleViewPlatform:
singleViewPlatform.MainView = new MainView
{
DataContext = new MainViewModel()
};
break;
}
base.OnFrameworkInitializationCompleted();
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@@ -0,0 +1,31 @@
using System.ComponentModel;
using System.Globalization;
using System.Resources;
namespace GalaxyViewer.Assets.Localization
{
public class LocalizationManager : INotifyPropertyChanged
{
private readonly ResourceManager _resourceManager = new(typeof(Strings));
private CultureInfo _currentCulture = CultureInfo.CurrentCulture;
public event PropertyChangedEventHandler? PropertyChanged;
public string GetString(string name)
{
return _resourceManager.GetString(name, _currentCulture) ?? $"[{name}]";
}
public void SetCulture(string cultureCode)
{
var newCulture = new CultureInfo(cultureCode);
if (_currentCulture.Name != newCulture.Name)
{
_currentCulture = newCulture;
CultureInfo.CurrentCulture = newCulture;
CultureInfo.CurrentUICulture = newCulture;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
}
}
}
+62
View File
@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GalaxyViewer.Assets.Localization {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Strings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GalaxyViewer.Assets.Localization.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}
+107
View File
@@ -0,0 +1,107 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GalaxyViewer.Assets.Localization {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Strings_en_US {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings_en_US() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GalaxyViewer.Assets.Localization.Strings.en-US", typeof(Strings_en_US).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Login.
/// </summary>
internal static string LoginScreenLoginButton {
get {
return ResourceManager.GetString("LoginScreenLoginButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password.
/// </summary>
internal static string LoginScreenPassword {
get {
return ResourceManager.GetString("LoginScreenPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Register.
/// </summary>
internal static string LoginScreenRegisterButton {
get {
return ResourceManager.GetString("LoginScreenRegisterButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login.
/// </summary>
internal static string LoginScreenTitle {
get {
return ResourceManager.GetString("LoginScreenTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Username.
/// </summary>
internal static string LoginScreenUsername {
get {
return ResourceManager.GetString("LoginScreenUsername", resourceCulture);
}
}
}
}
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<!-- Add your English strings here -->
<!-- General Stuff -->
<data name="ViewerName" xml:space="preserve">
<value>GalaxyViewer</value>
</data>
<data name="Version" xml:space="preserve">
<value>0.1</value>
</data>
<data name="WelcomeMessage" xml:space="preserve">
<value>Welcome to GalaxyViewer 🌌</value>
</data>
<data name="Error" xml:space="preserve">
<value>Error</value>
</data>
<data name="Ok" xml:space="preserve">
<value>Ok</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<!-- Login Screen -->
<data name="LoginScreenTitle" xml:space="preserve">
<value>Login</value>
</data>
<data name="LoginScreenUsername" xml:space="preserve">
<value>Username</value>
</data>
<data name="LoginScreenPassword" xml:space="preserve">
<value>Password</value>
</data>
<data name="LoginLocation" xml:space="preserve">
<value>Login Location</value>
</data>
<data name="LoginScreenGrid" xml:space="preserve">
<value>Grid</value>
</data>
<data name="LoginScreenLoginButton" xml:space="preserve">
<value>Login</value>
</data>
<data name="LoginSuceess" xml:space="preserve">
<value>Login successful</value>
</data>
<data name="LoginScreenWrongCredentials" xml:space="preserve">
<value>Wrong username or password</value>
</data>
<!-- Preferences Screen -->
<data name="PreferencesTitle" xml:space="preserve">
<value>Preferences</value>
</data>
<data name="PreferencesLanguage" xml:space="preserve">
<value>Language</value>
</data>
<data name="PreferencesLoginLocation" xml:space="preserve">
<value>Login Location</value>
</data>
<data name="PreferencesTheme" xml:space="preserve">
<value>Theme</value>
</data>
<data name="PreferencesFont" xml:space="preserve">
<value>Font</value>
</data>
<data name="PreferencesSaveButton" xml:space="preserve">
<value>Save Preferences</value>
</data>
</root>
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This is in English (United States) language, as the default. It is in case there is no other language file available.
-->
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!-- General Stuff -->
<data name="ViewerName" xml:space="preserve">
<value>GalaxyViewer</value>
</data>
<data name="Version" xml:space="preserve">
<value>0.1</value>
</data>
<data name="WelcomeMessage" xml:space="preserve">
<value>Welcome to GalaxyViewer 🌌</value>
</data>
<data name="Error" xml:space="preserve">
<value>Error</value>
</data>
<data name="Ok" xml:space="preserve">
<value>Ok</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<!-- Login Screen -->
<data name="LoginScreenTitle" xml:space="preserve">
<value>Login</value>
</data>
<data name="LoginScreenUsername" xml:space="preserve">
<value>Username</value>
</data>
<data name="LoginScreenPassword" xml:space="preserve">
<value>Password</value>
</data>
<data name="LoginLocation" xml:space="preserve">
<value>Login Location</value>
</data>
<data name="LoginScreenGrid" xml:space="preserve">
<value>Grid</value>
</data>
<data name="LoginScreenLoginButton" xml:space="preserve">
<value>Login</value>
</data>
<data name="LoginSuceess" xml:space="preserve">
<value>Login successful</value>
</data>
<data name="LoginScreenWrongCredentials" xml:space="preserve">
<value>Wrong username or password</value>
</data>
<!-- Preferences Screen -->
<data name="PreferencesTitle" xml:space="preserve">
<value>Preferences</value>
</data>
<data name="PreferencesLanguage" xml:space="preserve">
<value>Language</value>
</data>
<data name="PreferencesLoginLocation" xml:space="preserve">
<value>Login Location</value>
</data>
<data name="PreferencesTheme" xml:space="preserve">
<value>Theme</value>
</data>
<data name="PreferencesFont" xml:space="preserve">
<value>Font</value>
</data>
<data name="PreferencesSaveButton" xml:space="preserve">
<value>Save Preferences</value>
</data>
</root>
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferencesModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Theme>Default</Theme>
<LoginLocation>Home</LoginLocation>
<Font>Atkinson Hyperlegible</Font>
<Language>en-US</Language>
<LastSavedEpoch>1721172337</LastSavedEpoch>
</PreferencesModel>
@@ -0,0 +1,21 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using GalaxyViewer.Assets.Localization;
namespace GalaxyViewer.Converters;
public class LocalizedStringConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (parameter is string key) return new LocalizationManager().GetString(key);
return "Key not found for " + value;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
// Change to en-US if the language is not found
return value;
}
}
+54 -13
View File
@@ -3,29 +3,70 @@
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<!-- Local NuGet package sources here -->
<RestoreSources>$(RestoreSources);./Libs;</RestoreSources>
</PropertyGroup>
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<AvaloniaResource Include="Assets\**"/>
<None Update="Assets\galaxy.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="Assets\Fonts\*.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
<Watch Include="**\*.axaml"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)"/>
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)"/>
<!-- Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration. -->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)"/>
<!-- More things will go here -->
<PackageReference Include="LibreMetaverse" Version="2.0.10.*" />
<PackageReference Include="AvaloniaInside.Shell" Version="1.1.3"/>
<PackageReference Include="LibreMetaverse" Version="2.0.11.636"/>
</ItemGroup>
<ItemGroup>
<!-- Logging -->
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.5.1"/>
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0"/>
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Assets\Localization\Strings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Assets\Localization\Strings.en-US.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Strings.en.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Assets\Localization\Strings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Strings.resx</DependentUpon>
</Compile>
<Compile Update="Assets\Localization\Strings.en-US.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Strings.en-US.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Libs\"/>
</ItemGroup>
</Project>
+12 -51
View File
@@ -1,61 +1,22 @@
using System;
using System.Collections.Generic;
namespace GalaxyViewer.Models
{
public enum ThemeOptions
{
Light,
Dark,
System
}
public enum LoginLocationOptions
{
Home,
LastLocation
}
[Serializable]
public class PreferencesModel
{
private string _theme = Enum.TryParse(typeof(ThemeOptions), "System", out _) ? "System" : "Light";
private string _loginLocation = Enum.TryParse(typeof(LoginLocationOptions), "LastLocation", out _) ? "LastLocation" : "Home";
public long LastSavedEpoch { get; set; } // Hidden from UI, but stored
public List<string> ThemeOptions { get; set; } = ["Light", "Dark", "Default"];
public List<string> LoginLocationOptions { get; set; } = ["Home", "Last Location"];
public List<string> FontOptions { get; set; } =
["Inter", "Atkinson Hyperlegible"];
public List<string> LanguageOptions { get; set; } = ["en-US"];
public string Theme
{
get => _theme;
set
{
if (IsValidTheme(value))
{
_theme = value;
}
else
{
throw new ArgumentException($"Invalid theme value: {value}");
}
}
}
public string LoginLocation
{
get => _loginLocation;
set
{
if (IsValidLoginLocation(value))
{
_loginLocation = value;
}
else
{
throw new ArgumentException($"Invalid login location value: {value}");
}
}
}
// Assuming ThemeOptions and LoginLocationOptions are enums or similar
private bool IsValidTheme(string theme) => Enum.TryParse(typeof(ThemeOptions), theme, out _);
private bool IsValidLoginLocation(string location) => Enum.TryParse(typeof(LoginLocationOptions), location, out _);
// Default values
public string Theme { get; set; } = "Default";
public string LoginLocation { get; set; } = "Home";
public string Font { get; set; } = "Atkinson Hyperlegible";
public string Language { get; set; } = "en-US";
public long LastSavedEpoch { get; set; }
}
}
+18 -20
View File
@@ -10,30 +10,28 @@ namespace GalaxyViewer.Services
{
public List<Grid> ParseGridsFromXml()
{
string xmlFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "grids.xml");
var xdoc = XDocument.Load(xmlFilePath);
var xmlFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "grids.xml");
var xDoc = XDocument.Load(xmlFilePath);
var grids = new List<Grid>();
if (xdoc.Root != null)
if (xDoc.Root == null) return grids;
foreach (var gridElement in xDoc.Root.Elements("grid"))
{
foreach (var gridElement in xdoc.Root.Elements("grid"))
{
var grid = new Grid(
gridElement.Element("gridnick")?.Value ?? string.Empty,
gridElement.Element("gridname")?.Value ?? string.Empty,
gridElement.Element("platform")?.Value ?? string.Empty,
gridElement.Element("loginuri")?.Value ?? string.Empty,
gridElement.Element("loginpage")?.Value ?? string.Empty,
gridElement.Element("helperuri")?.Value ?? string.Empty,
gridElement.Element("website")?.Value ?? string.Empty,
gridElement.Element("support")?.Value ?? string.Empty,
gridElement.Element("register")?.Value ?? string.Empty,
gridElement.Element("password")?.Value ?? string.Empty,
gridElement.Element("version")?.Value ?? string.Empty
);
var grid = new Grid(
gridElement.Element("gridnick")?.Value ?? string.Empty,
gridElement.Element("gridname")?.Value ?? string.Empty,
gridElement.Element("platform")?.Value ?? string.Empty,
gridElement.Element("loginuri")?.Value ?? string.Empty,
gridElement.Element("loginpage")?.Value ?? string.Empty,
gridElement.Element("helperuri")?.Value ?? string.Empty,
gridElement.Element("website")?.Value ?? string.Empty,
gridElement.Element("support")?.Value ?? string.Empty,
gridElement.Element("register")?.Value ?? string.Empty,
gridElement.Element("password")?.Value ?? string.Empty,
gridElement.Element("version")?.Value ?? string.Empty
);
grids.Add(grid);
}
grids.Add(grid);
}
return grids;
@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Xml.Serialization;
using GalaxyViewer.Models;
using Serilog;
namespace GalaxyViewer.Services;
public class PreferencesManager
{
private readonly string _preferencesFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer", "preferences.xml");
private bool _preferencesLoaded;
public PreferencesManager()
{
EnsurePreferencesDirectory();
}
private void EnsurePreferencesDirectory()
{
var directoryPath = Path.GetDirectoryName(_preferencesFilePath);
if (!string.IsNullOrEmpty(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
}
public Task<PreferencesModel> LoadPreferencesAsync()
{
if (_preferencesLoaded)
{
return Task.FromResult(new PreferencesModel());
}
try
{
if (!File.Exists(_preferencesFilePath))
{
_preferencesLoaded = true;
return Task.FromResult(new PreferencesModel());
}
using var stream = new FileStream(_preferencesFilePath, FileMode.Open, FileAccess.Read);
var serializer = new XmlSerializer(typeof(PreferencesModel));
var preferences = (PreferencesModel)serializer.Deserialize(stream)!;
_preferencesLoaded = true;
return Task.FromResult(preferences);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to load preferences");
_preferencesLoaded = true;
return Task.FromResult(new PreferencesModel());
}
}
public async Task SavePreferencesAsync(PreferencesModel preferences)
{
try
{
await using var stream = new FileStream(_preferencesFilePath, FileMode.Create, FileAccess.Write);
var serializer = new XmlSerializer(typeof(PreferencesModel));
serializer.Serialize(stream, preferences);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to save preferences");
}
}
}
+6
View File
@@ -0,0 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TextBlock">
<Setter Property="FontFamily" Value="avares://GalaxyViewer/Assets/Fonts/#Noto Emoji, avar://GalaxyViewer/Assets/Fonts/#Atkinson Hyperlegible" />
</Style>
</Styles>
+67
View File
@@ -0,0 +1,67 @@
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using ReactiveUI;
using System.ComponentModel;
using GalaxyViewer.Assets.Localization;
using Serilog;
using OpenMetaverse;
namespace GalaxyViewer.ViewModels;
public abstract partial class LoginViewModel : ReactiveObject
{
private string _username;
private string _password;
private readonly GridClient _client = new GridClient();
private readonly LocalizationManager _localizationManager;
protected LoginViewModel(string username, string password,
LocalizationManager localizationManager)
{
_username = username;
_password = password;
_localizationManager = localizationManager;
ReactiveCommand.Create(TryLoginAsync);
}
public string Username
{
get => _username;
set => this.RaiseAndSetIfChanged(ref _username, value);
}
public string Password
{
get => _password;
set => this.RaiseAndSetIfChanged(ref _password, value);
}
private async void TryLoginAsync()
{
/*var loginParams = new LoginParams(
_client,
MyRegex().Split(Username.Trim())[0], // firstName
MyRegex().Split(Username.Trim()).Length > 1
? MyRegex().Split(Username.Trim())[1]
: "Resident", // lastName
Password,
ViewerName,
ViewerVersion,
DetermineGridUrl())
{
LoginLocation = DetermineStartLocation(),
AgreeToTos = true // Assuming you have a way to get this value
};
var loginSuccess = await Task.Run(() => _client.Network.Login(loginParams));
if (loginSuccess)
{
Log.Information(LoginSuccess);
}
else
{
Log.Logger.Error(LoginFailed);
}*/
}
}
+64 -31
View File
@@ -1,15 +1,17 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using GalaxyViewer.Views;
using ReactiveUI;
using System.Reactive;
using System.Threading.Tasks;
using OpenMetaverse;
using System.IO;
using System.Windows.Input;
using ReactiveUI;
using Serilog;
using Avalonia;
namespace GalaxyViewer.ViewModels
{
@@ -18,13 +20,39 @@ namespace GalaxyViewer.ViewModels
private UserControl? _currentView;
private bool _isLoggedIn;
private GridClient _client = new GridClient();
private readonly GridClient _client = new();
// Define properties for Username, Password, LoginLocation, and Grid.
public string? Username { get; set; }
public string? Password { get; set; }
public string? LoginLocation { get; set; }
public string? Grid { get; set; }
private string? _username;
public string? Username
{
get => _username;
set => this.RaiseAndSetIfChanged(ref _username, value);
}
private string? _password;
public string? Password
{
get => _password;
set => this.RaiseAndSetIfChanged(ref _password, value);
}
private string? _loginLocation;
public string? LoginLocation
{
get => _loginLocation;
set => this.RaiseAndSetIfChanged(ref _loginLocation, value);
}
private string? _grid;
public string? Grid
{
get => _grid;
set => this.RaiseAndSetIfChanged(ref _grid, value);
}
public UserControl? CurrentView
{
@@ -38,13 +66,12 @@ namespace GalaxyViewer.ViewModels
set
{
this.RaiseAndSetIfChanged(ref _isLoggedIn, value);
CurrentView = value ? (UserControl)new LoggedInView() : new LoginView();
CurrentView = value ? new LoggedInView() : new LoginView();
}
}
public ReactiveCommand<Unit, Unit> LogoutCommand { get; }
public ReactiveCommand<Unit, Unit> LoginCommand { get; }
public ReactiveCommand<Unit, Unit> LoginWithCurrentValues { get; }
public ICommand ShowPreferencesCommand { get; }
@@ -57,7 +84,6 @@ namespace GalaxyViewer.ViewModels
LogoutCommand = ReactiveCommand.Create(Logout);
LoginCommand = ReactiveCommand.Create(DisplayLoginView);
LoginWithCurrentValues = ReactiveCommand.CreateFromTask(Login);
ShowPreferencesCommand = ReactiveCommand.Create(ShowPreferences);
@@ -76,23 +102,24 @@ namespace GalaxyViewer.ViewModels
CurrentView = new LoginView();
}
public async Task Login()
public async Task Login(string username, string password)
{
try
{
// Validate properties before using them
if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
// Handle invalid login parameters
// For example, you might want to show an error message
// or log the invalid login attempt to a file
File.AppendAllText("error.log", "Invalid login parameters for user: " + Username);
await File.AppendAllTextAsync("error.log",
"Invalid login parameters for user: " + username);
return;
}
string userAgent = "GalaxyViewer/0.1.0";
LoginParams libreMetaverseLoginParams = _client.Network.DefaultLoginParams(Username, Password, userAgent, LoginLocation, Grid);
const string userAgent = "GalaxyViewer/0.1.0";
var libreMetaverseLoginParams =
_client.Network.DefaultLoginParams(username, password, userAgent, LoginLocation,
Grid);
bool loginSuccess = await Task.Run(() => _client.Network.Login(libreMetaverseLoginParams));
var loginSuccess =
await Task.Run(() => _client.Network.Login(libreMetaverseLoginParams));
if (loginSuccess)
{
@@ -100,27 +127,33 @@ namespace GalaxyViewer.ViewModels
}
else
{
// Handle failed login
Log.Error("Failed to login user: {Username}", Username);
Log.Error("Failed to login user: {Username}", username);
}
}
catch (Exception ex)
{
// Handle any exceptions that occur during login
Log.Error(ex, "An error occurred while logging in user: {Username}", Username);
Log.Error(ex, "An error occurred while logging in user: {Username}", username);
}
}
private void ShowPreferences()
{
CurrentView = new PreferencesView
#if ANDROID
CurrentView = new PreferencesView();
#else
var preferencesWindow = new PreferencesWindow
{
DataContext = new PreferencesViewModel()
};
preferencesWindow.Show();
#endif
}
private void ExitApplication()
{
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime
desktopLifetime)
{
desktopLifetime.Shutdown();
}
+179 -78
View File
@@ -1,123 +1,224 @@
using GalaxyViewer.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reactive;
using System.Threading.Tasks;
using System.Xml.Serialization;
using GalaxyViewer.Models;
using GalaxyViewer.Services;
using ReactiveUI;
using System.Reactive;
using Serilog;
namespace GalaxyViewer.ViewModels
{
public class PreferencesViewModel : ReactiveObject
public sealed class PreferencesViewModel : INotifyPropertyChanged
{
public IEnumerable<string> ThemeOptions => Enum.GetNames(typeof(ThemeOptions));
private PreferencesModel _preferences = new();
private readonly PreferencesManager _preferencesManager = new();
public IEnumerable<string> LoginLocationOptions => Enum.GetNames(typeof(LoginLocationOptions));
private string _selectedLanguage = "en-US";
private string _selectedTheme = "Default";
private string _selectedFont = "Atkinson Hyperlegible";
private string _selectedLoginLocation = "Home";
public IEnumerable<string> LanguageOptions => _preferences.LanguageOptions;
public string SelectedLanguage
{
get => _selectedLanguage;
set
{
if (_selectedLanguage == value) return;
_selectedLanguage = value;
OnPropertyChanged(nameof(SelectedLanguage));
_preferences.Language = value;
ChangeCulture(value);
}
}
public IEnumerable<string> ThemeOptions => _preferences.ThemeOptions;
public string SelectedTheme
{
get => _selectedTheme;
set
{
if (_selectedTheme == value) return;
_selectedTheme = value;
OnPropertyChanged(nameof(SelectedTheme));
_preferences.Theme = value;
}
}
public IEnumerable<string> FontOptions => _preferences.FontOptions;
public string SelectedFont
{
get => _selectedFont;
set
{
if (_selectedFont == value) return;
_selectedFont = value;
OnPropertyChanged(nameof(SelectedFont));
_preferences.Font = value;
}
}
public IEnumerable<string> LoginLocationOptions => _preferences.LoginLocationOptions;
public string SelectedLoginLocation
{
get => _selectedLoginLocation;
set
{
if (_selectedLoginLocation == value) return;
_selectedLoginLocation = value;
OnPropertyChanged(nameof(SelectedLoginLocation));
_preferences.LoginLocation = value;
}
}
private static void ChangeCulture(string cultureName)
{
CultureInfo.CurrentCulture = new CultureInfo(cultureName);
CultureInfo.CurrentUICulture = new CultureInfo(cultureName);
}
public PreferencesModel Preferences
{
get => _preferences;
set
{
_preferences = value;
OnPropertyChanged(nameof(Preferences));
}
}
private Lazy<PreferencesModel> _lazyPreferences;
private readonly string _preferencesFilePath;
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<Unit, Unit>? SaveCommand { get; private set; }
public PreferencesViewModel()
{
_preferencesFilePath = GetPreferencesFilePath();
_lazyPreferences = new Lazy<PreferencesModel>(LoadOrCreatePreferences);
_preferencesFilePath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"GalaxyViewer", "preferences.xml");
var directoryPath = Path.GetDirectoryName(_preferencesFilePath);
if (directoryPath != null)
{
_ = Directory.CreateDirectory(directoryPath);
}
else
{
Log.Error("Failed to create preferences directory");
}
SaveCommand = ReactiveCommand.CreateFromTask(SavePreferencesAsync);
Task.Run(InitializeAsync).Wait();
}
public static async Task<PreferencesViewModel> CreateAsync()
public async Task InitializeAsync()
{
var viewModel = new PreferencesViewModel();
await viewModel.LoadPreferencesAsync();
return viewModel;
Preferences = await _preferencesManager.LoadPreferencesAsync();
// Update local fields from loaded preferences
_selectedLanguage = Preferences.Language;
_selectedTheme = Preferences.Theme;
_selectedFont = Preferences.Font;
_selectedLoginLocation = Preferences.LoginLocation;
// Notify the UI to update
OnPropertyChanged(nameof(SelectedLanguage));
OnPropertyChanged(nameof(SelectedTheme));
OnPropertyChanged(nameof(SelectedFont));
OnPropertyChanged(nameof(SelectedLoginLocation));
ChangeCulture(
_selectedLanguage);
}
private PreferencesModel Preferences => _lazyPreferences.Value;
public event PropertyChangedEventHandler? PropertyChanged;
private static string GetPreferencesFilePath()
private void OnPropertyChanged(string propertyName)
{
var appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var applicationFolder = Path.Combine(appDataFolder, "GalaxyViewer");
Directory.CreateDirectory(applicationFolder); // CreateDirectory is no-op if exists
return Path.Combine(applicationFolder, "preferences.xml");
}
private PreferencesModel LoadOrCreatePreferences()
{
if (File.Exists(_preferencesFilePath))
{
using var stream = new FileStream(_preferencesFilePath, FileMode.Open);
var serializer = new XmlSerializer(typeof(PreferencesModel));
return serializer.Deserialize(stream) as PreferencesModel ?? new PreferencesModel();
}
return new PreferencesModel
{
Theme = Enum.TryParse(typeof(ThemeOptions), "System", out object themeResult) ? themeResult.ToString() : Models.ThemeOptions.Light.ToString(),
LoginLocation = Enum.TryParse(typeof(LoginLocationOptions), "LastLocation", out object loginLocationResult) ? loginLocationResult.ToString() : Models.LoginLocationOptions.Home.ToString()
};
}
private string _theme;
public string Theme
{
get => Preferences.Theme;
set
{
if (Preferences.Theme != value)
{
Preferences.Theme = value;
this.RaisePropertyChanged(nameof(Theme));
}
}
}
private string _loginLocation;
public string LoginLocation
{
get => Preferences.LoginLocation;
set
{
if (Preferences.LoginLocation != value)
{
Preferences.LoginLocation = value;
this.RaisePropertyChanged(nameof(LoginLocation));
}
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _statusMessage = string.Empty;
public string StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
}
public async Task LoadPreferencesAsync()
{
await Task.Run(() =>
set
{
_lazyPreferences = new Lazy<PreferencesModel>(LoadOrCreatePreferences);
});
_statusMessage = value;
OnPropertyChanged(nameof(StatusMessage));
}
}
public async Task SavePreferencesAsync()
public async Task<PreferencesModel> LoadPreferencesAsync()
{
try
{
// Step 1: Check and copy default preferences if necessary
var defaultPreferencesPath = Path.Combine("Assets", "preferences.xml");
if (!File.Exists(_preferencesFilePath) && File.Exists(defaultPreferencesPath))
{
File.Copy(defaultPreferencesPath, _preferencesFilePath);
}
// Step 2: Open the preferences file
await using var stream = new FileStream(_preferencesFilePath, FileMode.OpenOrCreate,
FileAccess.Read, FileShare.Read);
var serializer = new XmlSerializer(typeof(PreferencesModel));
// Step 3 & 4: Deserialize the XML content
var loadedPreferences = await Task.Run(() =>
{
try
{
var result = serializer.Deserialize(stream);
if (result is PreferencesModel preferences)
{
return preferences;
}
throw new InvalidCastException(
"Deserialized object is not of type PreferencesModel.");
}
catch (Exception ex)
{
// Step 5: Handle deserialization failure
Log.Error(ex, "Failed to deserialize preferences");
StatusMessage = "Failed to load preferences. Using default settings.";
return new PreferencesModel(); // Return default preferences on error
}
});
return loadedPreferences;
}
catch (Exception ex)
{
// Step 6: Handle any other exceptions
Log.Error(ex, "Error loading preferences");
StatusMessage = "Error loading preferences. Using default settings.";
return new PreferencesModel(); // Return default preferences on error
}
}
private async Task SavePreferencesAsync()
{
try
{
Preferences.LastSavedEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
using var stream = new FileStream(_preferencesFilePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true);
var serializer = new XmlSerializer(typeof(PreferencesModel));
await Task.Run(() => serializer.Serialize(stream, Preferences));
StatusMessage = "Preferences saved";
await _preferencesManager.SavePreferencesAsync(Preferences);
StatusMessage = "Preferences saved successfully.";
}
catch (Exception ex)
{
StatusMessage = $"Error saving preferences: {ex.Message}";
Log.Error(ex, "Error saving preferences");
}
}
}
+13
View File
@@ -0,0 +1,13 @@
using Avalonia.Controls;
namespace GalaxyViewer.Views
{
public class BaseWindow : Window
{
protected BaseWindow()
{
Icon = new WindowIcon("Assets/galaxy.ico");
CanResize = true;
}
}
}
+42 -6
View File
@@ -1,9 +1,45 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GalaxyViewer.Views.LoginView">
<StackPanel Margin="20">
<TextBox Name="UsernameBox" Watermark="Username" />
<TextBox Name="PasswordBox" Watermark="Password" PasswordChar="*" />
<Button Content="Login" Margin="0,10,0,0" Click="LoginButton_Click" />
</StackPanel>
x:Class="GalaxyViewer.Views.LoginView"
xmlns:converters="clr-namespace:GalaxyViewer.Converters"
xmlns:viewModels="clr-namespace:GalaxyViewer.ViewModels"
x:DataType="viewModels:LoginViewModel">
<UserControl.Resources>
<converters:LocalizedStringConverter x:Key="LocalizedStringConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- WebView or Image goes here -->
<Image Grid.Row="0" Source="/Assets/Images/banner.jpg" Stretch="Fill" />
<!-- Login Options -->
<Grid Grid.Row="1" Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="240" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="240" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenUsername}"
VerticalAlignment="Center" />
<TextBox Grid.Column="1" Name="UsernameBox"
Watermark="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenUsername}"
Margin="5,0" />
<Label Grid.Column="2"
Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenPassword}"
VerticalAlignment="Center" Margin="5,0" />
<TextBox Grid.Column="3" Name="PasswordBox"
Watermark="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenPassword}"
PasswordChar="*" Margin="5,0" />
<Button Grid.Column="4"
Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenLoginButton}"
Margin="5,0" VerticalAlignment="Center" />
</Grid>
<!-- TODO: Add in the Grid selector -->
<!-- TODO: Add in the Location selector -->
</Grid>
</UserControl>
+33 -3
View File
@@ -1,6 +1,10 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using GalaxyViewer.Assets.Localization;
using GalaxyViewer.ViewModels;
using MsBox.Avalonia;
using MsBox.Avalonia.Windows;
namespace GalaxyViewer.Views
{
@@ -16,9 +20,35 @@ namespace GalaxyViewer.Views
AvaloniaXamlLoader.Load(this);
}
private void LoginButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
private async void LoginButton_Click(object sender, RoutedEventArgs e)
{
// Your event handling code here, which attempts the Login method
// We run TryLoginAsync() and await it to ensure that the login process is completed before proceeding
// TODO: Implement TryLoginAsync()
/*
* The following code is a placeholder for the TryLoginAsync() method.
* This method should be implemented in the LoginViewModel class.
* The method should handle the login process and return a boolean value indicating whether the login was successful.
* The method should also handle any errors that may occur during the login process.
*/
/*
if (loginSuccess)
{
// If login is successful, we navigate to the MainView
var mainView = new MainView
{
DataContext = new MainViewModel()
};
}
else
{
// If login is unsuccessful, we show an error message
MsBoxWindow.Show(
// Localize the error message
// TODO: Add error message
);
}
*/
}
}
}
+40 -40
View File
@@ -6,53 +6,53 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="GalaxyViewer.Views.MainView"
x:DataType="vm:MainViewModel">
<Design.DataContext>
<Design.DataContext>
<vm:MainViewModel />
</Design.DataContext>
</Design.DataContext>
<DockPanel LastChildFill="True">
<DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="New Window"/>
<Separator/>
<MenuItem Header="Upload Blinn-Phong Texture" IsEnabled="{Binding IsLoggedIn}"/>
<MenuItem Header="Upload PBR Material" IsEnabled="{Binding IsLoggedIn}"/>
<MenuItem Header="Upload Mesh" IsEnabled="{Binding IsLoggedIn}"/>
<MenuItem Header="Import Object" IsEnabled="{Binding IsLoggedIn}"/>
<MenuItem Header="Script Editor" IsEnabled="{Binding IsLoggedIn}"/>
<Separator/>
<MenuItem Header="Login" Command="{Binding LoginCommand}" IsEnabled="{Binding !IsLoggedIn}"/>
<MenuItem Header="Logout" Command="{Binding LogoutCommand}" IsEnabled="{Binding IsLoggedIn}"/>
<MenuItem Header="Relog" IsEnabled="{Binding !IsLoggedIn}"/>
<MenuItem Header="Preferences" Command="{Binding ShowPreferencesCommand}"/>
<Separator/>
<MenuItem Header="Exit" Command="{Binding ExitCommand}"/>
</MenuItem>
<MenuItem Header="File">
<MenuItem Header="New Window" />
<Separator />
<MenuItem Header="Upload Blinn-Phong Texture" IsEnabled="{Binding IsLoggedIn}" />
<MenuItem Header="Upload PBR Material" IsEnabled="{Binding IsLoggedIn}" />
<MenuItem Header="Upload Mesh" IsEnabled="{Binding IsLoggedIn}" />
<MenuItem Header="Import Object" IsEnabled="{Binding IsLoggedIn}" />
<MenuItem Header="Script Editor" IsEnabled="{Binding IsLoggedIn}" />
<Separator />
<MenuItem Header="Login" Command="{Binding LoginCommand}" IsEnabled="{Binding !IsLoggedIn}" />
<MenuItem Header="Logout" Command="{Binding LogoutCommand}" IsEnabled="{Binding IsLoggedIn}" />
<MenuItem Header="Relog" IsEnabled="{Binding !IsLoggedIn}" />
<MenuItem Header="Preferences" Command="{Binding ShowPreferencesCommand}" />
<Separator />
<MenuItem Header="Exit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="World" IsEnabled="{Binding IsLoggedIn}">
<MenuItem Header="Create new Landmark Here"/>
<MenuItem Header="Landmarks"/>
<MenuItem Header="Teleport History"/>
<MenuItem Header="Favorites"/>
<MenuItem Header="Set Home to Here"/>
<MenuItem Header="Teleport Home"/>
<Separator/>
<MenuItem Header="About Land"/>
<MenuItem Header="About Region"/>
<MenuItem Header="World Map"/>
<MenuItem Header="Mini-Map"/>
<MenuItem Header="People Nearby"/>
<MenuItem Header="Objects Nearby"/>
<MenuItem Header="Create new Landmark Here" />
<MenuItem Header="Landmarks" />
<MenuItem Header="Teleport History" />
<MenuItem Header="Favorites" />
<MenuItem Header="Set Home to Here" />
<MenuItem Header="Teleport Home" />
<Separator />
<MenuItem Header="About Land" />
<MenuItem Header="About Region" />
<MenuItem Header="World Map" />
<MenuItem Header="Mini-Map" />
<MenuItem Header="People Nearby" />
<MenuItem Header="Objects Nearby" />
</MenuItem>
<MenuItem Header="Community" IsEnabled="{Binding IsLoggedIn}">
<MenuItem Header="Friends"/>
<MenuItem Header="Groups"/>
<MenuItem Header="Events"/>
<MenuItem Header="Marketplace"/>
<MenuItem Header="Search"/>
<MenuItem Header="Friends" />
<MenuItem Header="Groups" />
<MenuItem Header="Events" />
<MenuItem Header="Marketplace" />
<MenuItem Header="Search" />
</MenuItem>
<!-- Add more menu items as needed -->
</Menu>
<ContentControl Content="{Binding CurrentView}"/>
</DockPanel>
</UserControl>
<ContentControl Content="{Binding CurrentView}" />
</DockPanel>
</UserControl>
+6
View File
@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace GalaxyViewer.Views;
@@ -8,4 +9,9 @@ public partial class MainView : UserControl
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
+10 -12
View File
@@ -1,12 +1,10 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:GalaxyViewer.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:GalaxyViewer.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="GalaxyViewer.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="GalaxyViewer">
<views:MainView />
</Window>
<views:BaseWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:GalaxyViewer.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="GalaxyViewer.Views.MainWindow"
Title="GalaxyViewer">
<views:MainView />
</views:BaseWindow>
+16 -2
View File
@@ -1,11 +1,25 @@
using Avalonia.Controls;
using Avalonia;
using Avalonia.Markup.Xaml;
namespace GalaxyViewer.Views;
public partial class MainWindow : Window
public partial class MainWindow : BaseWindow
{
public MainWindow()
{
InitializeComponent();
AttachDevToolsConditional();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void AttachDevToolsConditional()
{
#if DEBUG
this.AttachDevTools();
#endif
}
}
+20 -5
View File
@@ -2,16 +2,31 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="clr-namespace:GalaxyViewer.Converters"
xmlns:vm="clr-namespace:GalaxyViewer.ViewModels"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"
x:Class="GalaxyViewer.Views.PreferencesView"
x:DataType="vm:PreferencesViewModel">
<UserControl.Resources>
<converters:LocalizedStringConverter x:Key="LocalizedStringConverter" />
</UserControl.Resources>
<StackPanel Margin="20">
<TextBlock Text="Preferences" />
<ComboBox ItemsSource="{Binding ThemeOptions}" SelectedItem="{Binding Theme}" />
<ComboBox ItemsSource="{Binding LoginLocationOptions}" SelectedItem="{Binding LoginLocation}" />
<!-- Add more UI elements as needed -->
<Button Content="Save Preferences" Command="{Binding SaveCommand}" HorizontalAlignment="Left" />
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesTitle}"
VerticalAlignment="Center" />
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesTheme}"
VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding ThemeOptions}" SelectedItem="{Binding SelectedTheme}" />
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesLoginLocation}"
VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding LoginLocationOptions}" SelectedItem="{Binding SelectedLoginLocation}" />
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesLanguage}"
VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding LanguageOptions}" SelectedItem="{Binding SelectedLanguage}" />
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesFont}"
VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding FontOptions}" SelectedItem="{Binding SelectedFont}" />
<Button Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesSaveButton}"
Command="{Binding SaveCommand}" HorizontalAlignment="Left" />
<TextBlock Text="{Binding StatusMessage}" />
</StackPanel>
</UserControl>
+14 -4
View File
@@ -1,4 +1,4 @@
using Avalonia;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using GalaxyViewer.ViewModels;
@@ -10,12 +10,22 @@ namespace GalaxyViewer.Views
public PreferencesView()
{
InitializeComponent();
this.DataContext = new PreferencesViewModel();
LoadViewModelAsync();
}
private void InitializeComponent()
private async void LoadViewModelAsync()
{
AvaloniaXamlLoader.Load(this);
var viewModel = new PreferencesViewModel();
await viewModel.InitializeAsync();
DataContext = viewModel;
}
private static async Task<PreferencesViewModel> CreateAsync()
{
var viewModel = new PreferencesViewModel();
await viewModel
.LoadPreferencesAsync();
return viewModel;
}
}
}
@@ -0,0 +1,8 @@
<views:BaseWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:GalaxyViewer.Views"
x:Class="GalaxyViewer.Views.PreferencesWindow"
Title="Preferences"
Width="450" Height="350">
<views:PreferencesView/>
</views:BaseWindow>
@@ -0,0 +1,20 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using GalaxyViewer.ViewModels;
namespace GalaxyViewer.Views
{
public partial class PreferencesWindow : Window
{
public PreferencesWindow()
{
InitializeComponent();
DataContext = new PreferencesViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
File diff suppressed because it is too large Load Diff