Update Dev (#23)

*  Add Last LoginLocation dropdown to LoginView

* Create FUNDING.yml

Signed-off-by: Galaxy Littlepaws <34807062+GalaxyLittlepaws@users.noreply.github.com>

* 💄 Font family is now fixed, for now Atkinson Hyperlegible default

*  Font can now bhe changed via preferences

* 🚧 Some Login functionality is added, now viewer name and version number are from props

* 🚧 Added error display for logins

* 📦️ Upgrade Avalonia, LibreMetaverse, and assorted packages

*  Add beginning of Login system

* 📦️ Update AbaloniaBrowser, merge in from dev

* 📝 Add CHANGELOG.md

* 💄 Updated UI in various views, added new Logo

* Update FUNDING.yml

Signed-off-by: Galaxy Littlepaws <34807062+GalaxyLittlepaws@users.noreply.github.com>

* Update FUNDING.yml

Signed-off-by: Galaxy Littlepaws <34807062+GalaxyLittlepaws@users.noreply.github.com>

* 📦️ Updates packages in solution

* 📦️ Add Litedb

* 🚧 Cleans up some code

* 🚧 Formatting

*  We now use normal routing and LiteDb for storing preferences and such, fixes crash

*  📦 🐛 We now use normal routing and LiteDb for storing preferences and such, fixes crash, Updates to AvaloniaUI 11.2.0

* 🚧 Progress towards grid selection feature, database improvements

* 🐛 Fixes issues with updating packages in Android project

* 📝 🐛 Updates ReadMe, updates to prevent issues with Android building

* Updates ReadMe.md (#20)

Signed-off-by: Galaxy Littlepaws <34807062+GalaxyLittlepaws@users.noreply.github.com>

* 🚧 Progress towards working Android and Desktop builds, various Android tweaks, additions to ReadMe

* 🚧 Progress towards working Android and Desktop builds, various Android tweaks, additions to ReadMe

* ♻️ Some cleanup

* 🚧 Ursa components should work now

---------

Signed-off-by: Galaxy Littlepaws <34807062+GalaxyLittlepaws@users.noreply.github.com>
This commit is contained in:
Galaxy Littlepaws
2024-11-25 07:37:27 -05:00
committed by GitHub
parent 5a4073df25
commit 718b23c970
54 changed files with 1495 additions and 1082 deletions
+15
View File
@@ -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']
+10
View File
@@ -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
+8 -7
View File
@@ -1,9 +1,10 @@
<Project>
<PropertyGroup>
<Version>0.1.0</Version>
<Product>GalaxyViewer</Product>
<Authors>Galaxy Littlepaws</Authors>
<Nullable>enable</Nullable>
<AvaloniaVersion>11.0.10</AvaloniaVersion>
</PropertyGroup>
<PropertyGroup>
<Version>0.1.0</Version>
<Product>GalaxyViewer</Product>
<Description>GalaxyViewer is a cross-platform viewer for Second Life and NGC OpenSimulator.</Description>
<Authors>Galaxy Littlepaws</Authors>
<Nullable>enable</Nullable>
<AvaloniaVersion>11.1.0</AvaloniaVersion>
</PropertyGroup>
</Project>
@@ -1,28 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ApplicationId>com.GalaxyViewer.GalaxyViewer</ApplicationId>
<ApplicationVersion>1</ApplicationVersion>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<AndroidPackageFormat>apk</AndroidPackageFormat>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-android34.0</TargetFramework>
<AndroidUseLatestPlatformSdk>true</AndroidUseLatestPlatformSdk>
<AndroidSdkVersion>30</AndroidSdkVersion>
<SupportedOSPlatformVersion>24.0</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ApplicationId>com.GalaxyViewer.GalaxyViewer</ApplicationId>
<ApplicationVersion>1</ApplicationVersion>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<AndroidPackageFormat>apk</AndroidPackageFormat>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
</PropertyGroup>
<ItemGroup>
<AndroidResource Include="Icon.png">
<Link>Resources\drawable\Icon.png</Link>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Android" Version="11.2.1"/>
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.13"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Android" Version="11.1.3" />
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GalaxyViewer\GalaxyViewer.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GalaxyViewer\GalaxyViewer.csproj"/>
</ItemGroup>
</Project>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/obj/Debug/net8.0-android34.0/android/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

+66 -15
View File
@@ -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<App>
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<App>
{
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();
}
}
}
}
@@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<application android:label="GalaxyViewer" android:icon="@drawable/Icon" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto"
package="com.example.yourapp">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
<application
android:label="GalaxyViewer"
android:icon="@drawable/icon"
android:allowBackup="true"
android:supportsRtl="true">
<!-- Your activities and other components -->
</application>
</manifest>
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_background">#212121</color>
<color name="splash_background">#0f1729</color>
</resources>
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_background">#FFFFFF</color>
<color name="splash_background">#0f1729</color>
</resources>
@@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-browser</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<WasmMainJSPath>wwwroot\main.js</WasmMainJSPath>
<WasmMainJSPath>wwwroot/main.js</WasmMainJSPath>
<WasmRuntimeAssetsLocation>./_framework</WasmRuntimeAssetsLocation>
</PropertyGroup>
<ItemGroup>
<WasmExtraFilesToDeploy Include="wwwroot\**" />
<WasmExtraFilesToDeploy Include="wwwroot/**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Browser" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Browser" Version="11.1.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GalaxyViewer\GalaxyViewer.csproj" />
</ItemGroup>
</Project>
</Project>
@@ -13,9 +13,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.1" />
</ItemGroup>
<ItemGroup>
+3
View File
@@ -1,2 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeEditing/SuppressNullableWarningFix/Enabled/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=hyperlegible/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=metaverse/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+4 -2
View File
@@ -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">
<Application.DataTemplates>
<local:ViewLocator />
@@ -10,7 +11,7 @@
<Application.Styles>
<semi:SemiTheme Locale="en-US" />
<u-semi:SemiTheme Locale="en-US"/>
<u-Semi:SemiTheme Locale="en-US" />
<StyleInclude Source="avares://GalaxyViewer/Styles.axaml" />
</Application.Styles>
<Application.Resources>
@@ -18,6 +19,7 @@
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://GalaxyViewer/Styles/Resources.axaml" />
</ResourceDictionary.MergedDictionaries>
<converters:LocalizedStringConverter x:Key="LocalizedStringConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
+87 -47
View File
@@ -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<LiteDbService>();
// Register other services here
}
public override void Initialize()
{
PreferencesManager = new PreferencesManager();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
_serviceProvider = serviceCollection.BuildServiceProvider();
var liteDbService = _serviceProvider.GetService<LiteDbService>();
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();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+119
View File
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="61.167931mm"
height="66.846802mm"
viewBox="0 0 61.167931 66.846802"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
sodipodi:docname="Logo.svg"
inkscape:export-filename="Logo.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="3.3763328"
inkscape:cx="81.745497"
inkscape:cy="150.01483"
inkscape:window-width="2510"
inkscape:window-height="1412"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g7"
inkscape:export-bgcolor="#ffffff00" /><defs
id="defs1" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-71.41723,-72.385321)"><g
id="g7"
transform="matrix(1.0018151,0,0,0.98691065,58.164341,-135.54191)"><g
style="fill:#000000"
id="g2-3"
transform="matrix(0.13225757,0,0,0.13225757,9.8908307,210.68501)"><g
id="SVGRepo_bgCarrier-8"
stroke-width="0" /><g
id="SVGRepo_tracerCarrier-99"
stroke-linecap="round"
stroke-linejoin="round" /><g
id="SVGRepo_iconCarrier-1"
style="stroke:none;stroke-width:0.998053;stroke-dasharray:none;stroke-opacity:1"> <g
transform="translate(1)"
id="g5-7"
style="stroke:none;stroke-width:0.998053;stroke-dasharray:none;stroke-opacity:1"> <polygon
style="fill:#aab3c7;fill-opacity:1;stroke:none;stroke-width:0.998053;stroke-dasharray:none;stroke-opacity:1"
points="254.639,230.532 254.639,503.599 32.773,392.665 32.773,119.599 "
id="polygon1-5" /> <polygon
style="fill:#a3abc2;fill-opacity:1;stroke:none;stroke-width:0.998053;stroke-dasharray:none;stroke-opacity:1"
points="254.639,230.532 254.639,503.599 476.506,392.665 476.506,119.599 "
id="polygon2-3" /> <polygon
style="fill:#000040;stroke:none;stroke-width:0.998053;stroke-dasharray:none;stroke-opacity:1"
points="254.639,230.532 32.773,119.599 246.106,8.665 476.506,119.599 "
id="polygon3-3" /> <path
d="m 254.639,512.132 c -1.707,0 -2.56,0 -3.413,-0.853 L 29.359,400.345 c -3.413,-1.707 -5.12,-4.267 -5.12,-7.68 V 119.599 c 0,-2.56 1.707,-5.973 4.267,-7.68 2.56,-1.707 5.973,-1.707 8.533,0 l 221.867,110.933 c 2.56,1.707 5.12,4.267 5.12,7.68 v 273.067 c 0,2.56 -1.707,5.973 -4.267,7.68 -1.706,0 -3.413,0.853 -5.12,0.853 z M 41.306,387.545 l 204.8,102.4 V 235.652 l -204.8,-102.4 z"
id="path3-4"
style="stroke:none;stroke-width:0.998053;stroke-dasharray:none;stroke-opacity:1" /> <path
d="m 254.639,512.132 c -1.707,0 -3.413,0 -4.267,-0.853 -2.56,-1.707 -4.267,-5.12 -4.267,-7.68 V 230.532 c 0,-3.413 1.707,-5.973 5.12,-7.68 L 473.092,111.919 c 2.56,-1.707 5.973,-0.853 8.533,0 2.56,1.707 4.267,4.267 4.267,7.68 v 273.067 c 0,3.413 -1.707,5.973 -5.12,7.68 L 258.906,511.279 c -1.707,0.853 -2.56,0.853 -4.267,0.853 z m 8.534,-276.48 v 254.293 l 204.8,-102.4 V 133.252 Z"
id="path4-6"
style="stroke:none;stroke-width:0.998053;stroke-dasharray:none;stroke-opacity:1" /> <path
d="m 254.639,239.065 c -1.707,0 -2.56,0 -3.413,-0.853 L 29.359,127.279 c -3.413,-1.707 -5.12,-4.267 -5.12,-7.68 0,-3.413 1.707,-5.973 4.267,-7.68 L 241.839,0.985 c 2.56,-0.853 5.12,-1.707 7.68,0 l 230.4,110.933 c 2.56,1.707 5.12,4.267 5.12,7.68 0,3.413 -1.707,5.973 -5.12,7.68 L 258.053,238.212 c -0.854,0.853 -1.707,0.853 -3.414,0.853 z M 51.546,119.599 254.639,221.146 456.879,119.599 246.106,18.052 Z"
id="path5-9"
style="stroke:none;stroke-width:0.998053;stroke-dasharray:none;stroke-opacity:1" /> </g> </g></g><g
style="fill:#000000"
id="g3-0"
transform="matrix(0.4612168,-0.2297859,0,0.52234532,44.594091,247.23515)"><g
id="g1-9-2">
<path
d="M 51.751,23.435 V 5.474 H 44.645 V 31.449 H 40.562 V 19.958 H 30.103 V 12.096 H 20.906 V 30.538 H 18.624 V 24.796 H 7.902 V 16.934 H 0 v 7.862 5.742 24.195 h 16.371 2.253 2.282 7.561 1.636 5.268 5.191 19.645 V 38.826 31.449 23.434 H 51.751 Z M 4.843,26.899 H 2.239 v -6.183 h 2.604 z m 6.25,12.021 H 8.489 V 28.439 h 2.604 z m 4.297,-4.297 h -2.604 v -6.184 h 2.604 z m 9.765,-4.632 h -2.604 v -6.183 h 2.604 z m 0,-8.887 H 22.551 V 14.92 h 2.604 z m 4.428,17.528 H 26.979 V 32.45 h 2.604 z M 34.4,29.991 H 31.796 V 23.808 H 34.4 Z m 14.713,5.549 h -2.604 v -6.184 h 2.604 z m 0,-11.345 h -2.604 v -6.183 h 2.604 z m 0,-10.31 H 46.509 V 7.703 h 2.604 z m 5.469,26.894 H 51.978 V 30.3 h 2.604 z"
id="path1-19" />
</g></g><g
style="fill:#000000;fill-opacity:1"
id="g4-4"
transform="matrix(0.31699982,0.16860553,0,0.35510983,13.001511,229.21359)"><g
id="SVGRepo_bgCarrier-9-3"
stroke-width="0"
style="fill:#000000;fill-opacity:1" /><g
id="SVGRepo_tracerCarrier-7-6"
stroke-linecap="round"
stroke-linejoin="round"
style="fill:#000000;fill-opacity:1" /><g
id="SVGRepo_iconCarrier-7-4"
style="fill:#000000;fill-opacity:1"> <g
id="g1-99-1"
style="fill:#000000;fill-opacity:1"> <path
fill="#231f20"
d="m 91.963,80.982 0.023,-0.013 -7.285,-12.617 h 2.867 v -0.013 c 0.598,0 1.083,-0.484 1.083,-1.082 0,-0.185 -0.059,-0.351 -0.14,-0.503 L 88.53,66.743 81.793,55.074 h 1.639 v -0.009 c 0.427,0 0.773,-0.347 0.773,-0.772 0,-0.132 -0.042,-0.25 -0.1,-0.359 l 0.013,-0.008 -9.802,-16.979 -0.01,0.006 c -0.216,-0.442 -0.66,-0.754 -1.186,-0.754 -0.524,0 -0.968,0.311 -1.185,0.752 l -0.005,-0.003 -9.802,16.978 0.002,10e-4 c -0.061,0.11 -0.105,0.231 -0.105,0.366 0,0.426 0.346,0.772 0.773,0.772 v 0.009 h 1.661 l -6.737,11.669 0.003,0.001 c -0.085,0.155 -0.147,0.324 -0.147,0.513 0,0.598 0.485,1.082 1.083,1.082 v 0.013 h 2.894 l -2.1,3.638 -8.399,-14.548 h 4.046 v -0.018 c 0.844,0 1.528,-0.685 1.528,-1.528 0,-0.26 -0.071,-0.502 -0.186,-0.717 L 56.459,55.17 46.952,38.703 h 2.313 v -0.012 c 0.603,0 1.091,-0.488 1.091,-1.092 0,-0.186 -0.059,-0.353 -0.141,-0.506 L 50.234,37.082 36.4,13.125 36.395,13.128 c -0.305,-0.625 -0.94,-1.06 -1.683,-1.06 -0.758,0 -1.408,0.452 -1.704,1.1 l -13.807,23.914 0.003,0.002 c -0.086,0.156 -0.148,0.326 -0.148,0.516 0,0.604 0.488,1.092 1.09,1.092 v 0.012 h 2.345 l -9.395,16.272 c -0.195,0.257 -0.316,0.573 -0.316,0.92 0,0.844 0.685,1.528 1.528,1.528 v 0.018 h 4.084 L 8.252,75.007 c -0.24,0.314 -0.387,0.702 -0.387,1.128 0,1.032 0.838,1.87 1.871,1.87 v 0.021 h 19.779 v 8.43 c 0,0.815 0.661,1.477 1.476,1.477 h 7.383 c 0.815,0 1.477,-0.661 1.477,-1.477 v -8.43 h 16.12 l -1.699,2.943 0.003,0.002 c -0.104,0.189 -0.18,0.396 -0.18,0.628 0,0.732 0.593,1.325 1.325,1.325 v 0.015 h 14.016 v 3.941 c 0,0.578 0.469,1.046 1.046,1.046 h 5.232 c 0.578,0 1.046,-0.468 1.046,-1.046 v -3.941 h 14.05 v -0.015 c 0.732,0 1.326,-0.593 1.326,-1.325 -10e-4,-0.227 -0.072,-0.431 -0.173,-0.617 z"
id="path1-7-6"
style="fill:#000000;fill-opacity:1" /> </g> </g></g><g
style="fill:#a2aac3;fill-opacity:1"
id="g6"
transform="matrix(0.04838256,0,0.0277314,0.02641433,19.746211,217.7951)"><g
id="SVGRepo_bgCarrier-3-7"
stroke-width="0"
style="fill:#a2aac3;fill-opacity:1" /><g
id="SVGRepo_tracerCarrier-9-7"
stroke-linecap="round"
stroke-linejoin="round"
style="fill:#a2aac3;fill-opacity:1" /><g
id="SVGRepo_iconCarrier-4-8"
style="fill:#a2aac3;fill-opacity:1"> <g
id="g1-4-4"
style="fill:#a2aac3;fill-opacity:1"> <path
d="M 611.502,203.367 C 607.613,150.151 580.581,102.529 518.226,78.361 448.865,51.477 366.266,65.328 301.456,95.174 233.672,126.39 150.143,189.109 129.774,266.779 c -7.962,30.357 -5.303,63.94 10.093,91.288 19.789,35.152 59.002,56.863 99.094,61.324 55.248,6.147 112.773,-16.493 155.435,-50.741 24.317,-19.521 46.5,-44.992 58.997,-73.774 11.212,-25.821 13.864,-56.419 1.925,-81.911 -16.243,-34.679 -54.683,-49.835 -91.121,-44.132 -4.787,0.735 -9.337,1.776 -13.657,3.016 -4.322,1.234 -8.419,2.662 -12.295,4.256 -7.765,3.148 -14.653,6.919 -20.773,10.925 -6.109,4.03 -11.447,8.348 -16.022,12.747 -1.037,1.043 -2.056,2.067 -3.054,3.071 -1.08,1.074 -2.021,2.094 -2.964,3.1 -1.877,2.012 -3.588,3.995 -5.207,5.882 -3.208,3.799 -5.955,7.307 -8.307,10.499 -4.731,6.35 -8.047,11.323 -10.284,14.671 -1.1,1.684 -1.934,2.961 -2.494,3.817 -0.55,0.863 -0.829,1.301 -0.829,1.301 -0.92,1.449 -0.97,3.367 0.036,4.894 1.366,2.076 4.157,2.652 6.233,1.285 0,0 0.433,-0.285 1.288,-0.846 0.846,-0.567 2.108,-1.413 3.772,-2.529 3.307,-2.238 8.187,-5.589 14.48,-9.753 6.333,-4.129 14.012,-9.048 23.248,-13.695 2.175,-1.069 4.431,-2.096 6.846,-3.028 2.399,-0.946 4.893,-1.86 7.522,-2.638 5.229,-1.598 10.883,-2.863 16.803,-3.589 2.961,-0.352 5.978,-0.597 9.027,-0.671 3.05,-0.067 6.128,0.03 9.183,0.299 10.759,1.003 24.149,4.372 30.659,13.714 4.245,6.092 4.815,14.123 3.391,21.409 -2.471,12.644 -10.28,23.585 -18.883,33.174 -30.278,33.744 -74.119,56.946 -119.526,60.718 -37.814,2.947 -73.075,-14.422 -78.121,-55.732 -3.607,-29.523 10.253,-58.809 29.349,-81.612 26.954,-32.186 60.518,-59.114 98.754,-76.011 14.09,-6.227 28.7,-11.46 43.667,-15.311 39.803,-10.321 86.856,-13.345 125.381,3.353 36.924,16.004 60.458,52.144 59.714,92.483 -0.223,11.808 -2.412,23.851 -6.114,35.878 -3.66,12.037 -8.996,24.017 -15.499,35.56 -6.539,11.542 -14.238,22.664 -22.764,33.124 C 454.222,364.819 387.62,404.605 326.223,430.7 252.114,462.198 110.559,499.434 57.635,412.857 30.047,367.726 40.167,309.994 55.133,262.396 c 0.541,-1.722 0.006,-3.684 -1.497,-4.867 -1.942,-1.528 -4.754,-1.193 -6.282,0.748 0,0 -1.277,1.622 -3.752,4.764 -0.624,0.768 -1.287,1.718 -2.039,2.741 -0.746,1.027 -1.563,2.153 -2.451,3.375 -22.006,30.295 -36.716,66.932 -38.848,104.465 -1.962,34.546 6.992,69.97 26.952,98.236 23.939,33.9 62.223,55.79 102.375,66.286 63.644,16.637 134.014,11.022 196.48,-7.885 14.716,-4.454 29.379,-9.645 43.893,-15.61 14.505,-5.984 28.826,-12.8 42.955,-20.315 51.202,-27.233 99.882,-64.099 136.409,-109.452 39.462,-48.997 66.735,-119.103 62.174,-181.515 z"
id="path1-1-2"
style="fill:#a2aac3;fill-opacity:1" /> </g> </g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 10 KiB

@@ -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));
}
}
}
@@ -25,12 +25,6 @@
</resheader>
<!-- Add your English strings here -->
<!-- General Stuff -->
<data name="ViewerName" xml:space="preserve">
<value>GalaxyViewer</value>
</data>
<data name="Version" xml:space="preserve">
<value>0.1</value>
</data>
<data name="WelcomeMessage" xml:space="preserve">
<value>Welcome to GalaxyViewer 🌌</value>
</data>
@@ -23,12 +23,6 @@ This is in English (United States) language, as the default. It is in case there
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!-- General Stuff -->
<data name="ViewerName" xml:space="preserve">
<value>GalaxyViewer</value>
</data>
<data name="Version" xml:space="preserve">
<value>0.1</value>
</data>
<data name="WelcomeMessage" xml:space="preserve">
<value>Welcome to GalaxyViewer 🌌</value>
</data>
-57
View File
@@ -1,57 +0,0 @@
<llsd>
<array>
<!-- Second Life -->
<map>
<key>gridnick</key>
<string>agni</string>
<key>gridname</key>
<string>Second Life (agni)</string>
<key>platform</key>
<string>SecondLife</string>
<key>loginuri</key>
<string>https://login.agni.lindenlab.com/cgi-bin/login.cgi</string>
<key>loginpage</key>
<string>http://secondlife.com/app/login/?channel=Second+Life+Release</string>
<key>helperuri</key>
<string>https://secondlife.com/helpers/</string>
<key>website</key>
<string>http://secondlife.com/</string>
<key>support</key>
<string>http://secondlife.com/support/</string>
<key>register</key>
<string>http://secondlife.com/registration/</string>
<key>password</key>
<string>http://secondlife.com/account/request.php</string>
<key>version</key>
<string>0</string>
</map>
<!-- Second Life Beta -->
<map>
<key>gridnick</key>
<string>aditi</string>
<key>gridname</key>
<string>Second Life Beta (aditi)</string>
<key>platform</key>
<string>SecondLife</string>
<key>loginuri</key>
<string>https://login.aditi.lindenlab.com/cgi-bin/login.cgi</string>
<key>loginpage</key>
<string>http://secondlife.com/app/login/?channel=Second+Life+Beta</string>
<key>helperuri</key>
<string>http://aditi-secondlife.webdev.lindenlab.com/helpers/</string>
<key>website</key>
<string>http://secondlife.com/</string>
<key>support</key>
<string>http://secondlife.com/support/</string>
<key>register</key>
<string>http://secondlife.com/registration/</string>
<key>password</key>
<string>http://secondlife.com/account/request.php</string>
<key>version</key>
<string>1</string>
</map>
</array>
</llsd>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferencesModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Theme>Default</Theme>
<LoginLocation>Home</LoginLocation>
<Font>Atkinson Hyperlegible</Font>
<Language>en-US</Language>
<LastSavedEpoch>1721172337</LastSavedEpoch>
</PreferencesModel>
+18 -19
View File
@@ -2,27 +2,26 @@
using System.Threading.Tasks;
using System.Windows.Input;
namespace GalaxyViewer.Commands
namespace GalaxyViewer.Commands;
public class RelayCommand(Func<Task> execute, Func<bool>? canExecute = null)
: ICommand
{
public class RelayCommand(Func<Task> execute, Func<bool>? canExecute = null)
: ICommand
private readonly Func<Task> _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<Task> _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; }
}
}
+45 -46
View File
@@ -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<FontFamily> EmojiFontFamilyProperty =
AvaloniaProperty.Register<EmojiTextBlock, FontFamily>(nameof(EmojiFontFamily));
public FontFamily EmojiFontFamily
{
private static readonly Regex EmojiRegex = MyRegex();
public static readonly StyledProperty<FontFamily> EmojiFontFamilyProperty =
AvaloniaProperty.Register<EmojiTextBlock, FontFamily>(nameof(EmojiFontFamily));
public FontFamily EmojiFontFamily
{
get => GetValue(EmojiFontFamilyProperty);
set => SetValue(EmojiFontFamilyProperty, value);
}
public static readonly StyledProperty<FontFamily> DefaultFontFamilyProperty =
AvaloniaProperty.Register<EmojiTextBlock, FontFamily>(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<Inline>().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<FontFamily> DefaultFontFamilyProperty =
AvaloniaProperty.Register<EmojiTextBlock, FontFamily>(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<Inline>().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();
}
+17 -15
View File
@@ -10,39 +10,41 @@
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**"/>
<None Update="Assets\galaxy.ico">
<AvaloniaResource Include="Assets\**" />
<None Update="Assets\GalaxyViewerLogo.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="Assets\Fonts\*.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<AvaloniaResource Include="Styles\**"/>
<AvaloniaResource Include="Styles\**" />
<AvaloniaResource Include="Styles\Atkinson Hyperlegible.axaml" />
<AvaloniaResource Include="Styles\Inter.axaml" />
</ItemGroup>
<ItemGroup>
<Watch Include="**\*.axaml"/>
<Watch Include="**\*.axaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.3" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.3" />
<PackageReference Include="Avalonia" Version="11.2.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.1" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.1" />
<!-- Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration. -->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.1" />
<!-- More things will go here -->
<PackageReference Include="AvaloniaInside.Shell" Version="1.2.0" />
<PackageReference Include="Irihi.Ursa" Version="1.3.0" />
<PackageReference Include="Irihi.Ursa.Themes.Semi" Version="1.3.0" />
<PackageReference Include="LibreMetaverse" Version="2.1.2.688" />
<PackageReference Include="Irihi.Ursa" Version="1.6.0" />
<PackageReference Include="Irihi.Ursa.ReactiveUIExtension" Version="1.0.1" />
<PackageReference Include="Irihi.Ursa.Themes.Semi" Version="1.6.0.2" />
<PackageReference Include="LibreMetaverse" Version="2.1.3.735" />
<PackageReference Include="LiteDB" Version="5.0.21" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Semi.Avalonia" Version="11.1.0.4" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Semi.Avalonia" Version="11.2.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
</ItemGroup>
@@ -72,6 +74,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Libs\"/>
<Folder Include="Libs\" />
</ItemGroup>
</Project>
+10
View File
@@ -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;
}
}
+3 -17
View File
@@ -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));
}
}
}
+7 -76
View File
@@ -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<string> { "Light", "Dark", "Default" };
LoginLocationOptions = new List<string> { "Home", "Last Location" };
FontOptions = new List<string> { "Inter", "Atkinson Hyperlegible" };
LanguageOptions = new List<string> { "en-US" };
}
[XmlIgnore]
public List<string> ThemeOptions { get; set; }
[XmlIgnore]
public List<string> LoginLocationOptions { get; set; }
[XmlIgnore]
public List<string> FontOptions { get; set; }
[XmlIgnore]
public List<string> 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; }
}
}
+11
View File
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace GalaxyViewer.Models;
public static class PreferencesOptions
{
public static readonly List<string> ThemeOptions = ["Light", "Dark", "Default"];
public static readonly List<string> LoginLocationOptions = ["Home", "Last Location"];
public static readonly List<string> FontOptions = ["Inter", "Atkinson Hyperlegible"];
public static readonly List<string> LanguageOptions = ["en-US"];
}
+27 -24
View File
@@ -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<Grid> 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<Grid>();
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<GridModel> 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<GridModel>("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;
}
}
}
+158
View File
@@ -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<T> GetCollection<T>(string name)
{
Log.Information("Retrieving collection: {CollectionName}", name);
return _database.GetCollection<T>(name);
}
public LiteDatabase? Database()
{
return _database;
}
private void SeedDatabase()
{
SeedPreferences();
SeedGrids();
}
private void SeedPreferences()
{
try
{
var preferencesCollection = _database.GetCollection<PreferencesModel>("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<GridModel>("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");
}
}
}
+21 -22
View File
@@ -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<string, Type> _routes = new();
private readonly ContentControl _contentControl;
public NavigationService(ContentControl contentControl)
{
private readonly Dictionary<string, Type> _routes = new Dictionary<string, Type>();
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}");
}
}
}
+66 -48
View File
@@ -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<PreferencesModel> _preferencesCollection;
private readonly ILiteCollection<GridModel>? _gridsCollection;
private bool _isLoadingPreferences;
private PreferencesModel _currentPreferences;
public PreferencesModel CurrentPreferences => _currentPreferences;
public event EventHandler<PreferencesModel>? 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<PreferencesModel>("preferences");
_gridsCollection = database?.GetCollection<GridModel>("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<PreferencesModel> 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<PreferencesModel>? 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<string, List<string>> GetCurrentPreferencesOptions()
{
return new Dictionary<string, List<string>>
{
Log.Error(ex, "Failed to save preferences");
}
{ "ThemeOptions", PreferencesOptions.ThemeOptions },
{ "LoginLocationOptions", PreferencesOptions.LoginLocationOptions },
{ "FontOptions", PreferencesOptions.FontOptions },
{ "LanguageOptions", PreferencesOptions.LanguageOptions }
};
}
public List<string> GetGridOptions()
{
return _gridsCollection?.FindAll().Select(grid => grid.GridNick).ToList() ??
[];
}
private void OnPreferencesChanged(PreferencesModel preferences)
{
PreferencesChanged?.Invoke(this, preferences);
}
}
}
+271 -55
View File
@@ -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<GridModel?> _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<GridModel?>();
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<string> 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<GridModel> 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<GridModel?>(grids);
SelectedGrid = _grids.FirstOrDefault(g =>
g.GridNick == _preferencesViewModel.SelectedGridNick);
}
public ReactiveCommand<Unit, Unit> 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, string>
{
{ 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<string> ShowMfaInputDialogAsync()
{
// TODO: Implement MFA input dialog
return await Task.FromResult(string.Empty);
}
}
}
+40 -42
View File
@@ -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<Unit, Unit> LogoutCommand { get; }
public ReactiveCommand<Unit, Unit> 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();
}
}
}
+47 -37
View File
@@ -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<string>(_preferences.ThemeOptions);
LoginLocationOptions = new ObservableCollection<string>(_preferences.LoginLocationOptions);
LanguageOptions = new ObservableCollection<string>(_preferences.LanguageOptions);
FontOptions = new ObservableCollection<string>(_preferences.FontOptions);
var preferencesOptions = _preferencesManager?.GetCurrentPreferencesOptions();
ThemeOptions = new ObservableCollection<string>(preferencesOptions?["ThemeOptions"] ??
[]);
LoginLocationOptions = new ObservableCollection<string>(
preferencesOptions?["LoginLocationOptions"] ??
[]);
LanguageOptions = new ObservableCollection<string>(
preferencesOptions?["LanguageOptions"] ??
[]);
FontOptions =
new ObservableCollection<string>(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<string>(gridOptions ?? []);
OnPropertyChanged(nameof(SelectedTheme));
OnPropertyChanged(nameof(SelectedLoginLocation));
OnPropertyChanged(nameof(SelectedLanguage));
OnPropertyChanged(nameof(SelectedFont));
_isLoadingPreferences = false;
}
@@ -55,22 +59,22 @@ namespace GalaxyViewer.ViewModels
public ObservableCollection<string> LoginLocationOptions { get; private set; }
public ObservableCollection<string> LanguageOptions { get; private set; }
public ObservableCollection<string> FontOptions { get; private set; }
public ObservableCollection<string> 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);
+7 -17
View File
@@ -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<T>(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));
}
}
+43 -15
View File
@@ -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<PreferencesModel>, 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
};
}
});
}
}
+11 -12
View File
@@ -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);
}
}
+11 -12
View File
@@ -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);
}
}
+29 -25
View File
@@ -1,45 +1,49 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GalaxyViewer.Views.LoginView"
xmlns:converters="clr-namespace:GalaxyViewer.Converters"
xmlns:viewModels="clr-namespace:GalaxyViewer.ViewModels"
x:DataType="viewModels:LoginViewModel">
<UserControl.Resources>
<converters:LocalizedStringConverter x:Key="LocalizedStringConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- WebView or Image goes here -->
<Image Grid.Row="0" Source="/Assets/Images/banner.jpg" Stretch="Fill" />
<!-- Login Options -->
<Grid Grid.Row="1" Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="240" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="240" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid RowDefinitions="*,Auto">
<Grid>
<Image Source="/Assets/Images/banner.jpg" Stretch="UniformToFill" />
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="/Assets/GalaxyViewerLogo.ico" Width="150" Height="150" />
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" FontWeight="Bold">
<Run
Text="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=WelcomeMessage}" />
</TextBlock>
</StackPanel>
</Grid>
<Grid Grid.Row="1" Margin="20" ColumnDefinitions="Auto,240,Auto,240,Auto,Auto,*">
<Label Grid.Column="0"
Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenUsername}"
VerticalAlignment="Center" />
<TextBox Grid.Column="1" Name="UsernameBox"
Watermark="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenUsername}"
Text="{Binding Username}"
Margin="5,0" />
<Label Grid.Column="2"
Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenPassword}"
VerticalAlignment="Center" Margin="5,0" />
<TextBox Grid.Column="3" Name="PasswordBox"
Watermark="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenPassword}"
PasswordChar="*" Margin="5,0" />
<Button Grid.Column="4"
PasswordChar="*"
Text="{Binding Password}"
Margin="5,0" />
<ComboBox Grid.Column="4" Name="LoginLocation"
ItemsSource="{Binding LoginLocations}"
SelectedItem="{Binding SelectedLoginLocation}"
Margin="5,0" />
<ComboBox Grid.Column="5" Name="GridSelection"
ItemsSource="{Binding Grids}"
SelectedItem="{Binding SelectedGrid}"
Margin="5,0" />
<!-- TODO: Fix the visibility of the GridSelection ComboBox options -->
<Button Grid.Column="6" Name="ButtonLogin"
Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenLoginButton}"
Margin="5,0" VerticalAlignment="Center" />
Command="{Binding TryLoginCommand}"
Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Left" />
</Grid>
<!-- TODO: Add in the Grid selector -->
<!-- TODO: Add in the Location selector -->
<TextBlock Grid.Row="0" Text="{Binding LoginStatusMessage}" />
</Grid>
</UserControl>
+10 -48
View File
@@ -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);
}
}
+11 -2
View File
@@ -43,19 +43,28 @@
<MenuItem Header="People Nearby" />
<MenuItem Header="Objects Nearby" />
</MenuItem>
<MenuItem Header="Communicate" IsEnabled="{Binding IsLoggedIn}">
<MenuItem Header="Chat" />
<MenuItem Header="Friends List" />
<MenuItem Header="Nearby People" />
<MenuItem Header="Voice" />
<MenuItem Header="Nearby Media" />
<MenuItem Header="Nearby Objects" />
</MenuItem>
<MenuItem Header="Community" IsEnabled="{Binding IsLoggedIn}">
<MenuItem Header="Friends" />
<MenuItem Header="Groups" />
<MenuItem Header="Events" />
<MenuItem Header="Classifieds" />
<MenuItem Header="Marketplace" />
<MenuItem Header="Search" />
</MenuItem>
<MenuItem Header="Dev">
<MenuItem Header="Debug View" Command="{Binding ShowDevViewCommand}" />
<MenuItem Header="Debug View" />
</MenuItem>
<!-- Add more menu items as needed -->
</Menu>
<ContentControl x:Name="ContentControl" />
<ContentControl Content="{Binding CurrentView}"/>
</DockPanel>
</UserControl>
+9 -21
View File
@@ -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>("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);
}
}
+1
View File
@@ -1,4 +1,5 @@
<views:BaseWindow xmlns="https://github.com/avaloniaui"
xmlns:u="https://irihi.tech/ursa"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+2 -9
View File
@@ -2,23 +2,16 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="clr-namespace:GalaxyViewer.Converters"
xmlns:vm="clr-namespace:GalaxyViewer.ViewModels"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"
x:Class="GalaxyViewer.Views.PreferencesView"
x:DataType="vm:PreferencesViewModel">
<UserControl.Resources>
<converters:LocalizedStringConverter x:Key="LocalizedStringConverter" />
</UserControl.Resources>
<StackPanel Margin="20">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesTitle}"
VerticalAlignment="Center" />
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesTheme}"
VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding ThemeOptions}" SelectedItem="{Binding SelectedTheme}" />
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesLoginLocation}"
VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding LoginLocationOptions}" SelectedItem="{Binding SelectedLoginLocation}" />
<Label Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesLanguage}"
VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding LanguageOptions}" SelectedItem="{Binding SelectedLanguage}" />
@@ -26,6 +19,6 @@
VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding FontOptions}" SelectedItem="{Binding SelectedFont}" />
<Button Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=PreferencesSaveButton}"
Command="{Binding SaveCommand}" HorizontalAlignment="Left" />
Command="{Binding SaveCommand}" HorizontalAlignment="Left" Margin="0,10" />
</StackPanel>
</UserControl>
+13 -13
View File
@@ -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);
}
}
+12 -13
View File
@@ -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);
}
}
@@ -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<ChatEventArgs>((sender, e) => OnChat?.Invoke(sender, e));
onChatEvent.AddEventHandler(agentManager, handler);
}
public event EventHandler<ChatEventArgs>? OnChat;
}
+172 -283
View File
@@ -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=="
}
}
}
+14 -7
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}