diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..4c9295f
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,15 @@
+# These are supported funding model platforms
+
+github: GalaxyViewer
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+polar: galaxylittlepaws
+buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
+thanks_dev: # Replace with a single thanks.dev username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f33b7c5
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+## [Unreleased] 0.1.0
+- Added Login functionality
+- Added Preferences functionality
+- Added ability to choose language from a list of predefined languages
+- Added ability to choose login location from a list of predefined locations
+- Added ability to choose theme from a list of predefined themes
+- Added ability to choose font from a list of predefined fonts
diff --git a/Directory.Build.props b/Directory.Build.props
index 17bc977..37fd070 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,9 +1,10 @@
-
- 0.1.0
- GalaxyViewer
- Galaxy Littlepaws
- enable
- 11.0.10
-
+
+ 0.1.0
+ GalaxyViewer
+ GalaxyViewer is a cross-platform viewer for Second Life and NGC OpenSimulator.
+ Galaxy Littlepaws
+ enable
+ 11.1.0
+
\ No newline at end of file
diff --git a/GalaxyViewer.Android/GalaxyViewer.Android.csproj b/GalaxyViewer.Android/GalaxyViewer.Android.csproj
index f03c260..612fd18 100644
--- a/GalaxyViewer.Android/GalaxyViewer.Android.csproj
+++ b/GalaxyViewer.Android/GalaxyViewer.Android.csproj
@@ -1,28 +1,24 @@
-
- Exe
- net8.0-android
- 21
- enable
- com.GalaxyViewer.GalaxyViewer
- 1
- 1.0
- apk
- false
-
+
+ Exe
+ net8.0-android34.0
+ true
+ 30
+ 24.0
+ enable
+ com.GalaxyViewer.GalaxyViewer
+ 1
+ 1.0
+ apk
+ false
+
-
-
- Resources\drawable\Icon.png
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
diff --git a/GalaxyViewer.Android/GalaxyViewer.Android.iml b/GalaxyViewer.Android/GalaxyViewer.Android.iml
new file mode 100644
index 0000000..9be9bdb
--- /dev/null
+++ b/GalaxyViewer.Android/GalaxyViewer.Android.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GalaxyViewer.Android/Icon.png b/GalaxyViewer.Android/Icon.png
deleted file mode 100644
index 41a2a61..0000000
Binary files a/GalaxyViewer.Android/Icon.png and /dev/null differ
diff --git a/GalaxyViewer.Android/MainActivity.cs b/GalaxyViewer.Android/MainActivity.cs
index b4fd703..d5bb678 100644
--- a/GalaxyViewer.Android/MainActivity.cs
+++ b/GalaxyViewer.Android/MainActivity.cs
@@ -1,23 +1,74 @@
-using Android.App;
+using Android;
+using Android.App;
using Android.Content.PM;
+using Android.OS;
+using Android.Widget;
+using AndroidX.Core.App;
+using AndroidX.Core.Content;
using Avalonia;
using Avalonia.Android;
using Avalonia.ReactiveUI;
+using GalaxyViewer.Services;
-namespace GalaxyViewer.Android;
-
-[Activity(
- Label = "GalaxyViewer.Android",
- Theme = "@style/MyTheme.NoActionBar",
- Icon = "@drawable/icon",
- MainLauncher = true,
- ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
-public class MainActivity : AvaloniaMainActivity
+namespace GalaxyViewer.Android
{
- protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
+ [Activity(
+ Label = "GalaxyViewer.Android",
+ Theme = "@style/MyTheme.NoActionBar",
+ Icon = "@drawable/icon",
+ MainLauncher = true,
+ ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
+ public class MainActivity : AvaloniaMainActivity
{
- return base.CustomizeAppBuilder(builder)
- .WithInterFont()
- .UseReactiveUI();
+ const int RequestStorageId = 0;
+
+ protected override void OnCreate(Bundle savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
+ {
+ ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.WriteExternalStorage }, RequestStorageId);
+ }
+ else
+ {
+ InitializeLiteDbService();
+ }
+ }
+
+ public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if (requestCode == RequestStorageId)
+ {
+ if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
+ {
+ // Permission granted, proceed with file operations
+ InitializeLiteDbService();
+ }
+ else
+ {
+ // Permission denied, show a message to the user
+ var message = "Permission to create data storage file denied. The application will not be able to function.";
+ var toast = Toast.MakeText(this, message, ToastLength.Long);
+ toast.Show();
+ }
+ }
+ }
+
+ private void InitializeLiteDbService()
+ {
+ // Initialize LiteDbService here
+ var liteDbService = new LiteDbService();
+ // Use liteDbService as needed
+ }
+
+ protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
+ {
+ return base.CustomizeAppBuilder(builder)
+ .WithInterFont()
+ .UseReactiveUI();
+ }
}
-}
+}
\ No newline at end of file
diff --git a/GalaxyViewer.Android/Properties/AndroidManifest.xml b/GalaxyViewer.Android/Properties/AndroidManifest.xml
index f19bba0..e3a0233 100644
--- a/GalaxyViewer.Android/Properties/AndroidManifest.xml
+++ b/GalaxyViewer.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,15 @@
-
-
-
+
+
+
+
+
+
+
+
diff --git a/GalaxyViewer.Android/Resources/drawable/icon.png b/GalaxyViewer.Android/Resources/drawable/icon.png
new file mode 100644
index 0000000..03b9417
Binary files /dev/null and b/GalaxyViewer.Android/Resources/drawable/icon.png differ
diff --git a/GalaxyViewer.Android/Resources/values-night/colors.xml b/GalaxyViewer.Android/Resources/values-night/colors.xml
index 3d47b6f..e727ff0 100644
--- a/GalaxyViewer.Android/Resources/values-night/colors.xml
+++ b/GalaxyViewer.Android/Resources/values-night/colors.xml
@@ -1,4 +1,4 @@
- #212121
+ #0f1729
diff --git a/GalaxyViewer.Android/Resources/values/colors.xml b/GalaxyViewer.Android/Resources/values/colors.xml
index 59279d5..e727ff0 100644
--- a/GalaxyViewer.Android/Resources/values/colors.xml
+++ b/GalaxyViewer.Android/Resources/values/colors.xml
@@ -1,4 +1,4 @@
- #FFFFFF
+ #0f1729
diff --git a/GalaxyViewer.Browser/GalaxyViewer.Browser.csproj b/GalaxyViewer.Browser/GalaxyViewer.Browser.csproj
index ab16d45..ec18777 100644
--- a/GalaxyViewer.Browser/GalaxyViewer.Browser.csproj
+++ b/GalaxyViewer.Browser/GalaxyViewer.Browser.csproj
@@ -1,21 +1,21 @@
Exe
- net8.0-browser
+ net8.0
browser-wasm
- wwwroot\main.js
+ wwwroot/main.js
./_framework
-
+
-
+
-
+
\ No newline at end of file
diff --git a/GalaxyViewer.Desktop/GalaxyViewer.Desktop.csproj b/GalaxyViewer.Desktop/GalaxyViewer.Desktop.csproj
index 081b729..4d7ebe5 100644
--- a/GalaxyViewer.Desktop/GalaxyViewer.Desktop.csproj
+++ b/GalaxyViewer.Desktop/GalaxyViewer.Desktop.csproj
@@ -13,9 +13,9 @@
-
+
-
+
diff --git a/GalaxyViewer.sln.DotSettings b/GalaxyViewer.sln.DotSettings
index 29d1099..317d147 100644
--- a/GalaxyViewer.sln.DotSettings
+++ b/GalaxyViewer.sln.DotSettings
@@ -1,2 +1,5 @@
+ False
+ False
+ True
True
\ No newline at end of file
diff --git a/GalaxyViewer/App.axaml b/GalaxyViewer/App.axaml
index 3e557b5..fb4985b 100644
--- a/GalaxyViewer/App.axaml
+++ b/GalaxyViewer/App.axaml
@@ -2,7 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GalaxyViewer"
xmlns:semi="https://irihi.tech/semi"
- xmlns:u-semi="https://irihi.tech/ursa/themes/semi"
+ xmlns:u-Semi="https://irihi.tech/ursa/themes/semi"
+ xmlns:converters="clr-namespace:GalaxyViewer.Converters"
x:Class="GalaxyViewer.App">
@@ -10,7 +11,7 @@
-
+
@@ -18,6 +19,7 @@
+
\ No newline at end of file
diff --git a/GalaxyViewer/App.axaml.cs b/GalaxyViewer/App.axaml.cs
index 5e32e54..d00e80d 100644
--- a/GalaxyViewer/App.axaml.cs
+++ b/GalaxyViewer/App.axaml.cs
@@ -1,4 +1,6 @@
+using System;
using System.Diagnostics;
+using System.IO;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
@@ -6,96 +8,134 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using GalaxyViewer.Models;
+using GalaxyViewer.Services;
using GalaxyViewer.ViewModels;
using GalaxyViewer.Views;
-using GalaxyViewer.Services;
+using Microsoft.Extensions.DependencyInjection;
using Serilog;
namespace GalaxyViewer;
-public class App : Application
+public class App : Application, IDisposable
{
+ private static IServiceProvider? _serviceProvider;
+ public static PreferencesManager? PreferencesManager { get; private set; }
+
public App()
{
- // Initialize Serilog here
+ ConfigureLogging();
+ }
+
+ private static void ConfigureLogging()
+ {
+ var logFilePath =
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "GalaxyViewer", "logs", "error.log");
+
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
- .WriteTo.File("logs/error.log", rollingInterval: RollingInterval.Day)
+ .WriteTo.File(logFilePath, rollingInterval: RollingInterval.Day)
.CreateLogger();
}
- public static PreferencesManager? PreferencesManager { get; private set; }
+ private static void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton();
+ // Register other services here
+ }
public override void Initialize()
{
- PreferencesManager = new PreferencesManager();
+ var serviceCollection = new ServiceCollection();
+ ConfigureServices(serviceCollection);
+ _serviceProvider = serviceCollection.BuildServiceProvider();
+
+ var liteDbService = _serviceProvider.GetService();
+ if (liteDbService == null)
+ {
+ throw new InvalidOperationException("LiteDbService is not registered.");
+ }
+
+ PreferencesManager = new PreferencesManager(liteDbService);
PreferencesManager.PreferencesChanged += OnPreferencesChanged;
+
AvaloniaXamlLoader.Load(this);
base.Initialize();
}
- public override async void OnFrameworkInitializationCompleted()
+ public override void OnFrameworkInitializationCompleted()
{
- var contentControl = new ContentControl();
- var navigationService = new NavigationService(contentControl);
-
- // Register routes
- navigationService.RegisterRoute("login", typeof(LoginView));
- navigationService.RegisterRoute("debug", typeof(DebugView));
- navigationService.RegisterRoute("preferences", typeof(PreferencesView));
-
- switch (ApplicationLifetime)
+ try
{
- case IClassicDesktopStyleApplicationLifetime desktop:
- desktop.MainWindow = new MainWindow
- {
- DataContext = new MainViewModel(navigationService)
- };
- break;
- case ISingleViewApplicationLifetime singleViewPlatform:
- singleViewPlatform.MainView = new MainView
- {
- DataContext = new MainViewModel(navigationService)
- };
- break;
+ switch (ApplicationLifetime)
+ {
+ case IClassicDesktopStyleApplicationLifetime desktop:
+ Log.Information("Initializing MainWindow for desktop application.");
+ desktop.MainWindow = new MainWindow
+ {
+ DataContext = new MainViewModel()
+ };
+ desktop.MainWindow.Show();
+ break;
+ case ISingleViewApplicationLifetime singleViewPlatform:
+ Log.Information("Initializing MainView for single view application.");
+ singleViewPlatform.MainView = new MainView
+ {
+ DataContext = new MainViewModel()
+ };
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "An error occurred while initializing the main window.");
+ throw; // Optionally rethrow the exception if you want to halt the application
}
-
- Debug.Assert(PreferencesManager != null, nameof(PreferencesManager) + " != null");
- var preferences = await PreferencesManager.LoadPreferencesAsync();
- ApplyPreferences(preferences);
base.OnFrameworkInitializationCompleted();
}
- private async void OnPreferencesChanged(object? sender, PreferencesModel preferences)
+ private void OnPreferencesChanged(object? sender, PreferencesModel preferences)
{
- if (PreferencesManager?.IsLoadingPreferences == true) return;
ApplyPreferences(preferences);
RefreshThemeForAllWindows();
}
private void ApplyPreferences(PreferencesModel preferences)
{
- RequestedThemeVariant = preferences.Theme switch
+ Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
{
- "Light" => ThemeVariant.Light,
- "Dark" => ThemeVariant.Dark,
- _ => ThemeVariant.Default
- };
+ RequestedThemeVariant = preferences.Theme switch
+ {
+ "Light" => ThemeVariant.Light,
+ "Dark" => ThemeVariant.Dark,
+ _ => ThemeVariant.Default
+ };
- // TODO: Apply other preferences
+ // TODO: Apply other preferences
+ });
}
- private void RefreshThemeForAllWindows()
+ private async void RefreshThemeForAllWindows()
{
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktopLifetime)
return;
- foreach (var window in desktopLifetime.Windows)
+
+ await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () =>
{
- if (window is not BaseWindow baseWindow) continue;
- var resultTheme = PreferencesManager?.LoadPreferencesAsync().Result.Theme;
- if (resultTheme != null)
- baseWindow.ApplyTheme(resultTheme);
- }
+ foreach (var window in desktopLifetime.Windows)
+ {
+ if (window is not BaseWindow baseWindow) continue;
+ var resultTheme = (await PreferencesManager?.LoadPreferencesAsync())?.Theme;
+ if (resultTheme != null)
+ baseWindow.ApplyTheme(resultTheme);
+ }
+ });
+ }
+
+ public void Dispose()
+ {
+ //PreferencesManager?.Dispose();
+ (_serviceProvider as IDisposable)?.Dispose();
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Assets/GalaxyViewerLogo.ico b/GalaxyViewer/Assets/GalaxyViewerLogo.ico
new file mode 100644
index 0000000..74a3c6c
Binary files /dev/null and b/GalaxyViewer/Assets/GalaxyViewerLogo.ico differ
diff --git a/GalaxyViewer/Assets/Images/Logo.png b/GalaxyViewer/Assets/Images/Logo.png
new file mode 100644
index 0000000..03b9417
Binary files /dev/null and b/GalaxyViewer/Assets/Images/Logo.png differ
diff --git a/GalaxyViewer/Assets/Images/Logo.svg b/GalaxyViewer/Assets/Images/Logo.svg
new file mode 100644
index 0000000..97b9d96
--- /dev/null
+++ b/GalaxyViewer/Assets/Images/Logo.svg
@@ -0,0 +1,119 @@
+
+
+
+
diff --git a/GalaxyViewer/Assets/Localization/LocalizationManager.cs b/GalaxyViewer/Assets/Localization/LocalizationManager.cs
index b6fc5bd..e38578e 100644
--- a/GalaxyViewer/Assets/Localization/LocalizationManager.cs
+++ b/GalaxyViewer/Assets/Localization/LocalizationManager.cs
@@ -2,30 +2,29 @@
using System.Globalization;
using System.Resources;
-namespace GalaxyViewer.Assets.Localization
+namespace GalaxyViewer.Assets.Localization;
+
+public class LocalizationManager : INotifyPropertyChanged
{
- 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)
{
- private readonly ResourceManager _resourceManager = new(typeof(Strings));
- private CultureInfo _currentCulture = CultureInfo.CurrentCulture;
+ return _resourceManager.GetString(name, _currentCulture) ?? $"[{name}]";
+ }
- public event PropertyChangedEventHandler? PropertyChanged;
-
- public string GetString(string name)
+ public void SetCulture(string cultureCode)
+ {
+ var newCulture = new CultureInfo(cultureCode);
+ if (_currentCulture.Name != newCulture.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));
- }
+ _currentCulture = newCulture;
+ CultureInfo.CurrentCulture = newCulture;
+ CultureInfo.CurrentUICulture = newCulture;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Assets/Localization/Strings.en-US.resx b/GalaxyViewer/Assets/Localization/Strings.en-US.resx
index e8d3471..3d14c34 100644
--- a/GalaxyViewer/Assets/Localization/Strings.en-US.resx
+++ b/GalaxyViewer/Assets/Localization/Strings.en-US.resx
@@ -25,12 +25,6 @@
-
- GalaxyViewer
-
-
- 0.1
-
Welcome to GalaxyViewer 🌌
diff --git a/GalaxyViewer/Assets/Localization/Strings.resx b/GalaxyViewer/Assets/Localization/Strings.resx
index 71a4efe..54388f7 100644
--- a/GalaxyViewer/Assets/Localization/Strings.resx
+++ b/GalaxyViewer/Assets/Localization/Strings.resx
@@ -23,12 +23,6 @@ This is in English (United States) language, as the default. It is in case there
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- GalaxyViewer
-
-
- 0.1
-
Welcome to GalaxyViewer 🌌
diff --git a/GalaxyViewer/Assets/grids.xml b/GalaxyViewer/Assets/grids.xml
deleted file mode 100644
index f08a750..0000000
--- a/GalaxyViewer/Assets/grids.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/GalaxyViewer/Assets/preferences.xml b/GalaxyViewer/Assets/preferences.xml
deleted file mode 100644
index e5bcc7a..0000000
--- a/GalaxyViewer/Assets/preferences.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- Default
- Home
- Atkinson Hyperlegible
- en-US
- 1721172337
-
\ No newline at end of file
diff --git a/GalaxyViewer/Commands/RelayCommand.cs b/GalaxyViewer/Commands/RelayCommand.cs
index e81a879..01e1fdc 100644
--- a/GalaxyViewer/Commands/RelayCommand.cs
+++ b/GalaxyViewer/Commands/RelayCommand.cs
@@ -2,27 +2,26 @@
using System.Threading.Tasks;
using System.Windows.Input;
-namespace GalaxyViewer.Commands
+namespace GalaxyViewer.Commands;
+
+public class RelayCommand(Func execute, Func? canExecute = null)
+ : ICommand
{
- public class RelayCommand(Func execute, Func? canExecute = null)
- : ICommand
+ private readonly Func _execute =
+ execute ?? throw new ArgumentNullException(nameof(execute));
+
+ public bool CanExecute(object? parameter) => canExecute?.Invoke() ?? true;
+
+ public async void Execute(object? parameter) => await _execute();
+
+ public event EventHandler? CanExecuteChanged
{
- private readonly Func _execute =
- execute ?? throw new ArgumentNullException(nameof(execute));
+ add => CommandManager.RequerySuggested += value;
+ remove => CommandManager.RequerySuggested -= value;
+ }
- public bool CanExecute(object? parameter) => canExecute?.Invoke() ?? true;
-
- public async void Execute(object? parameter) => await _execute();
-
- public event EventHandler? CanExecuteChanged
- {
- add => CommandManager.RequerySuggested += value;
- remove => CommandManager.RequerySuggested -= value;
- }
-
- private abstract class CommandManager
- {
- public static EventHandler? RequerySuggested { get; set; }
- }
+ private abstract class CommandManager
+ {
+ public static EventHandler? RequerySuggested { get; set; }
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Controls/EmojiTextBlock.cs b/GalaxyViewer/Controls/EmojiTextBlock.cs
index 8f9d018..e6820c8 100644
--- a/GalaxyViewer/Controls/EmojiTextBlock.cs
+++ b/GalaxyViewer/Controls/EmojiTextBlock.cs
@@ -5,53 +5,52 @@ using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Media;
-namespace GalaxyViewer.Controls
+namespace GalaxyViewer.Controls;
+
+public abstract partial class EmojiTextBlock : TextBlock
{
- public abstract partial class EmojiTextBlock : TextBlock
+ private static readonly Regex EmojiRegex = MyRegex();
+
+ public static readonly StyledProperty EmojiFontFamilyProperty =
+ AvaloniaProperty.Register(nameof(EmojiFontFamily));
+
+ public FontFamily EmojiFontFamily
{
- private static readonly Regex EmojiRegex = MyRegex();
-
- public static readonly StyledProperty EmojiFontFamilyProperty =
- AvaloniaProperty.Register(nameof(EmojiFontFamily));
-
- public FontFamily EmojiFontFamily
- {
- get => GetValue(EmojiFontFamilyProperty);
- set => SetValue(EmojiFontFamilyProperty, value);
- }
-
- public static readonly StyledProperty DefaultFontFamilyProperty =
- AvaloniaProperty.Register(nameof(DefaultFontFamily));
-
- public FontFamily DefaultFontFamily
- {
- get => GetValue(DefaultFontFamilyProperty);
- set => SetValue(DefaultFontFamilyProperty, value);
- }
-
- protected EmojiTextBlock()
- {
- this.PropertyChanged += (_, e) =>
- {
- if (e.Property == TextProperty)
- {
- ApplyFonts();
- }
- };
- }
-
- private void ApplyFonts()
- {
- if (string.IsNullOrEmpty(Text))
- return;
-
- var inlines = Text.Select(ch => new Run(ch.ToString()) { FontFamily = EmojiRegex.IsMatch(ch.ToString()) ? EmojiFontFamily : DefaultFontFamily }).Cast().ToList();
-
- Inlines?.Clear();
- Inlines?.AddRange(inlines);
- }
-
- [GeneratedRegex(@"[\u203C-\u3299\u1F000-\u1F644\u1F680-\u1F6FF\u1F700-\u1F77F\u1F780-\u1F7FF\u1F800-\u1F8FF\u1F900-\u1F9FF\u1FA00-\u1FA6F\u1FA70-\u1FAFF\u1FB00-\u1FBFF]", RegexOptions.Compiled)]
- private static partial Regex MyRegex();
+ get => GetValue(EmojiFontFamilyProperty);
+ set => SetValue(EmojiFontFamilyProperty, value);
}
+
+ public static readonly StyledProperty DefaultFontFamilyProperty =
+ AvaloniaProperty.Register(nameof(DefaultFontFamily));
+
+ public FontFamily DefaultFontFamily
+ {
+ get => GetValue(DefaultFontFamilyProperty);
+ set => SetValue(DefaultFontFamilyProperty, value);
+ }
+
+ protected EmojiTextBlock()
+ {
+ this.PropertyChanged += (_, e) =>
+ {
+ if (e.Property == TextProperty)
+ {
+ ApplyFonts();
+ }
+ };
+ }
+
+ private void ApplyFonts()
+ {
+ if (string.IsNullOrEmpty(Text))
+ return;
+
+ var inlines = Text.Select(ch => new Run(ch.ToString()) { FontFamily = EmojiRegex.IsMatch(ch.ToString()) ? EmojiFontFamily : DefaultFontFamily }).Cast().ToList();
+
+ Inlines?.Clear();
+ Inlines?.AddRange(inlines);
+ }
+
+ [GeneratedRegex(@"[\u203C-\u3299\u1F000-\u1F644\u1F680-\u1F6FF\u1F700-\u1F77F\u1F780-\u1F7FF\u1F800-\u1F8FF\u1F900-\u1F9FF\u1FA00-\u1FA6F\u1FA70-\u1FAFF\u1FB00-\u1FBFF]", RegexOptions.Compiled)]
+ private static partial Regex MyRegex();
}
\ No newline at end of file
diff --git a/GalaxyViewer/GalaxyViewer.csproj b/GalaxyViewer/GalaxyViewer.csproj
index 41c26ac..b79e0da 100644
--- a/GalaxyViewer/GalaxyViewer.csproj
+++ b/GalaxyViewer/GalaxyViewer.csproj
@@ -10,39 +10,41 @@
-
-
+
+
PreserveNewest
PreserveNewest
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
+
+
-
-
+
+
@@ -72,6 +74,6 @@
-
+
diff --git a/GalaxyViewer/Models/DebugViewModel.cs b/GalaxyViewer/Models/DebugViewModel.cs
new file mode 100644
index 0000000..f793495
--- /dev/null
+++ b/GalaxyViewer/Models/DebugViewModel.cs
@@ -0,0 +1,10 @@
+using ReactiveUI;
+
+namespace GalaxyViewer.Models
+{
+ public class DebugViewModel(IScreen screen) : ReactiveObject, IRoutableViewModel
+ {
+ public string UrlPathSegment => "debug";
+ public IScreen HostScreen { get; } = screen;
+ }
+}
\ No newline at end of file
diff --git a/GalaxyViewer/Models/GridModel.cs b/GalaxyViewer/Models/GridModel.cs
index af48ec9..b67ed00 100644
--- a/GalaxyViewer/Models/GridModel.cs
+++ b/GalaxyViewer/Models/GridModel.cs
@@ -1,9 +1,10 @@
-using System;
+using LiteDB;
namespace GalaxyViewer.Models
{
- public class Grid
+ public class GridModel
{
+ [BsonId] public ObjectId Id { get; set; }
public string GridNick { get; set; }
public string GridName { get; set; }
public string Platform { get; set; }
@@ -15,20 +16,5 @@ namespace GalaxyViewer.Models
public string Register { get; set; }
public string Password { get; set; }
public string Version { get; set; }
-
- public Grid(string gridNick, string gridName, string platform, string loginUri, string loginPage, string helperUri, string website, string support, string register, string password, string version)
- {
- GridNick = !string.IsNullOrEmpty(gridNick) ? gridNick : throw new ArgumentNullException(nameof(gridNick));
- GridName = !string.IsNullOrEmpty(gridName) ? gridName : throw new ArgumentNullException(nameof(gridName));
- Platform = !string.IsNullOrEmpty(platform) ? platform : throw new ArgumentNullException(nameof(platform));
- LoginUri = !string.IsNullOrEmpty(loginUri) ? loginUri : throw new ArgumentNullException(nameof(loginUri));
- LoginPage = !string.IsNullOrEmpty(loginPage) ? loginPage : throw new ArgumentNullException(nameof(loginPage));
- HelperUri = !string.IsNullOrEmpty(helperUri) ? helperUri : throw new ArgumentNullException(nameof(helperUri));
- Website = !string.IsNullOrEmpty(website) ? website : throw new ArgumentNullException(nameof(website));
- Support = !string.IsNullOrEmpty(support) ? support : throw new ArgumentNullException(nameof(support));
- Register = !string.IsNullOrEmpty(register) ? register : throw new ArgumentNullException(nameof(register));
- Password = !string.IsNullOrEmpty(password) ? password : throw new ArgumentNullException(nameof(password));
- Version = !string.IsNullOrEmpty(version) ? version : throw new ArgumentNullException(nameof(version));
- }
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Models/PreferencesModel.cs b/GalaxyViewer/Models/PreferencesModel.cs
index 00f1d15..48ca3ec 100644
--- a/GalaxyViewer/Models/PreferencesModel.cs
+++ b/GalaxyViewer/Models/PreferencesModel.cs
@@ -1,85 +1,16 @@
-using System;
using System.Collections.Generic;
-using System.Xml.Serialization;
+using LiteDB;
namespace GalaxyViewer.Models
{
- [Serializable]
public class PreferencesModel
{
- public PreferencesModel()
- {
- ThemeOptions = new List { "Light", "Dark", "Default" };
- LoginLocationOptions = new List { "Home", "Last Location" };
- FontOptions = new List { "Inter", "Atkinson Hyperlegible" };
- LanguageOptions = new List { "en-US" };
- }
-
- [XmlIgnore]
- public List ThemeOptions { get; set; }
-
- [XmlIgnore]
- public List LoginLocationOptions { get; set; }
-
- [XmlIgnore]
- public List FontOptions { get; set; }
-
- [XmlIgnore]
- public List LanguageOptions { get; set; }
-
- // Default values
- private string _theme = "Default";
- public string Theme
- {
- get => _theme;
- set
- {
- if (_theme != value)
- {
- _theme = value;
- }
- }
- }
-
- private string _loginLocation = "Home";
- public string LoginLocation
- {
- get => _loginLocation;
- set
- {
- if (_loginLocation != value)
- {
- _loginLocation = value;
- }
- }
- }
-
- private string _font = "Atkinson Hyperlegible";
- public string Font
- {
- get => _font;
- set
- {
- if (_font != value)
- {
- _font = value;
- }
- }
- }
-
- private string _language = "en-US";
- public string Language
- {
- get => _language;
- set
- {
- if (_language != value)
- {
- _language = value;
- }
- }
- }
-
+ [BsonId] public ObjectId Id { get; set; }
+ public string Theme { get; set; }
+ public string LoginLocation { get; set; }
+ public string Font { get; set; }
+ public string Language { get; set; }
+ public string SelectedGridNick { get; set; }
public long LastSavedEpoch { get; set; }
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Models/PreferencesOptions.cs b/GalaxyViewer/Models/PreferencesOptions.cs
new file mode 100644
index 0000000..f938e9f
--- /dev/null
+++ b/GalaxyViewer/Models/PreferencesOptions.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace GalaxyViewer.Models;
+
+public static class PreferencesOptions
+{
+ public static readonly List ThemeOptions = ["Light", "Dark", "Default"];
+ public static readonly List LoginLocationOptions = ["Home", "Last Location"];
+ public static readonly List FontOptions = ["Inter", "Atkinson Hyperlegible"];
+ public static readonly List LanguageOptions = ["en-US"];
+}
\ No newline at end of file
diff --git a/GalaxyViewer/Services/GridService.cs b/GalaxyViewer/Services/GridService.cs
index a3ba748..b5c6a5f 100644
--- a/GalaxyViewer/Services/GridService.cs
+++ b/GalaxyViewer/Services/GridService.cs
@@ -1,40 +1,43 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Xml.Linq;
+using System.Linq;
+using LiteDB;
using GalaxyViewer.Models;
namespace GalaxyViewer.Services
{
public class GridService
{
- public List ParseGridsFromXml()
+ private readonly string _databasePath;
+
+ public GridService()
{
- var xmlFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "grids.xml");
- var xDoc = XDocument.Load(xmlFilePath);
- var grids = new List();
+ var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer");
+ Directory.CreateDirectory(appDataPath); // Ensure the directory exists
+ _databasePath = Path.Combine(appDataPath, "data.db");
+ }
- if (xDoc.Root == null) return grids;
- foreach (var gridElement in xDoc.Root.Elements("grid"))
+ public List GetAllGrids()
+ {
+ try
{
- 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);
+ using var db = new LiteDatabase(_databasePath);
+ var collection = db.GetCollection("grids");
+ return collection.FindAll().ToList();
+ }
+ catch (IOException ex) when (ex.Message.Contains("Read-only file system"))
+ {
+ // Handle read-only file system scenario
+ Console.Error.WriteLine("Error: The file system is read-only. Please check the file system permissions.");
+ return [];
+ }
+ catch (Exception ex)
+ {
+ // Handle other exceptions
+ Console.Error.WriteLine($"An error occurred: {ex.Message}");
+ throw;
}
-
- return grids;
}
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Services/LiteDbService.cs b/GalaxyViewer/Services/LiteDbService.cs
new file mode 100644
index 0000000..b6d2b69
--- /dev/null
+++ b/GalaxyViewer/Services/LiteDbService.cs
@@ -0,0 +1,158 @@
+using LiteDB;
+using System;
+using System.IO;
+using GalaxyViewer.Models;
+using Serilog;
+
+namespace GalaxyViewer.Services
+{
+ public class LiteDbService : IDisposable
+ {
+ private LiteDatabase? _database;
+ private readonly string _databasePath;
+
+ public LiteDbService()
+ {
+ try
+ {
+ _databasePath = GetDatabasePath();
+ Directory.CreateDirectory(Path.GetDirectoryName(_databasePath) ?? throw new InvalidOperationException()); // Ensure the directory exists
+ _database = new LiteDatabase(_databasePath);
+ Log.Information("LiteDbService initialized with database path: {DbPath}", _databasePath);
+
+ // Call SeedDatabase to ensure the grids table is generated
+ SeedDatabase();
+ }
+ catch (LiteException ex)
+ {
+ Log.Error(ex, "LiteDB exception occurred. Attempting to recreate the database.");
+ HandleDatabaseCorruption();
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to initialize LiteDbService");
+ throw;
+ }
+ }
+
+ private static string GetDatabasePath()
+ {
+ string appDataPath;
+ appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer");
+ return Path.Combine(appDataPath, "data.db");
+ }
+
+ private void HandleDatabaseCorruption()
+ {
+ try
+ {
+ if (File.Exists(_databasePath))
+ {
+ File.Delete(_databasePath);
+ }
+ _database = new LiteDatabase(_databasePath);
+ SeedDatabase();
+ Log.Information("Database recreated successfully.");
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to recreate the database.");
+ throw;
+ }
+ }
+
+ public ILiteCollection GetCollection(string name)
+ {
+ Log.Information("Retrieving collection: {CollectionName}", name);
+ return _database.GetCollection(name);
+ }
+
+ public LiteDatabase? Database()
+ {
+ return _database;
+ }
+
+ private void SeedDatabase()
+ {
+ SeedPreferences();
+ SeedGrids();
+ }
+
+ private void SeedPreferences()
+ {
+ try
+ {
+ var preferencesCollection = _database.GetCollection("preferences");
+ if (preferencesCollection.Count() != 0) return;
+ var defaultPreferences = new PreferencesModel
+ {
+ Theme = "Default",
+ LoginLocation = "Home",
+ Font = "Atkinson Hyperlegible",
+ Language = "en-US",
+ SelectedGridNick = "agni",
+ LastSavedEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ };
+ preferencesCollection.Insert(defaultPreferences);
+ Log.Information("Database seeded with default preferences");
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to seed preferences");
+ }
+ }
+
+ private void SeedGrids()
+ {
+ try
+ {
+ var gridsCollection = _database.GetCollection("grids");
+ if (gridsCollection.Count() != 0) return;
+ var grids = new[]
+ {
+ new GridModel
+ {
+ GridNick = "agni",
+ GridName = "Second Life (agni)",
+ Platform = "SecondLife",
+ LoginUri = "https://login.agni.lindenlab.com/cgi-bin/login.cgi",
+ LoginPage = "http://secondlife.com/app/login/?channel=Second+Life+Release",
+ HelperUri = "https://secondlife.com/helpers/",
+ Website = "http://secondlife.com/",
+ Support = "http://secondlife.com/support/",
+ Register = "http://secondlife.com/registration/",
+ Password = "http://secondlife.com/account/request.php",
+ Version = "0"
+ },
+ new GridModel
+ {
+ GridNick = "aditi",
+ GridName = "Second Life Beta (aditi)",
+ Platform = "SecondLife",
+ LoginUri = "https://login.aditi.lindenlab.com/cgi-bin/login.cgi",
+ LoginPage = "http://secondlife.com/app/login/?channel=Second+Life+Beta",
+ HelperUri = "http://aditi-secondlife.webdev.lindenlab.com/helpers/",
+ Website = "http://secondlife.com/",
+ Support = "http://secondlife.com/support/",
+ Register = "http://secondlife.com/registration/",
+ Password = "http://secondlife.com/account/request.php",
+ Version = "1"
+ }
+ };
+
+ gridsCollection.InsertBulk(grids);
+ Log.Information("Database seeded with default grids");
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to seed grids");
+ }
+ }
+
+ public void Dispose()
+ {
+ _database?.Dispose();
+ Log.Information("LiteDbService disposed");
+ }
+ }
+}
\ No newline at end of file
diff --git a/GalaxyViewer/Services/NavigationService.cs b/GalaxyViewer/Services/NavigationService.cs
index b016d98..9cace2b 100644
--- a/GalaxyViewer/Services/NavigationService.cs
+++ b/GalaxyViewer/Services/NavigationService.cs
@@ -2,34 +2,33 @@
using System.Collections.Generic;
using Avalonia.Controls;
-namespace GalaxyViewer.Services
+namespace GalaxyViewer.Services;
+
+public class NavigationService
{
- public class NavigationService
+ private readonly Dictionary _routes = new();
+ private readonly ContentControl _contentControl;
+
+ public NavigationService(ContentControl contentControl)
{
- private readonly Dictionary _routes = new Dictionary();
- private readonly ContentControl _contentControl;
+ _contentControl = contentControl;
+ }
- public NavigationService(ContentControl contentControl)
+ public void RegisterRoute(string uri, Type viewType)
+ {
+ _routes[uri] = viewType;
+ }
+
+ public void NavigateTo(string uri)
+ {
+ if (_routes.TryGetValue(uri, out var viewType))
{
- _contentControl = contentControl;
+ var view = (Control)Activator.CreateInstance(viewType)!;
+ _contentControl.Content = view;
}
-
- public void RegisterRoute(string uri, Type viewType)
+ else
{
- _routes[uri] = viewType;
- }
-
- public void Navigate(string uri)
- {
- if (_routes.TryGetValue(uri, out var viewType))
- {
- var view = (Control)Activator.CreateInstance(viewType)!;
- _contentControl.Content = view;
- }
- else
- {
- throw new InvalidOperationException($"No view registered for URI: {uri}");
- }
+ throw new InvalidOperationException($"No view registered for URI: {uri}");
}
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Services/PreferencesManager.cs b/GalaxyViewer/Services/PreferencesManager.cs
index 9d4b959..600bbfc 100644
--- a/GalaxyViewer/Services/PreferencesManager.cs
+++ b/GalaxyViewer/Services/PreferencesManager.cs
@@ -1,77 +1,95 @@
using System;
-using System.IO;
-using System.Threading.Tasks;
-using System.Xml.Serialization;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using LiteDB;
using GalaxyViewer.Models;
-using Serilog;
+using System.Threading.Tasks;
namespace GalaxyViewer.Services
{
- public class PreferencesManager
+ public sealed class PreferencesManager
{
- private readonly string _preferencesFilePath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer",
- "preferences.xml");
+ private readonly ILiteCollection _preferencesCollection;
+ private readonly ILiteCollection? _gridsCollection;
- private bool _isLoadingPreferences;
- private PreferencesModel _currentPreferences;
- public PreferencesModel CurrentPreferences => _currentPreferences;
+ public event EventHandler? PreferencesChanged;
- public PreferencesManager()
+ public PreferencesManager(LiteDbService? liteDbService)
{
- EnsurePreferencesDirectory();
+ Debug.Assert(liteDbService != null, nameof(liteDbService) + " != null");
+ var database = liteDbService.Database();
+ Debug.Assert(database != null, nameof(database) + " != null");
+ _preferencesCollection = database.GetCollection("preferences");
+ _gridsCollection = database?.GetCollection("grids");
}
- private void EnsurePreferencesDirectory()
+ public PreferencesModel CurrentPreferences
{
- var directoryPath = Path.GetDirectoryName(_preferencesFilePath);
- if (!string.IsNullOrEmpty(directoryPath))
+ get
{
- Directory.CreateDirectory(directoryPath);
+ var preferences = _preferencesCollection.FindOne(Query.All());
+ return preferences ?? new PreferencesModel
+ {
+ Id = ObjectId.NewObjectId(),
+ Theme = "Default",
+ LoginLocation = "Home",
+ Font = "Atkinson Hyperlegible",
+ Language = "en-US",
+ LastSavedEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
+ SelectedGridNick = string.Empty
+ };
}
}
public async Task LoadPreferencesAsync()
{
- try
+ return await Task.Run(() =>
{
- await using var stream = new FileStream(_preferencesFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
- var serializer = new XmlSerializer(typeof(PreferencesModel));
- _currentPreferences = (PreferencesModel)serializer.Deserialize(stream)!;
- _isLoadingPreferences = false;
- return _currentPreferences;
- }
- catch (Exception ex)
- {
- Log.Error(ex, "Failed to load preferences");
- _currentPreferences = new PreferencesModel();
- _isLoadingPreferences = false;
- return _currentPreferences;
- }
+ var preferences = _preferencesCollection.FindOne(Query.All());
+ return preferences ?? new PreferencesModel
+ {
+ Id = ObjectId.NewObjectId(),
+ Theme = "Default",
+ LoginLocation = "Home",
+ Font = "Atkinson Hyperlegible",
+ Language = "en-US",
+ SelectedGridNick = "agni",
+ LastSavedEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
+ };
+ });
}
- public bool IsLoadingPreferences => _isLoadingPreferences;
-
- public event EventHandler? PreferencesChanged;
-
public async Task SavePreferencesAsync(PreferencesModel preferences)
{
- try
+ await Task.Run(() =>
{
- await using var stream = new FileStream(_preferencesFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
- var serializer = new XmlSerializer(typeof(PreferencesModel));
- serializer.Serialize(stream, preferences);
- _currentPreferences = preferences;
- if (!_isLoadingPreferences)
- {
- PreferencesChanged?.Invoke(this, preferences);
- }
- }
- catch (Exception ex)
+ _preferencesCollection.Upsert(preferences);
+ OnPreferencesChanged(preferences);
+ });
+ }
+
+ public Dictionary> GetCurrentPreferencesOptions()
+ {
+ return new Dictionary>
{
- Log.Error(ex, "Failed to save preferences");
- }
+ { "ThemeOptions", PreferencesOptions.ThemeOptions },
+ { "LoginLocationOptions", PreferencesOptions.LoginLocationOptions },
+ { "FontOptions", PreferencesOptions.FontOptions },
+ { "LanguageOptions", PreferencesOptions.LanguageOptions }
+ };
+ }
+
+ public List GetGridOptions()
+ {
+ return _gridsCollection?.FindAll().Select(grid => grid.GridNick).ToList() ??
+ [];
+ }
+
+ private void OnPreferencesChanged(PreferencesModel preferences)
+ {
+ PreferencesChanged?.Invoke(this, preferences);
}
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/ViewModels/LoginViewModel.cs b/GalaxyViewer/ViewModels/LoginViewModel.cs
index ce9be40..7a53b2d 100644
--- a/GalaxyViewer/ViewModels/LoginViewModel.cs
+++ b/GalaxyViewer/ViewModels/LoginViewModel.cs
@@ -1,67 +1,283 @@
-using System.Threading.Tasks;
-using System.Text.RegularExpressions;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Reactive;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Avalonia.Controls.Notifications;
+using GalaxyViewer.Models;
+using GalaxyViewer.Services;
using ReactiveUI;
-using System.ComponentModel;
-using GalaxyViewer.Assets.Localization;
+using Ursa.Controls;
using Serilog;
using OpenMetaverse;
-namespace GalaxyViewer.ViewModels;
-
-public abstract partial class LoginViewModel : ReactiveObject
+namespace GalaxyViewer.ViewModels
{
- private string _username;
- private string _password;
- private readonly GridClient _client = new GridClient();
- private readonly LocalizationManager _localizationManager;
-
- protected LoginViewModel(string username, string password,
- LocalizationManager localizationManager)
+ public class LoginViewModel : ReactiveObject, IRoutableViewModel
{
- _username = username;
- _password = password;
- _localizationManager = localizationManager;
- ReactiveCommand.Create(TryLoginAsync);
- }
+ public string UrlPathSegment => "login";
- public string Username
- {
- get => _username;
- set => this.RaiseAndSetIfChanged(ref _username, value);
- }
-
- public string Password
- {
- get => _password;
- set => this.RaiseAndSetIfChanged(ref _password, value);
- }
-
- private static 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())
+ public IScreen HostScreen
{
- 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);
+ get
+ {
+ Debug.Assert(_routableViewModelImplementation?.HostScreen != null,
+ "_routableViewModelImplementation?.HostScreen != null");
+ return _routableViewModelImplementation.HostScreen;
+ }
}
- else
+
+ private readonly PreferencesViewModel _preferencesViewModel;
+ private string _username;
+ private string _password;
+ private readonly GridClient _client = new();
+ private IRoutableViewModel? _routableViewModelImplementation;
+ private readonly GridService _gridService;
+ private ObservableCollection _grids;
+ private GridModel _selectedGrid;
+
+ private const string DefaultGridUri =
+ "https://login.agni.lindenlab.com/cgi-bin/login.cgi";
+
+ public WindowToastManager? ToastManager { get; set; }
+
+ public LoginViewModel()
{
- Log.Logger.Error(LoginFailed);
- }*/
+ _preferencesViewModel = new PreferencesViewModel();
+ _username = string.Empty;
+ _password = string.Empty;
+ LoginLocations = _preferencesViewModel.LoginLocationOptions;
+ SelectedLoginLocation = _preferencesViewModel.SelectedLoginLocation;
+ TryLoginCommand = ReactiveCommand.CreateFromTask(TryLoginAsync);
+ _gridService = new GridService();
+ _grids = new ObservableCollection();
+
+ LoadGrids();
+
+ // Subscribe to the ThrownExceptions property to handle errors
+ TryLoginCommand.ThrownExceptions.Subscribe(ex =>
+ {
+ Log.Error("An error occurred during login: {Error}", ex.Message);
+ // Handle the error (e.g., show a dialog to the user)
+ _ = ShowLoginErrorAsync("An error occurred during login. Please try again.");
+ });
+
+ // Subscribe to the Network.LoginProgress event
+ _client.Network.LoginProgress += OnLoginProgress;
+ }
+
+ public ObservableCollection LoginLocations { get; }
+
+ public string Username
+ {
+ get => _username;
+ set => this.RaiseAndSetIfChanged(ref _username, value);
+ }
+
+ public string Password
+ {
+ get => _password;
+ set => this.RaiseAndSetIfChanged(ref _password, value);
+ }
+
+ public string SelectedLoginLocation
+ {
+ get => _preferencesViewModel.SelectedLoginLocation;
+ set
+ {
+ _preferencesViewModel.SelectedLoginLocation = value;
+ this.RaisePropertyChanged();
+ }
+ }
+
+ public string LoginStatusMessage { get; set; }
+
+ public ObservableCollection Grids { get; set; }
+
+ public GridModel? SelectedGrid
+ {
+ get
+ {
+ var selectedGrid = _grids.FirstOrDefault(g =>
+ g.GridNick == _preferencesViewModel.SelectedGridNick);
+ if (selectedGrid == null)
+ {
+ selectedGrid = new GridModel
+ {
+ GridNick = "Second Life",
+ LoginUri = DefaultGridUri
+ };
+ }
+
+ return selectedGrid;
+ }
+ set
+ {
+ if (value == null) return;
+ _preferencesViewModel.SelectedGridNick = value.GridNick;
+ this.RaiseAndSetIfChanged(ref _selectedGrid, value);
+ }
+ }
+
+ private void LoadGrids()
+ {
+ var grids = _gridService.GetAllGrids();
+ _grids = new ObservableCollection(grids);
+ SelectedGrid = _grids.FirstOrDefault(g =>
+ g.GridNick == _preferencesViewModel.SelectedGridNick);
+ }
+
+ public ReactiveCommand TryLoginCommand { get; }
+
+ private async Task ShowLoginErrorAsync(string errorMessage)
+ {
+ ToastManager?.Show(
+ new Toast(
+ errorMessage),
+ showIcon: true,
+ showClose: true,
+ type: NotificationType.Error
+ );
+ await Task.CompletedTask;
+ }
+
+ private async Task TryLoginAsync()
+ {
+ if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password))
+ {
+ Log.Warning("Username or password is empty");
+ await ShowLoginErrorAsync("Username or password is empty");
+ return;
+ }
+
+ var platformMap = new Dictionary
+ {
+ { OSPlatform.Windows, "Win" },
+ { OSPlatform.Linux, "Lin" },
+ { OSPlatform.OSX, "Mac" },
+ { OSPlatform.Create("ANDROID"), "And" },
+ { OSPlatform.Create("IOS"), "iOS" }
+ };
+
+ var ourPlatform =
+ platformMap.FirstOrDefault(kv => RuntimeInformation.IsOSPlatform(kv.Key)).Value ??
+ "Unk";
+
+ var loginParams = _client?.Network?.DefaultLoginParams(
+ Username.Split(' ')[0], // firstName
+ Username.Contains(' ') ? Username.Split(' ')[1] : "Resident", // lastName
+ Password,
+ "GalaxyViewer", // ViewerName
+ "0.1.0" // ViewerVersion
+ );
+
+ if (loginParams == null)
+ {
+ Log.Error("Failed to create login parameters");
+ await ShowLoginErrorAsync("Failed to create login parameters");
+ return;
+ }
+
+ loginParams.URI =
+ SelectedGrid?.LoginUri; // Set the login URI to the selected grid's URI
+ loginParams.MfaEnabled = true; // Inform the server that we support MFA
+ loginParams.Platform = ourPlatform; // Set the platform - our operating system
+ loginParams.PlatformVersion =
+ Environment.OSVersion.VersionString; // Set the platform version
+ loginParams.Start =
+ _preferencesViewModel
+ ?.SelectedLoginLocation; // Set the start location to the selected login location
+ loginParams.MfaHash = string.Empty; // Clear the MFA hash
+
+ var loginSuccess = await Task.Run(() => _client?.Network?.Login(loginParams) ?? false);
+
+ if (loginSuccess)
+ {
+ Log.Information("Login successful");
+ await ProcessCapabilitiesAsync();
+ }
+ else if (_client?.Network?.LoginMessage.Contains("MFA required") == true)
+ {
+ Log.Warning("MFA required");
+ var mfaCode = await ShowMfaInputDialogAsync();
+ if (!string.IsNullOrEmpty(mfaCode))
+ {
+ loginParams.MfaHash = Utils.MD5(mfaCode);
+ loginSuccess =
+ await Task.Run(() => _client?.Network?.Login(loginParams) ?? false);
+ if (loginSuccess)
+ {
+ Log.Information("Login successful with MFA");
+ await ProcessCapabilitiesAsync();
+ }
+ else
+ {
+ Log.Error("Login failed with MFA: {Error}", _client?.Network?.LoginMessage);
+ await ShowLoginErrorAsync(
+ $"Login failed with MFA: {_client?.Network?.LoginMessage}");
+ }
+ }
+ else
+ {
+ Log.Warning("MFA code entry was canceled");
+ await ShowLoginErrorAsync("MFA code entry was canceled");
+ }
+ }
+ else
+ {
+ Log.Error("Login failed: {Error}", _client?.Network?.LoginMessage);
+ await ShowLoginErrorAsync($"Login failed: {_client?.Network?.LoginMessage}");
+ }
+ }
+
+ private async Task ProcessCapabilitiesAsync()
+ {
+ var capabilities = _client.Network.CurrentSim.Caps;
+ if (capabilities != null)
+ {
+ // TODO: Process the capabilities as needed
+ }
+
+ await Task.CompletedTask;
+ }
+
+ private void OnLoginProgress(object? sender, LoginProgressEventArgs e)
+ {
+ Log.Information("Login progress: {Status}", e.Status);
+ switch (e.Status)
+ {
+ case LoginStatus.ConnectingToLogin:
+ LoginStatusMessage = "Connecting to login server...";
+ break;
+ case LoginStatus.ConnectingToSim:
+ LoginStatusMessage = "Connecting to region...";
+ break;
+ case LoginStatus.Redirecting:
+ LoginStatusMessage = "Redirecting...";
+ break;
+ case LoginStatus.ReadingResponse:
+ LoginStatusMessage = "Reading response...";
+ break;
+ case LoginStatus.Success:
+ LoginStatusMessage = $"Logged in as {_client.Self.Name}";
+ break;
+ case LoginStatus.Failed:
+ LoginStatusMessage = $"Login failed: {e.Message}";
+ break;
+ case LoginStatus.None:
+ default:
+ LoginStatusMessage = $"Unknown login status: {e.Status}";
+ break;
+ }
+ }
+
+ private async Task ShowMfaInputDialogAsync()
+ {
+ // TODO: Implement MFA input dialog
+ return await Task.FromResult(string.Empty);
+ }
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/ViewModels/MainViewModel.cs b/GalaxyViewer/ViewModels/MainViewModel.cs
index 19766c5..96d0f98 100644
--- a/GalaxyViewer/ViewModels/MainViewModel.cs
+++ b/GalaxyViewer/ViewModels/MainViewModel.cs
@@ -1,5 +1,4 @@
using System;
-using System.Diagnostics;
using System.IO;
using System.Reactive;
using System.Threading.Tasks;
@@ -7,7 +6,6 @@ using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
-using GalaxyViewer.Services;
using GalaxyViewer.Views;
using OpenMetaverse;
using ReactiveUI;
@@ -17,12 +15,12 @@ namespace GalaxyViewer.ViewModels
{
public class MainViewModel : ViewModelBase
{
+ private UserControl? _currentView;
private bool _isLoggedIn;
private readonly GridClient _client = new();
private string? _username;
-
public string? Username
{
get => _username;
@@ -30,7 +28,6 @@ namespace GalaxyViewer.ViewModels
}
private string? _password;
-
public string? Password
{
get => _password;
@@ -38,7 +35,6 @@ namespace GalaxyViewer.ViewModels
}
private string? _loginLocation;
-
public string? LoginLocation
{
get => _loginLocation;
@@ -46,44 +42,42 @@ namespace GalaxyViewer.ViewModels
}
private string? _grid;
-
public string? Grid
{
get => _grid;
set => this.RaiseAndSetIfChanged(ref _grid, value);
}
+ public UserControl? CurrentView
+ {
+ get => _currentView;
+ set => this.RaiseAndSetIfChanged(ref _currentView, value);
+ }
+
public bool IsLoggedIn
{
get => _isLoggedIn;
- set => this.RaiseAndSetIfChanged(ref _isLoggedIn, value);
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _isLoggedIn, value);
+ CurrentView = value ? new LoggedInView() : new LoginView();
+ }
}
public ReactiveCommand LogoutCommand { get; }
public ReactiveCommand LoginCommand { get; }
-
public ICommand ShowPreferencesCommand { get; }
- public ICommand ShowDevViewCommand { get; }
-
public ICommand ExitCommand { get; }
- public MainViewModel(NavigationService navigationService)
+ public MainViewModel()
{
+ _currentView = new LoginView();
IsLoggedIn = false; // By default you aren't logged in
LogoutCommand = ReactiveCommand.Create(Logout);
- LoginCommand = ReactiveCommand.Create(() => navigationService.Navigate("login"));
-
+ LoginCommand = ReactiveCommand.CreateFromTask(Login);
ShowPreferencesCommand = ReactiveCommand.Create(ShowPreferences);
- ShowDevViewCommand = ReactiveCommand.Create(() => navigationService.Navigate("debug"));
-
ExitCommand = ReactiveCommand.Create(ExitApplication);
-
- // Navigate to login view on startup
- if (!IsLoggedIn)
- {
- navigationService.Navigate("login");
- }
}
private void Logout()
@@ -93,24 +87,22 @@ namespace GalaxyViewer.ViewModels
IsLoggedIn = false;
}
- public async Task Login(string username, string password)
+ private async Task Login()
{
- /*try
+ try
{
- if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
+ // Validate properties before using them
+ if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
{
- await File.AppendAllTextAsync("error.log",
- "Invalid login parameters for user: " + username);
- return;
+ // Handle invalid login parameters
+ await File.AppendAllTextAsync("error.log", $"Invalid login parameters for user: {Username}");
+ return; // Exit the method if validation fails
}
const string userAgent = "GalaxyViewer/0.1.0";
- var libreMetaverseLoginParams =
- _client.Network.DefaultLoginParams(username, password, userAgent, LoginLocation,
- Grid);
+ var loginParams = _client.Network.DefaultLoginParams(Username, Password, userAgent, LoginLocation, Grid);
- var loginSuccess =
- await Task.Run(() => _client.Network.Login(libreMetaverseLoginParams));
+ var loginSuccess = await Task.Run(() => _client.Network.Login(loginParams));
if (loginSuccess)
{
@@ -118,19 +110,21 @@ namespace GalaxyViewer.ViewModels
}
else
{
- Log.Error("Failed to login user: {Username}", username);
+ // Handle failed login
+ Log.Error("Failed to login user: {Username}", Username);
}
}
catch (Exception ex)
{
- Log.Error(ex, "An error occurred while logging in user: {Username}", username);
- }*/
+ // Handle any exceptions that occur during login
+ Log.Error(ex, "An error occurred while logging in user: {Username}", Username);
+ }
}
- private static void ShowPreferences()
+ private void ShowPreferences()
{
#if ANDROID
- _navigationService.Navigate("preferences");
+ CurrentView = new PreferencesView();
#else
var preferencesWindow = new PreferencesWindow
{
@@ -142,11 +136,15 @@ namespace GalaxyViewer.ViewModels
private void ExitApplication()
{
- if (Application.Current?.ApplicationLifetime is not
- IClassicDesktopStyleApplicationLifetime
- desktopLifetime) return;
- Logout();
- desktopLifetime.Shutdown();
+ if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+ {
+ desktopLifetime.Shutdown();
+ }
+ }
+
+ public void Dispose()
+ {
+ _client.Network.Logout();
}
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/ViewModels/PreferencesViewModel.cs b/GalaxyViewer/ViewModels/PreferencesViewModel.cs
index 16e8107..0e91df8 100644
--- a/GalaxyViewer/ViewModels/PreferencesViewModel.cs
+++ b/GalaxyViewer/ViewModels/PreferencesViewModel.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
@@ -17,15 +18,24 @@ namespace GalaxyViewer.ViewModels
{
if (App.PreferencesManager != null) _preferencesManager = App.PreferencesManager;
_preferences = App.PreferencesManager?.CurrentPreferences ?? new PreferencesModel();
- ThemeOptions = new ObservableCollection(_preferences.ThemeOptions);
- LoginLocationOptions = new ObservableCollection(_preferences.LoginLocationOptions);
- LanguageOptions = new ObservableCollection(_preferences.LanguageOptions);
- FontOptions = new ObservableCollection(_preferences.FontOptions);
+ var preferencesOptions = _preferencesManager?.GetCurrentPreferencesOptions();
+ ThemeOptions = new ObservableCollection(preferencesOptions?["ThemeOptions"] ??
+ []);
+ LoginLocationOptions = new ObservableCollection(
+ preferencesOptions?["LoginLocationOptions"] ??
+ []);
+ LanguageOptions = new ObservableCollection(
+ preferencesOptions?["LanguageOptions"] ??
+ []);
+ FontOptions =
+ new ObservableCollection(preferencesOptions?["FontOptions"] ?? []);
_selectedTheme = _preferences.Theme;
_selectedLoginLocation = _preferences.LoginLocation;
_selectedLanguage = _preferences.Language;
_selectedFont = _preferences.Font;
+ _selectedGridNick = _preferences.SelectedGridNick;
SaveCommand = new RelayCommand(async () => await SavePreferencesAsync());
+ LoadPreferences();
}
private async void LoadPreferences()
@@ -34,20 +44,14 @@ namespace GalaxyViewer.ViewModels
if (_preferencesManager != null)
_preferences = await _preferencesManager.LoadPreferencesAsync();
- // Set selected options from preferences.xml
- if (_selectedTheme != _preferences.Theme)
- _selectedTheme = _preferences.Theme;
- if (_selectedLoginLocation != _preferences.LoginLocation)
- _selectedLoginLocation = _preferences.LoginLocation;
- if (_selectedLanguage != _preferences.Language)
- _selectedLanguage = _preferences.Language;
- if (_selectedFont != _preferences.Font)
- _selectedFont = _preferences.Font;
+ SelectedTheme = _preferences.Theme;
+ SelectedLoginLocation = _preferences.LoginLocation;
+ SelectedLanguage = _preferences.Language;
+ SelectedFont = _preferences.Font;
+ SelectedGridNick = _preferences.SelectedGridNick;
+ var gridOptions = _preferencesManager?.GetGridOptions();
+ GridOptions = new ObservableCollection(gridOptions ?? []);
- OnPropertyChanged(nameof(SelectedTheme));
- OnPropertyChanged(nameof(SelectedLoginLocation));
- OnPropertyChanged(nameof(SelectedLanguage));
- OnPropertyChanged(nameof(SelectedFont));
_isLoadingPreferences = false;
}
@@ -55,22 +59,22 @@ namespace GalaxyViewer.ViewModels
public ObservableCollection LoginLocationOptions { get; private set; }
public ObservableCollection LanguageOptions { get; private set; }
public ObservableCollection FontOptions { get; private set; }
+ public ObservableCollection GridOptions { get; private set; }
private string _selectedLoginLocation;
private string _selectedLanguage;
private string _selectedFont;
private string _selectedTheme;
+ private string _selectedGridNick;
public string SelectedTheme
{
get => _selectedTheme;
set
{
- if (_selectedTheme != value && !_isLoadingPreferences)
- {
- _selectedTheme = value;
- OnPropertyChanged(nameof(SelectedTheme));
- }
+ if (_selectedTheme == value || _isLoadingPreferences) return;
+ _selectedTheme = value;
+ OnPropertyChanged(nameof(SelectedTheme));
}
}
@@ -79,11 +83,9 @@ namespace GalaxyViewer.ViewModels
get => _selectedLoginLocation;
set
{
- if (_selectedLoginLocation != value && !_isLoadingPreferences)
- {
- _selectedLoginLocation = value;
- OnPropertyChanged(nameof(SelectedLoginLocation));
- }
+ if (_selectedLoginLocation == value || _isLoadingPreferences) return;
+ _selectedLoginLocation = value;
+ OnPropertyChanged(nameof(SelectedLoginLocation));
}
}
@@ -92,11 +94,9 @@ namespace GalaxyViewer.ViewModels
get => _selectedLanguage;
set
{
- if (_selectedLanguage != value && !_isLoadingPreferences)
- {
- _selectedLanguage = value;
- OnPropertyChanged(nameof(SelectedLanguage));
- }
+ if (_selectedLanguage == value || _isLoadingPreferences) return;
+ _selectedLanguage = value;
+ OnPropertyChanged(nameof(SelectedLanguage));
}
}
@@ -105,11 +105,20 @@ namespace GalaxyViewer.ViewModels
get => _selectedFont;
set
{
- if (_selectedFont != value && !_isLoadingPreferences)
- {
- _selectedFont = value;
- OnPropertyChanged(nameof(SelectedFont));
- }
+ if (_selectedFont == value || _isLoadingPreferences) return;
+ _selectedFont = value;
+ OnPropertyChanged(nameof(SelectedFont));
+ }
+ }
+
+ public string SelectedGridNick
+ {
+ get => _selectedGridNick;
+ set
+ {
+ if (_selectedGridNick == value || _isLoadingPreferences) return;
+ _selectedGridNick = value;
+ OnPropertyChanged(nameof(SelectedGridNick));
}
}
@@ -119,6 +128,7 @@ namespace GalaxyViewer.ViewModels
_preferences.LoginLocation = SelectedLoginLocation;
_preferences.Language = SelectedLanguage;
_preferences.Font = SelectedFont;
+ _preferences.SelectedGridNick = SelectedGridNick;
if (_preferencesManager != null)
await _preferencesManager.SavePreferencesAsync(_preferences);
diff --git a/GalaxyViewer/ViewModels/SetProperty.cs b/GalaxyViewer/ViewModels/SetProperty.cs
index 1b2c418..8053084 100644
--- a/GalaxyViewer/ViewModels/SetProperty.cs
+++ b/GalaxyViewer/ViewModels/SetProperty.cs
@@ -1,23 +1,13 @@
using System.ComponentModel;
-using System.Runtime.CompilerServices;
-namespace GalaxyViewer.ViewModels
+namespace GalaxyViewer.ViewModels;
+
+public abstract partial class ViewModelBase : INotifyPropertyChanged
{
- public abstract partial class ViewModelBase : INotifyPropertyChanged
+ public new event PropertyChangedEventHandler? PropertyChanged;
+
+ protected void OnPropertyChanged(string propertyName)
{
- public new event PropertyChangedEventHandler? PropertyChanged;
-
- protected bool SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null!)
- {
- if (Equals(field, value)) return false;
- field = value;
- OnPropertyChanged(propertyName);
- return true;
- }
-
- protected void OnPropertyChanged(string propertyName)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Views/BaseWindow.cs b/GalaxyViewer/Views/BaseWindow.cs
index a6fa4bc..6370d0e 100644
--- a/GalaxyViewer/Views/BaseWindow.cs
+++ b/GalaxyViewer/Views/BaseWindow.cs
@@ -1,25 +1,53 @@
-using Avalonia.Controls;
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Media;
using Avalonia.Styling;
using GalaxyViewer.Models;
+using Ursa.ReactiveUIExtension;
-namespace GalaxyViewer.Views
+namespace GalaxyViewer.Views;
+
+public class BaseWindow : ReactiveUrsaWindow, IStyleable
{
- public class BaseWindow : Window
- {
- protected BaseWindow()
- {
- Icon = new WindowIcon("Assets/galaxy.ico");
- CanResize = true;
- App.PreferencesManager!.PreferencesChanged += OnPreferencesChanged;
- ApplyTheme(App.PreferencesManager.LoadPreferencesAsync().Result.Theme);
- }
+ Type IStyleable.StyleKey => typeof(Window);
- private void OnPreferencesChanged(object? sender, PreferencesModel preferences)
+ protected BaseWindow()
+ {
+ Title = "GalaxyViewer";
+ Icon = new WindowIcon("Assets/GalaxyViewerLogo.ico");
+ CanResize = true;
+ if (App.PreferencesManager != null)
+ App.PreferencesManager.PreferencesChanged += OnPreferencesChanged;
+
+ // Load preferences asynchronously without blocking the UI thread
+ _ = LoadPreferencesAsync();
+ }
+
+ private async Task LoadPreferencesAsync()
+ {
+ if (App.PreferencesManager == null) return;
+ var preferences = await App.PreferencesManager.LoadPreferencesAsync();
+ await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
{
ApplyTheme(preferences.Theme);
- }
+ FontFamily = new FontFamily(preferences.Font);
+ });
+ }
- public void ApplyTheme(string theme)
+ private void OnPreferencesChanged(object? sender, PreferencesModel preferences)
+ {
+ Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ ApplyTheme(preferences.Theme);
+ FontFamily = new FontFamily(preferences.Font);
+ });
+ }
+
+ internal void ApplyTheme(string theme)
+ {
+ Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
{
RequestedThemeVariant = theme switch
{
@@ -27,6 +55,6 @@ namespace GalaxyViewer.Views
"Dark" => ThemeVariant.Dark,
_ => ThemeVariant.Default
};
- }
+ });
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Views/DebugView.axaml.cs b/GalaxyViewer/Views/DebugView.axaml.cs
index f2d093f..1f6385c 100644
--- a/GalaxyViewer/Views/DebugView.axaml.cs
+++ b/GalaxyViewer/Views/DebugView.axaml.cs
@@ -1,18 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-namespace GalaxyViewer.Views
-{
- public partial class DebugView : UserControl
- {
- public DebugView()
- {
- InitializeComponent();
- }
+namespace GalaxyViewer.Views;
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
+public partial class DebugView : UserControl
+{
+ public DebugView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Views/LoggedInView.axaml.cs b/GalaxyViewer/Views/LoggedInView.axaml.cs
index 887e771..06541c1 100644
--- a/GalaxyViewer/Views/LoggedInView.axaml.cs
+++ b/GalaxyViewer/Views/LoggedInView.axaml.cs
@@ -1,18 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-namespace GalaxyViewer.Views
-{
- public partial class LoggedInView : UserControl
- {
- public LoggedInView()
- {
- InitializeComponent();
- }
+namespace GalaxyViewer.Views;
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
+public partial class LoggedInView : UserControl
+{
+ public LoggedInView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Views/LoginView.axaml b/GalaxyViewer/Views/LoginView.axaml
index b603aa3..39d4bc0 100644
--- a/GalaxyViewer/Views/LoginView.axaml
+++ b/GalaxyViewer/Views/LoginView.axaml
@@ -1,45 +1,49 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+ Command="{Binding TryLoginCommand}"
+ Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Left" />
-
-
+
\ No newline at end of file
diff --git a/GalaxyViewer/Views/LoginView.axaml.cs b/GalaxyViewer/Views/LoginView.axaml.cs
index 04c3cd6..0283fdb 100644
--- a/GalaxyViewer/Views/LoginView.axaml.cs
+++ b/GalaxyViewer/Views/LoginView.axaml.cs
@@ -1,57 +1,19 @@
-using System.Diagnostics;
using Avalonia.Controls;
-using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
-using GalaxyViewer.Assets.Localization;
using GalaxyViewer.ViewModels;
-using MsBox.Avalonia;
-using MsBox.Avalonia.Windows;
-namespace GalaxyViewer.Views
+namespace GalaxyViewer.Views;
+
+public partial class LoginView : UserControl
{
- public partial class LoginView : UserControl
+ public LoginView()
{
- public LoginView()
- {
- InitializeComponent();
- }
+ InitializeComponent();
+ DataContext = new LoginViewModel();
+ }
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- // debug soi we can see the view
- Debug.WriteLine("LoginView initialized");
- }
-
- private async void LoginButton_Click(object sender, RoutedEventArgs e)
- {
- // 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
- );
- }
- */
- }
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Views/MainView.axaml b/GalaxyViewer/Views/MainView.axaml
index 26b195d..f57c292 100644
--- a/GalaxyViewer/Views/MainView.axaml
+++ b/GalaxyViewer/Views/MainView.axaml
@@ -43,19 +43,28 @@
+
+
-
+
-
+
\ No newline at end of file
diff --git a/GalaxyViewer/Views/MainView.axaml.cs b/GalaxyViewer/Views/MainView.axaml.cs
index fd394be..0470a37 100644
--- a/GalaxyViewer/Views/MainView.axaml.cs
+++ b/GalaxyViewer/Views/MainView.axaml.cs
@@ -1,29 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-using GalaxyViewer.Services;
-using GalaxyViewer.ViewModels;
-namespace GalaxyViewer.Views
+namespace GalaxyViewer.Views;
+
+public partial class MainView : UserControl
{
- public partial class MainView : UserControl
+ public MainView()
{
- public MainView()
- {
- InitializeComponent();
- var contentControl = this.FindControl("ContentControl");
- var navigationService = new NavigationService(contentControl);
+ InitializeComponent();
+ }
- // Register routes
- navigationService.RegisterRoute("login", typeof(LoginView));
- navigationService.RegisterRoute("debug", typeof(DebugView));
- navigationService.RegisterRoute("preferences", typeof(PreferencesView));
-
- DataContext = new MainViewModel(navigationService);
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Views/MainWindow.axaml b/GalaxyViewer/Views/MainWindow.axaml
index 37d0512..656e976 100644
--- a/GalaxyViewer/Views/MainWindow.axaml
+++ b/GalaxyViewer/Views/MainWindow.axaml
@@ -1,4 +1,5 @@
-
-
-
-
+
-
-
@@ -26,6 +19,6 @@
VerticalAlignment="Center" />
+ Command="{Binding SaveCommand}" HorizontalAlignment="Left" Margin="0,10" />
\ No newline at end of file
diff --git a/GalaxyViewer/Views/PreferencesView.axaml.cs b/GalaxyViewer/Views/PreferencesView.axaml.cs
index 53a49ae..c5aa8cb 100644
--- a/GalaxyViewer/Views/PreferencesView.axaml.cs
+++ b/GalaxyViewer/Views/PreferencesView.axaml.cs
@@ -1,20 +1,20 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using GalaxyViewer.Services;
using GalaxyViewer.ViewModels;
-namespace GalaxyViewer.Views
-{
- public partial class PreferencesView : UserControl
- {
- public PreferencesView()
- {
- InitializeComponent();
- DataContext = new PreferencesViewModel();
- }
+namespace GalaxyViewer.Views;
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
+public partial class PreferencesView : UserControl
+{
+ public PreferencesView()
+ {
+ InitializeComponent();
+ DataContext = new PreferencesViewModel();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Views/PreferencesWindow.axaml.cs b/GalaxyViewer/Views/PreferencesWindow.axaml.cs
index 33ed12a..f272fd1 100644
--- a/GalaxyViewer/Views/PreferencesWindow.axaml.cs
+++ b/GalaxyViewer/Views/PreferencesWindow.axaml.cs
@@ -2,19 +2,18 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using GalaxyViewer.ViewModels;
-namespace GalaxyViewer.Views
-{
- public partial class PreferencesWindow : Window
- {
- public PreferencesWindow()
- {
- InitializeComponent();
- DataContext = new PreferencesViewModel();
- }
+namespace GalaxyViewer.Views;
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
+public partial class PreferencesWindow : Window
+{
+ public PreferencesWindow()
+ {
+ InitializeComponent();
+ DataContext = new PreferencesViewModel();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
}
}
\ No newline at end of file
diff --git a/GalaxyViewer/Wrappers/AgentManagerWrapper.cs b/GalaxyViewer/Wrappers/AgentManagerWrapper.cs
new file mode 100644
index 0000000..c186027
--- /dev/null
+++ b/GalaxyViewer/Wrappers/AgentManagerWrapper.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Reflection;
+using OpenMetaverse;
+
+namespace GalaxyViewer.Wrappers;
+
+public class AgentManagerWrapper
+{
+ public AgentManagerWrapper(AgentManager agentManager)
+ {
+ var onChatEvent = agentManager.GetType().GetEvent("OnChat", BindingFlags.NonPublic | BindingFlags.Instance);
+ if (onChatEvent == null) return;
+ var handler = new EventHandler((sender, e) => OnChat?.Invoke(sender, e));
+ onChatEvent.AddEventHandler(agentManager, handler);
+ }
+
+ public event EventHandler? OnChat;
+}
\ No newline at end of file
diff --git a/GalaxyViewer/packages.lock.json b/GalaxyViewer/packages.lock.json
index 2517008..d8302fc 100644
--- a/GalaxyViewer/packages.lock.json
+++ b/GalaxyViewer/packages.lock.json
@@ -4,54 +4,54 @@
"net8.0": {
"Avalonia": {
"type": "Direct",
- "requested": "[11.1.3, )",
- "resolved": "11.1.3",
- "contentHash": "kpVAnKTbg/ZVnWyYLGPYXR0Ce4uiHLT1y6BcgJYpdFfn3b0TmvbxFZV37lpc1wEjUyaU28V+DoNnRbYSLsv0nw==",
+ "requested": "[11.2.1, )",
+ "resolved": "11.2.1",
+ "contentHash": "AyYhIN2A7bRwxp6BFHrIbXAHUFPXegzSMYwDrUnw1BzZs9ctwYTiCPCM5wbE2PXsEBwFDVJ/a2YHTOp56fSYAw==",
"dependencies": {
"Avalonia.BuildServices": "0.0.29",
- "Avalonia.Remote.Protocol": "11.1.3",
+ "Avalonia.Remote.Protocol": "11.2.1",
"MicroCom.Runtime": "0.11.0"
}
},
"Avalonia.Diagnostics": {
"type": "Direct",
- "requested": "[11.1.3, )",
- "resolved": "11.1.3",
- "contentHash": "1oJQivGbnBJL2a1TpQEt5jfvdn5+iclDdRn6JzPu54oPHv48iTnulT6mlKPTe4L9JX/bUjJsxhZxcyTh6dhhqQ==",
+ "requested": "[11.2.1, )",
+ "resolved": "11.2.1",
+ "contentHash": "axUWa4sZoe9HgUXPEDhbZXijL8ex+lwQGVwNQLmD299O7pCqKcYThjyG/eCETO/boqjKTt3H85LHEPx94BP9dg==",
"dependencies": {
- "Avalonia": "11.1.3",
- "Avalonia.Controls.ColorPicker": "11.1.3",
- "Avalonia.Controls.DataGrid": "11.1.3",
- "Avalonia.Themes.Simple": "11.1.3"
+ "Avalonia": "11.2.1",
+ "Avalonia.Controls.ColorPicker": "11.2.1",
+ "Avalonia.Controls.DataGrid": "11.2.1",
+ "Avalonia.Themes.Simple": "11.2.1"
}
},
"Avalonia.Fonts.Inter": {
"type": "Direct",
- "requested": "[11.1.3, )",
- "resolved": "11.1.3",
- "contentHash": "HN50EoZV/6vkPYEGOlugAeEUhzy9rgkmyiZmSCLXIX5XhfoHj5LerRjVkfttWEdjCYqDzeGq4EKOSGZDCblIUQ==",
+ "requested": "[11.2.1, )",
+ "resolved": "11.2.1",
+ "contentHash": "egEFQWLHuSzyWKolPy9u4qPor270N2GL/4CI33eBxr09chrUVQsOlxQ6zeWPiBLzzgv/lCrZhOMCAIWsOz3tNg==",
"dependencies": {
- "Avalonia": "11.1.3"
+ "Avalonia": "11.2.1"
}
},
"Avalonia.ReactiveUI": {
"type": "Direct",
- "requested": "[11.1.3, )",
- "resolved": "11.1.3",
- "contentHash": "MJQw+16d3ipzmlgPw+tMaNEWzYfU+LXS8ewkmKOxRD7HAL1SHHmEtd9YF1hk9n8qkz1kapg1PzAJwP+ZXsY7Ig==",
+ "requested": "[11.2.1, )",
+ "resolved": "11.2.1",
+ "contentHash": "SgjmPrkpAyxnG9z9Ms1Nj53xTvD2W00GQ0w+WGMrt3Jm8UNHha8b0LK1Gx9WT4Do/ggH51j76RfRdXchbardWw==",
"dependencies": {
- "Avalonia": "11.1.3",
- "ReactiveUI": "18.3.1",
- "System.Reactive": "5.0.0"
+ "Avalonia": "11.2.1",
+ "ReactiveUI": "20.1.1",
+ "System.Reactive": "6.0.1"
}
},
"Avalonia.Themes.Fluent": {
"type": "Direct",
- "requested": "[11.1.3, )",
- "resolved": "11.1.3",
- "contentHash": "/2O9HXnnqRVGQ6VUjLLOkCNQx1/gXu69yyzUmDgS13Q0LdbGaYUYzSnPFnIBtOABuVCmdwbu+W+YI6jSwnT0uw==",
+ "requested": "[11.2.1, )",
+ "resolved": "11.2.1",
+ "contentHash": "9YUzDmZO5oDppsoA3Igeu/v1cVi4xu8jdO6ZrBzXJXJ9mma/htK0Ub9+V1lRoCW/O70nQfBX+ZDpm0dca1PVgw==",
"dependencies": {
- "Avalonia": "11.1.3"
+ "Avalonia": "11.2.1"
}
},
"AvaloniaInside.Shell": {
@@ -68,69 +68,82 @@
},
"Irihi.Ursa": {
"type": "Direct",
- "requested": "[1.3.0, )",
- "resolved": "1.3.0",
- "contentHash": "Rt1Y2ktihh8U43cS2H74nG1LI9zwqEuWa04QPw3M79NGc3U7EKOGjgSgszksOTZ3SBSSgWwpOGuVhK6RJ9sN+g==",
+ "requested": "[1.6.0, )",
+ "resolved": "1.6.0",
+ "contentHash": "RwftirYL3prVg+iOUERmdu6mxD7IyEuDBKyoaP/GijPWRu9CRz6wPTTFlhW2v/bJW2xNxS36tlvi3VSu1X4VmQ==",
"dependencies": {
"Avalonia": "11.1.1",
- "Irihi.Avalonia.Shared": "0.1.9"
+ "Irihi.Avalonia.Shared": "0.2.1",
+ "Irihi.Avalonia.Shared.Contracts": "0.2.1"
+ }
+ },
+ "Irihi.Ursa.ReactiveUIExtension": {
+ "type": "Direct",
+ "requested": "[1.0.1, )",
+ "resolved": "1.0.1",
+ "contentHash": "ooXvGRVxXMWtkcTy3XWO6B2R4mEzzG044yETua8UUScwGXw51DL3rg3Z7ghysl/XHaDBIGUVdQ738J0pjdD78Q==",
+ "dependencies": {
+ "Avalonia.ReactiveUI": "11.1.1",
+ "Irihi.Ursa": "1.4.0"
}
},
"Irihi.Ursa.Themes.Semi": {
"type": "Direct",
- "requested": "[1.3.0, )",
- "resolved": "1.3.0",
- "contentHash": "voma7NoP0kGBWUPIHaHSNXqLepdiCASR7tDPKY+xyEzcBuM0hpBbsDDt3bNVDZyftanRdJZ4/BCNOYEMnlNV4g==",
+ "requested": "[1.6.0.2, )",
+ "resolved": "1.6.0.2",
+ "contentHash": "+J7Ug8FN9yswYPG8qznCWaIqR3cDgq2drWLMU8BZ5u1mIax9QYRjzDHiECmehNkfb8xzWYXPEQ+F2zqQocSHJg==",
"dependencies": {
"Avalonia": "11.1.1",
- "Irihi.Ursa": "1.3.0"
+ "Irihi.Ursa": "1.6.0"
}
},
"LibreMetaverse": {
"type": "Direct",
- "requested": "[2.1.2.688, )",
- "resolved": "2.1.2.688",
- "contentHash": "C2MlPYz7XOhhhBz2DAd7BrKlekEub/adqAob1QvcDH5Qt77pyO1h/e73s02FkdBPdIB84DpzY8SITIoVMbaznA==",
+ "requested": "[2.1.3.735, )",
+ "resolved": "2.1.3.735",
+ "contentHash": "NEKEcC41M0fB+/sj+MXVfS4EU2x1alCa6yv7a0XyTm/ClBe+hgcD6YiOPD6zHmEwcemGYwhXcMyXANapS7tw9Q==",
"dependencies": {
- "CSJ2K.Skia": "0.10.5.38",
- "LibreMetaverse.StructuredData": "2.1.2.688",
- "LibreMetaverse.Types": "2.1.2.688",
- "Microsoft.Extensions.ObjectPool": "8.0.7",
+ "CoreJ2K.Skia": "1.0.4.21",
+ "LibreMetaverse.StructuredData": "2.1.3.735",
+ "LibreMetaverse.Types": "2.1.3.735",
+ "MemoryPack": "1.21.3",
+ "Microsoft.Extensions.ObjectPool": "8.0.10",
"OggVorbisEncoder": "1.2.2",
- "Pfim": "0.11.2",
+ "Pfim": "0.11.3",
"SkiaSharp": "2.88.8",
- "SkiaSharp.NativeAssets.Linux": "2.88.8",
"System.Net.Http": "4.3.4",
"System.Net.NameResolution": "4.3.0",
"System.Threading.Channels": "8.0.0",
"XmlRpcCore": "3.1.3",
- "log4net": "2.0.17",
- "zlib.net-mutliplatform": "1.0.7"
+ "log4net": "3.0.2",
+ "zlib.net-mutliplatform": "1.0.8"
+ }
+ },
+ "LiteDB": {
+ "type": "Direct",
+ "requested": "[5.0.21, )",
+ "resolved": "5.0.21",
+ "contentHash": "ykJ7ffFl7P9YQKR/PLci6zupiLrsSCNkOTiw6OtzntH7d2kCYp5L1+3a/pksKgTEHcJBoPXFtg7VZSGVBseN9w==",
+ "dependencies": {
+ "System.Buffers": "4.5.1"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Direct",
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0"
}
},
"Semi.Avalonia": {
"type": "Direct",
- "requested": "[11.1.0.4, )",
- "resolved": "11.1.0.4",
- "contentHash": "soJvhZG2h4TU/6L4E7ireUxEFf2lOtADWDg+CZcOklKD9TTVh76ggQz+VYx8pEjoHLx1B0Gk1svxdp/Xrjf04w==",
+ "requested": "[11.2.1, )",
+ "resolved": "11.2.1",
+ "contentHash": "cVbHRgLq+eAMnWDywDQyV5ox+Au0XdhOUI1GPDTZZF5oqGtz1hBQmBGHjmnKZUopcEET/C6Qe4UvcaRJvoH+Eg==",
"dependencies": {
- "Avalonia": "11.1.0"
- }
- },
- "Serilog.AspNetCore": {
- "type": "Direct",
- "requested": "[8.0.2, )",
- "resolved": "8.0.2",
- "contentHash": "LNUd1bHsik2E7jSoCQFdeMGAWXjH7eUQ6c2pqm5vl+jGqvxdabYXxlrfaqApjtX5+BfAjW9jTA2EKmPwxknpIA==",
- "dependencies": {
- "Microsoft.Extensions.Logging": "8.0.0",
- "Serilog": "3.1.1",
- "Serilog.Extensions.Hosting": "8.0.0",
- "Serilog.Formatting.Compact": "2.0.0",
- "Serilog.Settings.Configuration": "8.0.2",
- "Serilog.Sinks.Console": "5.0.0",
- "Serilog.Sinks.Debug": "2.0.0",
- "Serilog.Sinks.File": "5.0.0"
+ "Avalonia": "11.2.1"
}
},
"Serilog.Sinks.Console": {
@@ -158,190 +171,124 @@
},
"Avalonia.Controls.ColorPicker": {
"type": "Transitive",
- "resolved": "11.1.3",
- "contentHash": "xhFYsnGUz/9O9X0iNzpNg1S1D3D5RdeYmF7T9jgAeGme6gY76tFJCYSWozzvtTKhLLXgpj8v2bTnIxd2DBIH9w==",
+ "resolved": "11.2.1",
+ "contentHash": "t8ViFwfIe6jCO5HvzPWOtwGNSMHYNc8XakWp76Rgy1MOiht8tHKry9cU7k40AHEYU6wVjiYBkl0c8zYZyyha1g==",
"dependencies": {
- "Avalonia": "11.1.3",
- "Avalonia.Remote.Protocol": "11.1.3"
+ "Avalonia": "11.2.1",
+ "Avalonia.Remote.Protocol": "11.2.1"
}
},
"Avalonia.Controls.DataGrid": {
"type": "Transitive",
- "resolved": "11.1.3",
- "contentHash": "Ypqjb74jzhxAYGW6DumBvJaxOsB1IB5XcXHL5KfhgkvMZ5aW3ol3q7IBwScBvkffo2wgYaVvxkwyaDEuyjlrmQ==",
+ "resolved": "11.2.1",
+ "contentHash": "UaNQrY86GBqMZqZ/N/5/wLzr4Emh2N405VZI/IgH0I8BoMrjnosNr+++D7BOcahMNce0lUZLOsFyy+OY02PUAw==",
"dependencies": {
- "Avalonia": "11.1.3",
- "Avalonia.Remote.Protocol": "11.1.3"
+ "Avalonia": "11.2.1",
+ "Avalonia.Remote.Protocol": "11.2.1"
}
},
"Avalonia.Remote.Protocol": {
"type": "Transitive",
- "resolved": "11.1.3",
- "contentHash": "ByXwboJC1jg13Qdxfs3/aX/yY4wi9n1XKT+a6+ckLnpSeesjQ0J6N1+wix7ISuGwFo0cyT0nXpvI4s2Zo+rkSA=="
+ "resolved": "11.2.1",
+ "contentHash": "aqEialxjir7DO/dOFf7BGN/yQ4/adSC5UuVfqBr/RUHOENSH6CqoHj8kmtmJxnuz7ESQFSB2+h1kLVnk5csiDw=="
},
"Avalonia.Themes.Simple": {
"type": "Transitive",
- "resolved": "11.1.3",
- "contentHash": "R/bLBXRzcKiDqq6unUL9zHsPFrnJezj4hK2+6hDtmRnd+JEHizEV/aK/fotP//0WROYFz23fJqH0SG0BOlXX6w==",
+ "resolved": "11.2.1",
+ "contentHash": "ToiYv8hhJ5gcEtD54VZv7NpBFiqGasj4bjFh/AtjXApiYOp8r3orFPX8Nsc3kHcUCvNNjbjAy9dmBG65nYePkw==",
"dependencies": {
- "Avalonia": "11.1.3"
+ "Avalonia": "11.2.1"
}
},
- "CSJ2K.Skia": {
+ "CoreJ2K": {
"type": "Transitive",
- "resolved": "0.10.5.38",
- "contentHash": "AQlj1YwH2IzOQgPtG9hmu2h27Q8FFUOpgi+VDYn2Uyi6pDte8JO7iJ/+OJeqlaVMX8O67HyVkCtUC/7NFHcRZA==",
+ "resolved": "1.0.4.21",
+ "contentHash": "s3ARP/MF+WeJHBt3j/5xai1/Wpn7xWeVKfxBec9v/LsD7tHv/cjcNvLDSbDkNQA6UU5NcfICx/MlVG51oz9P1Q=="
+ },
+ "CoreJ2K.Skia": {
+ "type": "Transitive",
+ "resolved": "1.0.4.21",
+ "contentHash": "FDsRs5y2jfpSOKZaQHEuvaY5AHGds3qYTwE0Xsr9ClVNkioq3Ma68MoMuZEaFSwvN/b6m0IAE7RRduOEGYcoXg==",
"dependencies": {
- "SkiaSharp": "2.88.8",
- "SkiaSharp.NativeAssets.Linux.NoDependencies": "2.88.8"
+ "CoreJ2K": "1.0.4.21",
+ "SkiaSharp": "2.88.8"
}
},
"DynamicData": {
"type": "Transitive",
- "resolved": "7.9.5",
- "contentHash": "xFwVha7o3qUtVYxco5p+7Urcztc/m1gmaEUxOG0i7LNe+vfCfyb0ECAsT2FLm3zOPHb0g8s9qVu5LfPKfRNVng==",
+ "resolved": "8.4.1",
+ "contentHash": "Mn1+fU/jqxgONEJq8KLQPGWEi7g/hUVTbjZyn4QM0sWWDAVOHPO9WjXWORSykwdfg/6S3GM15qsfz+2EvO+QAQ==",
"dependencies": {
- "System.Reactive": "5.0.0"
+ "System.Reactive": "6.0.0"
}
},
"Irihi.Avalonia.Shared": {
"type": "Transitive",
- "resolved": "0.1.9",
- "contentHash": "RBGGTM360aeAAE+VoS5ekZOKX81SsjgezwvK39/kDslpvugg45qnIumd1XadIp3xa31WLVjifFI85Z+9cMEv2g==",
+ "resolved": "0.2.1",
+ "contentHash": "rajX9CkBGNFzPASnsxP1jxBHg03H9BKa+iFTUifUsetlfTxRr3UeqSNGNIVlIIwekgQNMfbMFkmlvDAq7ebgEg==",
"dependencies": {
"Avalonia": "11.0.0"
}
},
+ "Irihi.Avalonia.Shared.Contracts": {
+ "type": "Transitive",
+ "resolved": "0.2.1",
+ "contentHash": "HJZlrD34jc2f0CSnXsggbfR++prpsMtSRLL2BbYXTFSyk3wshjvGCwUY+BW5IL2Z4S6b3JQln1nH04j1tjA/4g=="
+ },
"LibreMetaverse.StructuredData": {
"type": "Transitive",
- "resolved": "2.1.2.688",
- "contentHash": "CYuHgGuNOkO0msZNA3+lDsSTTieEtrolpCOllotx1SRMZ0oQwy5Zng6iKuSwnxhMhjvI16RUHfr1QSLed3n9jQ==",
+ "resolved": "2.1.3.735",
+ "contentHash": "6vuM8NOUcHHDHWAwX465TiiIncMPcJUcFW96pRF++ELKVbMjXv0jVYb4VbruHY1waMF7YGOHfYMM7oWxmVnZuQ==",
"dependencies": {
- "LibreMetaverse.Types": "2.1.2.688"
+ "LibreMetaverse.Types": "2.1.3.735"
}
},
"LibreMetaverse.Types": {
"type": "Transitive",
- "resolved": "2.1.2.688",
- "contentHash": "igfVq6XuljTJPUrGlRk5pTCsYUmd6rl7HDiJie18xSITVH/Dtq+gBFdQY0rQ2N511ahN2X8u53fd8l5kXV2ohw=="
+ "resolved": "2.1.3.735",
+ "contentHash": "RZyjfrnk+CQchQKh4VggCijuuMPSDwWk0lnfeK8JfMKfJib58OfDOVDtPS3pZK2tGGLoas+KagSHv/WaPxBBrw=="
},
"log4net": {
"type": "Transitive",
- "resolved": "2.0.17",
- "contentHash": "qnnDf/ubJzwm2i1xH7nRMjEDoD+ctse7nZDqb+p7L1PvZc6ykpMoEesWr1/9hFqlsbII2v9e8yyQHJhoDQh7ZA==",
+ "resolved": "3.0.2",
+ "contentHash": "4uSGqXB3xICuVS35obHawUpwHoopZ/v4mhYt8ZYaK6B85YXh1vIrlY1UgV/ixb1aMeU7JVCGanlmaUpVcnnbaw==",
"dependencies": {
"System.Configuration.ConfigurationManager": "4.5.0"
}
},
+ "MemoryPack": {
+ "type": "Transitive",
+ "resolved": "1.21.3",
+ "contentHash": "cwCtED8y400vMWx/Vp0QCSeEpVFjDU4JwF52VX9WTaqVERUvNqjG9n6osFlmFuytegyXnHvYEu1qRJ8rv/rkbg==",
+ "dependencies": {
+ "MemoryPack.Core": "1.21.3",
+ "MemoryPack.Generator": "1.21.3"
+ }
+ },
+ "MemoryPack.Core": {
+ "type": "Transitive",
+ "resolved": "1.21.3",
+ "contentHash": "ajrYoBWT2aKeH4tlY8q/1C9qK1R/NK+7FkuVOX58ebOSxkABoFTqCR7W+Zk2rakUHZiEgNdRqO67hiRZPq6fLA=="
+ },
+ "MemoryPack.Generator": {
+ "type": "Transitive",
+ "resolved": "1.21.3",
+ "contentHash": "hYU0TAIarDKnbkNIWvb7P4zBUL+CTahkuNkczsKvycSMR5kiwQ4IfLexywNKX3s05Izp4gzDSPbueepNWZRpWA=="
+ },
"MicroCom.Runtime": {
"type": "Transitive",
"resolved": "0.11.0",
"contentHash": "MEnrZ3UIiH40hjzMDsxrTyi8dtqB5ziv3iBeeU4bXsL/7NLSal9F1lZKpK+tfBRnUoDSdtcW3KufE4yhATOMCA=="
},
- "Microsoft.Extensions.Configuration.Abstractions": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==",
- "dependencies": {
- "Microsoft.Extensions.Primitives": "8.0.0"
- }
- },
- "Microsoft.Extensions.Configuration.Binder": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "8.0.0"
- }
- },
- "Microsoft.Extensions.DependencyInjection": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
- }
- },
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
- },
- "Microsoft.Extensions.DependencyModel": {
- "type": "Transitive",
- "resolved": "8.0.1",
- "contentHash": "5Ou6varcxLBzQ+Agfm0k0pnH7vrEITYlXMDuE6s7ZHlZHz6/G8XJ3iISZDr5rfwfge6RnXJ1+Wc479mMn52vjA==",
- "dependencies": {
- "System.Text.Encodings.Web": "8.0.0",
- "System.Text.Json": "8.0.4"
- }
- },
- "Microsoft.Extensions.Diagnostics.Abstractions": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
- "Microsoft.Extensions.Options": "8.0.0",
- "System.Diagnostics.DiagnosticSource": "8.0.0"
- }
- },
- "Microsoft.Extensions.FileProviders.Abstractions": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==",
- "dependencies": {
- "Microsoft.Extensions.Primitives": "8.0.0"
- }
- },
- "Microsoft.Extensions.Hosting.Abstractions": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "8.0.0",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
- "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0",
- "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0",
- "Microsoft.Extensions.Logging.Abstractions": "8.0.0"
- }
- },
- "Microsoft.Extensions.Logging": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection": "8.0.0",
- "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
- "Microsoft.Extensions.Options": "8.0.0"
- }
- },
- "Microsoft.Extensions.Logging.Abstractions": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
- }
+ "resolved": "9.0.0",
+ "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg=="
},
"Microsoft.Extensions.ObjectPool": {
"type": "Transitive",
- "resolved": "8.0.7",
- "contentHash": "2yLweyqmpuuFSRo+I3sLHMxmnAgNcI537kBJiyv49U2ZEqo00jZcG8lrnD8uCiOJp9IklYyTZULtbsXoFVzsjQ=="
- },
- "Microsoft.Extensions.Options": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
- "Microsoft.Extensions.Primitives": "8.0.0"
- }
- },
- "Microsoft.Extensions.Primitives": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
+ "resolved": "8.0.10",
+ "contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg=="
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
@@ -363,16 +310,17 @@
},
"Pfim": {
"type": "Transitive",
- "resolved": "0.11.2",
- "contentHash": "Wb1Y4XJERCPGzMf4Revxu0c812uKijhhIh/htTkiO86W2T7c/cGIeb2REvOV2DLq8+g155liFwwZEZIm95WCZw=="
+ "resolved": "0.11.3",
+ "contentHash": "UNVStuGHVIGyBlQaLX8VY6KpzZm/pG2zpV8ewNSXNFKFVPn8dLQKJITfps3lwUMzwTL+Do7RrMUvgQ1ZsPTu4w=="
},
"ReactiveUI": {
"type": "Transitive",
- "resolved": "18.3.1",
- "contentHash": "0tclGtjrRPfA2gbjiM7O3DeNmo6/TpDn7CMN6jgzDrbgrnysM7oEzjGEeXbtXaOxH6kEf6RiMKWobZoSgbBXhQ==",
+ "resolved": "20.1.1",
+ "contentHash": "9hNPknWjijnaSWs6auypoXqUptPZcRpUypF+cf1zD50fgW+SEoQda502N3fVZ2eWPcaiUad+z6GaLwOWmUVHNw==",
"dependencies": {
- "DynamicData": "7.9.5",
- "Splat": "14.4.1"
+ "DynamicData": "8.4.1",
+ "Splat": "15.1.1",
+ "System.ComponentModel.Annotations": "5.0.0"
}
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
@@ -478,53 +426,6 @@
"resolved": "4.0.0",
"contentHash": "2jDkUrSh5EofOp7Lx5Zgy0EB+7hXjjxE2ktTb1WVQmU00lDACR2TdROGKU0K1pDTBSJBN1PqgYpgOZF8mL7NJw=="
},
- "Serilog.Extensions.Hosting": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "db0OcbWeSCvYQkHWu6n0v40N4kKaTAXNjlM3BKvcbwvNzYphQFcBR+36eQ/7hMMwOkJvAyLC2a9/jNdUL5NjtQ==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
- "Microsoft.Extensions.Hosting.Abstractions": "8.0.0",
- "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
- "Serilog": "3.1.1",
- "Serilog.Extensions.Logging": "8.0.0"
- }
- },
- "Serilog.Extensions.Logging": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==",
- "dependencies": {
- "Microsoft.Extensions.Logging": "8.0.0",
- "Serilog": "3.1.1"
- }
- },
- "Serilog.Formatting.Compact": {
- "type": "Transitive",
- "resolved": "2.0.0",
- "contentHash": "ob6z3ikzFM3D1xalhFuBIK1IOWf+XrQq+H4KeH4VqBcPpNcmUgZlRQ2h3Q7wvthpdZBBoY86qZOI2LCXNaLlNA==",
- "dependencies": {
- "Serilog": "3.1.0"
- }
- },
- "Serilog.Settings.Configuration": {
- "type": "Transitive",
- "resolved": "8.0.2",
- "contentHash": "hn8HCAmupon7N0to20EwGeNJ+L3iRzjGzAHIl8+8CCFlEkVedHvS6NMYMb0VPNMsDgDwOj4cPBPV6Fc2hb0/7w==",
- "dependencies": {
- "Microsoft.Extensions.Configuration.Binder": "8.0.0",
- "Microsoft.Extensions.DependencyModel": "8.0.1",
- "Serilog": "3.1.1"
- }
- },
- "Serilog.Sinks.Debug": {
- "type": "Transitive",
- "resolved": "2.0.0",
- "contentHash": "Y6g3OBJ4JzTyyw16fDqtFcQ41qQAydnEvEqmXjhwhgjsnG/FaJ8GUqF5ldsC/bVkK8KYmqrPhDO+tm4dF6xx4A==",
- "dependencies": {
- "Serilog": "2.10.0"
- }
- },
"SkiaSharp": {
"type": "Transitive",
"resolved": "2.88.8",
@@ -534,22 +435,6 @@
"SkiaSharp.NativeAssets.macOS": "2.88.8"
}
},
- "SkiaSharp.NativeAssets.Linux": {
- "type": "Transitive",
- "resolved": "2.88.8",
- "contentHash": "0FO6YA7paNFBMJULvEyecPmCvL9/STvOAi5VOUw2srqJ7pNTbiiZkfl7sulAzcumbWgfzaVjRXYTgMj7SoUnWQ==",
- "dependencies": {
- "SkiaSharp": "2.88.8"
- }
- },
- "SkiaSharp.NativeAssets.Linux.NoDependencies": {
- "type": "Transitive",
- "resolved": "2.88.8",
- "contentHash": "/DoKtdyvRgCC5GR/SH+ps3ZiOjmf0BYpAyrhWQELFOO1hdcqddrDVJjDNCOJ41vV+NlS5b3kcDoZZ7jLhFjyXg==",
- "dependencies": {
- "SkiaSharp": "2.88.8"
- }
- },
"SkiaSharp.NativeAssets.macOS": {
"type": "Transitive",
"resolved": "2.88.8",
@@ -562,8 +447,13 @@
},
"Splat": {
"type": "Transitive",
- "resolved": "14.4.1",
- "contentHash": "Z1Mncnzm9pNIaIbZ/EWH6x5ESnKsmAvu8HP4StBRw+yhz0lzE7LCbt22TNTPaFrYLYbYCbGQIc/61yuSnpLidg=="
+ "resolved": "15.1.1",
+ "contentHash": "RHDTdF90FwVbRia2cmuIzkiVoETqnXSB2dDBBi/I35HWXqv4OKGqoMcfcd6obMvO2OmmY5PjU1M62K8LkJafAA=="
+ },
+ "System.Buffers": {
+ "type": "Transitive",
+ "resolved": "4.5.1",
+ "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
},
"System.Collections": {
"type": "Transitive",
@@ -592,6 +482,11 @@
"System.Threading.Tasks": "4.3.0"
}
},
+ "System.ComponentModel.Annotations": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
+ },
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
"resolved": "4.5.0",
@@ -613,8 +508,15 @@
},
"System.Diagnostics.DiagnosticSource": {
"type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ=="
+ "resolved": "4.3.0",
+ "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
},
"System.Diagnostics.Tracing": {
"type": "Transitive",
@@ -779,8 +681,8 @@
},
"System.Reactive": {
"type": "Transitive",
- "resolved": "5.0.0",
- "contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
+ "resolved": "6.0.1",
+ "contentHash": "rHaWtKDwCi9qJ3ObKo8LHPMuuwv33YbmQi7TcUK1C264V3MFnOr5Im7QgCTdLniztP3GJyeiSg5x8NqYJFqRmg=="
},
"System.Reflection": {
"type": "Transitive",
@@ -1053,19 +955,6 @@
"System.Runtime": "4.3.0"
}
},
- "System.Text.Encodings.Web": {
- "type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
- },
- "System.Text.Json": {
- "type": "Transitive",
- "resolved": "8.0.4",
- "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==",
- "dependencies": {
- "System.Text.Encodings.Web": "8.0.0"
- }
- },
"System.Threading": {
"type": "Transitive",
"resolved": "4.3.0",
@@ -1102,8 +991,8 @@
},
"zlib.net-mutliplatform": {
"type": "Transitive",
- "resolved": "1.0.7",
- "contentHash": "dCGM9phfmCXqWxuM45Q9+hgPYRT6m9sRHFLsJ2wmlYXcmMqkkFua1ho4ulVnIQZ+qeUu5ov3pPjptfofZsCC2w=="
+ "resolved": "1.0.8",
+ "contentHash": "6sYoMfSmDdfG8HgxXi0ydwCMUv5Ia1gMRh2Q0JLlTUxE9ZeWJ9dir/1Hq5MW6JcOZARRsZIOGecVx6WuAoloEA=="
}
}
}
diff --git a/ReadMe.md b/ReadMe.md
index 95aeb62..227ff8e 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -1,6 +1,6 @@
# GalaxyViewer
-GalaxyViewer is a viewer for Second Life and OpenSimulator. It is heavily inspired by Radegast, using the Avalonia UI framework and .NET Core. It should work on Windows, Linux, and Android. iOS and macOS may be supported in the future.
+GalaxyViewer is a viewer for Second Life and OpenSimulator. It is heavily inspired by Radegast and Lumiya, using the Avalonia UI framework and .NET Core. It should work on Windows, Linux, and Android. iOS and macOS may be supported in the future.
Features unique to this viewer (compared to the stock viewer) will include:
@@ -12,7 +12,7 @@ Features unique to this viewer (compared to the stock viewer) will include:
- [ ] User Authentication
- - [ ] Login
+ - [x] Login
- [ ] MFA Support
- [ ] Communication
@@ -51,18 +51,25 @@ Features unique to this viewer (compared to the stock viewer) will include:
- [ ] User Interface
- - [ ] Light and Dark Modes
+ - [x] Light and Dark Modes
- [ ] Customizable UI
- [ ] Customizable Keybinds
- [ ] Customizable Notifications
- [ ] Grid Manager
-- [ ] Preferences
+- [x] Preferences
- [ ] Plugin System
- [ ] RLV Support
- [ ] Automation Support
- [ ] Accessibility
-- [ ] Discord Rich Presence
+ - [ ] Screen Reader Support
+ - [ ] High Contrast Mode
+ - [ ] Keyboard Navigation
+ - [ ] Voice Commands
+ - [ ] Text-to-Speech
+ - [ ] Speech-to-Text
+- [x] Localization
+- [ ] Discord Rich Presence (Desktop only)
## Installation
@@ -70,7 +77,7 @@ To be added...
## Building
-Make sure you have the [.NET Core SDK](https://dotnet.microsoft.com/download) installed. We use .NET 8.0.
+Make sure you have the [.NET Core SDK](https://dotnet.microsoft.com/download) installed. We currently use .NET 8.0.
Clone the repository and navigate to the project directory.
@@ -89,7 +96,7 @@ Also, please note the [Code of Conduct](CODE_OF_CONDUCT.md) for this project.
Please note that while the viewer is in the early development stage, it is not recommended for everyday use and no support will be given. This ReadMe will be updated with support information once the viewer is in a more stable state.
-If you have any questions or concerns, please contact [Galaxy Littlepaws](mailto:support@galaxyviewer.com) and include "Galaxy Viewer" in the subject line.
+If you have any questions or concerns, please feel free to use our [Discussion](https://github.com/GalaxyViewer/GalaxyViewer/discussions) forum.
## License
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..dad2db5
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "8.0.0",
+ "rollForward": "latestMajor",
+ "allowPrerelease": true
+ }
+}
\ No newline at end of file