From 718b23c970ceae01174dfe346f9e60000b3d6803 Mon Sep 17 00:00:00 2001 From: Galaxy Littlepaws <34807062+GalaxyLittlepaws@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:37:27 -0500 Subject: [PATCH] Update Dev (#23) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ 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> --- .github/FUNDING.yml | 15 + CHANGELOG.md | 10 + Directory.Build.props | 15 +- .../GalaxyViewer.Android.csproj | 44 +- GalaxyViewer.Android/GalaxyViewer.Android.iml | 11 + GalaxyViewer.Android/Icon.png | Bin 14349 -> 0 bytes GalaxyViewer.Android/MainActivity.cs | 81 +++- .../Properties/AndroidManifest.xml | 16 +- .../Resources/drawable/icon.png | Bin 0 -> 16376 bytes .../Resources/values-night/colors.xml | 2 +- .../Resources/values/colors.xml | 2 +- .../GalaxyViewer.Browser.csproj | 10 +- .../GalaxyViewer.Desktop.csproj | 4 +- GalaxyViewer.sln.DotSettings | 3 + GalaxyViewer/App.axaml | 6 +- GalaxyViewer/App.axaml.cs | 134 ++++-- GalaxyViewer/Assets/GalaxyViewerLogo.ico | Bin 0 -> 70324 bytes GalaxyViewer/Assets/Images/Logo.png | Bin 0 -> 16376 bytes GalaxyViewer/Assets/Images/Logo.svg | 119 +++++ .../Localization/LocalizationManager.cs | 39 +- .../Assets/Localization/Strings.en-US.resx | 6 - GalaxyViewer/Assets/Localization/Strings.resx | 6 - GalaxyViewer/Assets/grids.xml | 57 --- GalaxyViewer/Assets/preferences.xml | 8 - GalaxyViewer/Commands/RelayCommand.cs | 37 +- GalaxyViewer/Controls/EmojiTextBlock.cs | 91 ++-- GalaxyViewer/GalaxyViewer.csproj | 32 +- GalaxyViewer/Models/DebugViewModel.cs | 10 + GalaxyViewer/Models/GridModel.cs | 20 +- GalaxyViewer/Models/PreferencesModel.cs | 83 +--- GalaxyViewer/Models/PreferencesOptions.cs | 11 + GalaxyViewer/Services/GridService.cs | 51 +- GalaxyViewer/Services/LiteDbService.cs | 158 ++++++ GalaxyViewer/Services/NavigationService.cs | 43 +- GalaxyViewer/Services/PreferencesManager.cs | 114 +++-- GalaxyViewer/ViewModels/LoginViewModel.cs | 326 ++++++++++--- GalaxyViewer/ViewModels/MainViewModel.cs | 82 ++-- .../ViewModels/PreferencesViewModel.cs | 84 ++-- GalaxyViewer/ViewModels/SetProperty.cs | 24 +- GalaxyViewer/Views/BaseWindow.cs | 58 ++- GalaxyViewer/Views/DebugView.axaml.cs | 23 +- GalaxyViewer/Views/LoggedInView.axaml.cs | 23 +- GalaxyViewer/Views/LoginView.axaml | 54 ++- GalaxyViewer/Views/LoginView.axaml.cs | 58 +-- GalaxyViewer/Views/MainView.axaml | 13 +- GalaxyViewer/Views/MainView.axaml.cs | 30 +- GalaxyViewer/Views/MainWindow.axaml | 1 + GalaxyViewer/Views/PreferencesView.axaml | 11 +- GalaxyViewer/Views/PreferencesView.axaml.cs | 26 +- GalaxyViewer/Views/PreferencesWindow.axaml.cs | 25 +- GalaxyViewer/Wrappers/AgentManagerWrapper.cs | 18 + GalaxyViewer/packages.lock.json | 455 +++++++----------- ReadMe.md | 21 +- global.json | 7 + 54 files changed, 1495 insertions(+), 1082 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 CHANGELOG.md create mode 100644 GalaxyViewer.Android/GalaxyViewer.Android.iml delete mode 100644 GalaxyViewer.Android/Icon.png create mode 100644 GalaxyViewer.Android/Resources/drawable/icon.png create mode 100644 GalaxyViewer/Assets/GalaxyViewerLogo.ico create mode 100644 GalaxyViewer/Assets/Images/Logo.png create mode 100644 GalaxyViewer/Assets/Images/Logo.svg delete mode 100644 GalaxyViewer/Assets/grids.xml delete mode 100644 GalaxyViewer/Assets/preferences.xml create mode 100644 GalaxyViewer/Models/DebugViewModel.cs create mode 100644 GalaxyViewer/Models/PreferencesOptions.cs create mode 100644 GalaxyViewer/Services/LiteDbService.cs create mode 100644 GalaxyViewer/Wrappers/AgentManagerWrapper.cs create mode 100644 global.json diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..4c9295f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: GalaxyViewer +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: galaxylittlepaws +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f33b7c5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog +All notable changes to this project will be documented in this file. + +## [Unreleased] 0.1.0 +- Added Login functionality +- Added Preferences functionality +- Added ability to choose language from a list of predefined languages +- Added ability to choose login location from a list of predefined locations +- Added ability to choose theme from a list of predefined themes +- Added ability to choose font from a list of predefined fonts diff --git a/Directory.Build.props b/Directory.Build.props index 17bc977..37fd070 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,10 @@ - - 0.1.0 - GalaxyViewer - Galaxy Littlepaws - enable - 11.0.10 - + + 0.1.0 + GalaxyViewer + GalaxyViewer is a cross-platform viewer for Second Life and NGC OpenSimulator. + Galaxy Littlepaws + enable + 11.1.0 + \ No newline at end of file diff --git a/GalaxyViewer.Android/GalaxyViewer.Android.csproj b/GalaxyViewer.Android/GalaxyViewer.Android.csproj index f03c260..612fd18 100644 --- a/GalaxyViewer.Android/GalaxyViewer.Android.csproj +++ b/GalaxyViewer.Android/GalaxyViewer.Android.csproj @@ -1,28 +1,24 @@ - - Exe - net8.0-android - 21 - enable - com.GalaxyViewer.GalaxyViewer - 1 - 1.0 - apk - false - + + Exe + net8.0-android34.0 + true + 30 + 24.0 + enable + com.GalaxyViewer.GalaxyViewer + 1 + 1.0 + apk + false + - - - Resources\drawable\Icon.png - - + + + + - - - - - - - - + + + diff --git a/GalaxyViewer.Android/GalaxyViewer.Android.iml b/GalaxyViewer.Android/GalaxyViewer.Android.iml new file mode 100644 index 0000000..9be9bdb --- /dev/null +++ b/GalaxyViewer.Android/GalaxyViewer.Android.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/GalaxyViewer.Android/Icon.png b/GalaxyViewer.Android/Icon.png deleted file mode 100644 index 41a2a618fb02e4cb7f6a15caf572b693bfe1ebb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14349 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 +namespace GalaxyViewer.Android { - protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) + [Activity( + Label = "GalaxyViewer.Android", + Theme = "@style/MyTheme.NoActionBar", + Icon = "@drawable/icon", + MainLauncher = true, + ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)] + public class MainActivity : AvaloniaMainActivity { - return base.CustomizeAppBuilder(builder) - .WithInterFont() - .UseReactiveUI(); + const int RequestStorageId = 0; + + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + + if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted) + { + ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.WriteExternalStorage }, RequestStorageId); + } + else + { + InitializeLiteDbService(); + } + } + + public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) + { + base.OnRequestPermissionsResult(requestCode, permissions, grantResults); + + if (requestCode == RequestStorageId) + { + if (grantResults.Length > 0 && grantResults[0] == Permission.Granted) + { + // Permission granted, proceed with file operations + InitializeLiteDbService(); + } + else + { + // Permission denied, show a message to the user + var message = "Permission to create data storage file denied. The application will not be able to function."; + var toast = Toast.MakeText(this, message, ToastLength.Long); + toast.Show(); + } + } + } + + private void InitializeLiteDbService() + { + // Initialize LiteDbService here + var liteDbService = new LiteDbService(); + // Use liteDbService as needed + } + + protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .WithInterFont() + .UseReactiveUI(); + } } -} +} \ No newline at end of file diff --git a/GalaxyViewer.Android/Properties/AndroidManifest.xml b/GalaxyViewer.Android/Properties/AndroidManifest.xml index f19bba0..e3a0233 100644 --- a/GalaxyViewer.Android/Properties/AndroidManifest.xml +++ b/GalaxyViewer.Android/Properties/AndroidManifest.xml @@ -1,5 +1,15 @@  - - - + + + + + + + + diff --git a/GalaxyViewer.Android/Resources/drawable/icon.png b/GalaxyViewer.Android/Resources/drawable/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..03b941723237c740a92da7e676c362258915cc30 GIT binary patch literal 16376 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 literal 0 HcmV?d00001 diff --git a/GalaxyViewer.Android/Resources/values-night/colors.xml b/GalaxyViewer.Android/Resources/values-night/colors.xml index 3d47b6f..e727ff0 100644 --- a/GalaxyViewer.Android/Resources/values-night/colors.xml +++ b/GalaxyViewer.Android/Resources/values-night/colors.xml @@ -1,4 +1,4 @@  - #212121 + #0f1729 diff --git a/GalaxyViewer.Android/Resources/values/colors.xml b/GalaxyViewer.Android/Resources/values/colors.xml index 59279d5..e727ff0 100644 --- a/GalaxyViewer.Android/Resources/values/colors.xml +++ b/GalaxyViewer.Android/Resources/values/colors.xml @@ -1,4 +1,4 @@  - #FFFFFF + #0f1729 diff --git a/GalaxyViewer.Browser/GalaxyViewer.Browser.csproj b/GalaxyViewer.Browser/GalaxyViewer.Browser.csproj index ab16d45..ec18777 100644 --- a/GalaxyViewer.Browser/GalaxyViewer.Browser.csproj +++ b/GalaxyViewer.Browser/GalaxyViewer.Browser.csproj @@ -1,21 +1,21 @@  Exe - net8.0-browser + net8.0 browser-wasm - wwwroot\main.js + wwwroot/main.js ./_framework - + - + - + \ No newline at end of file diff --git a/GalaxyViewer.Desktop/GalaxyViewer.Desktop.csproj b/GalaxyViewer.Desktop/GalaxyViewer.Desktop.csproj index 081b729..4d7ebe5 100644 --- a/GalaxyViewer.Desktop/GalaxyViewer.Desktop.csproj +++ b/GalaxyViewer.Desktop/GalaxyViewer.Desktop.csproj @@ -13,9 +13,9 @@ - + - + diff --git a/GalaxyViewer.sln.DotSettings b/GalaxyViewer.sln.DotSettings index 29d1099..317d147 100644 --- a/GalaxyViewer.sln.DotSettings +++ b/GalaxyViewer.sln.DotSettings @@ -1,2 +1,5 @@  + False + False + True True \ No newline at end of file diff --git a/GalaxyViewer/App.axaml b/GalaxyViewer/App.axaml index 3e557b5..fb4985b 100644 --- a/GalaxyViewer/App.axaml +++ b/GalaxyViewer/App.axaml @@ -2,7 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:GalaxyViewer" xmlns:semi="https://irihi.tech/semi" - xmlns:u-semi="https://irihi.tech/ursa/themes/semi" + xmlns:u-Semi="https://irihi.tech/ursa/themes/semi" + xmlns:converters="clr-namespace:GalaxyViewer.Converters" x:Class="GalaxyViewer.App"> @@ -10,7 +11,7 @@ - + @@ -18,6 +19,7 @@ + \ No newline at end of file diff --git a/GalaxyViewer/App.axaml.cs b/GalaxyViewer/App.axaml.cs index 5e32e54..d00e80d 100644 --- a/GalaxyViewer/App.axaml.cs +++ b/GalaxyViewer/App.axaml.cs @@ -1,4 +1,6 @@ +using System; using System.Diagnostics; +using System.IO; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -6,96 +8,134 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Styling; using GalaxyViewer.Models; +using GalaxyViewer.Services; using GalaxyViewer.ViewModels; using GalaxyViewer.Views; -using GalaxyViewer.Services; +using Microsoft.Extensions.DependencyInjection; using Serilog; namespace GalaxyViewer; -public class App : Application +public class App : Application, IDisposable { + private static IServiceProvider? _serviceProvider; + public static PreferencesManager? PreferencesManager { get; private set; } + public App() { - // Initialize Serilog here + ConfigureLogging(); + } + + private static void ConfigureLogging() + { + var logFilePath = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "GalaxyViewer", "logs", "error.log"); + Log.Logger = new LoggerConfiguration() .WriteTo.Console() - .WriteTo.File("logs/error.log", rollingInterval: RollingInterval.Day) + .WriteTo.File(logFilePath, rollingInterval: RollingInterval.Day) .CreateLogger(); } - public static PreferencesManager? PreferencesManager { get; private set; } + private static void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + // Register other services here + } public override void Initialize() { - PreferencesManager = new PreferencesManager(); + var serviceCollection = new ServiceCollection(); + ConfigureServices(serviceCollection); + _serviceProvider = serviceCollection.BuildServiceProvider(); + + var liteDbService = _serviceProvider.GetService(); + if (liteDbService == null) + { + throw new InvalidOperationException("LiteDbService is not registered."); + } + + PreferencesManager = new PreferencesManager(liteDbService); PreferencesManager.PreferencesChanged += OnPreferencesChanged; + AvaloniaXamlLoader.Load(this); base.Initialize(); } - public override async void OnFrameworkInitializationCompleted() + public override void OnFrameworkInitializationCompleted() { - var contentControl = new ContentControl(); - var navigationService = new NavigationService(contentControl); - - // Register routes - navigationService.RegisterRoute("login", typeof(LoginView)); - navigationService.RegisterRoute("debug", typeof(DebugView)); - navigationService.RegisterRoute("preferences", typeof(PreferencesView)); - - switch (ApplicationLifetime) + try { - case IClassicDesktopStyleApplicationLifetime desktop: - desktop.MainWindow = new MainWindow - { - DataContext = new MainViewModel(navigationService) - }; - break; - case ISingleViewApplicationLifetime singleViewPlatform: - singleViewPlatform.MainView = new MainView - { - DataContext = new MainViewModel(navigationService) - }; - break; + switch (ApplicationLifetime) + { + case IClassicDesktopStyleApplicationLifetime desktop: + Log.Information("Initializing MainWindow for desktop application."); + desktop.MainWindow = new MainWindow + { + DataContext = new MainViewModel() + }; + desktop.MainWindow.Show(); + break; + case ISingleViewApplicationLifetime singleViewPlatform: + Log.Information("Initializing MainView for single view application."); + singleViewPlatform.MainView = new MainView + { + DataContext = new MainViewModel() + }; + break; + } + } + catch (Exception ex) + { + Log.Error(ex, "An error occurred while initializing the main window."); + throw; // Optionally rethrow the exception if you want to halt the application } - - Debug.Assert(PreferencesManager != null, nameof(PreferencesManager) + " != null"); - var preferences = await PreferencesManager.LoadPreferencesAsync(); - ApplyPreferences(preferences); base.OnFrameworkInitializationCompleted(); } - private async void OnPreferencesChanged(object? sender, PreferencesModel preferences) + private void OnPreferencesChanged(object? sender, PreferencesModel preferences) { - if (PreferencesManager?.IsLoadingPreferences == true) return; ApplyPreferences(preferences); RefreshThemeForAllWindows(); } private void ApplyPreferences(PreferencesModel preferences) { - RequestedThemeVariant = preferences.Theme switch + Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => { - "Light" => ThemeVariant.Light, - "Dark" => ThemeVariant.Dark, - _ => ThemeVariant.Default - }; + RequestedThemeVariant = preferences.Theme switch + { + "Light" => ThemeVariant.Light, + "Dark" => ThemeVariant.Dark, + _ => ThemeVariant.Default + }; - // TODO: Apply other preferences + // TODO: Apply other preferences + }); } - private void RefreshThemeForAllWindows() + private async void RefreshThemeForAllWindows() { if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktopLifetime) return; - foreach (var window in desktopLifetime.Windows) + + await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () => { - if (window is not BaseWindow baseWindow) continue; - var resultTheme = PreferencesManager?.LoadPreferencesAsync().Result.Theme; - if (resultTheme != null) - baseWindow.ApplyTheme(resultTheme); - } + foreach (var window in desktopLifetime.Windows) + { + if (window is not BaseWindow baseWindow) continue; + var resultTheme = (await PreferencesManager?.LoadPreferencesAsync())?.Theme; + if (resultTheme != null) + baseWindow.ApplyTheme(resultTheme); + } + }); + } + + public void Dispose() + { + //PreferencesManager?.Dispose(); + (_serviceProvider as IDisposable)?.Dispose(); } } \ No newline at end of file diff --git a/GalaxyViewer/Assets/GalaxyViewerLogo.ico b/GalaxyViewer/Assets/GalaxyViewerLogo.ico new file mode 100644 index 0000000000000000000000000000000000000000..74a3c6cd44405340b01a18657ac4e654de321ced GIT binary patch literal 70324 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 JO2#zw{{aM6KJ5Si literal 0 HcmV?d00001 diff --git a/GalaxyViewer/Assets/Images/Logo.png b/GalaxyViewer/Assets/Images/Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..03b941723237c740a92da7e676c362258915cc30 GIT binary patch literal 16376 zcmXY2by!s0*F_LSS~`^&x}+PF9=e7u>F$sgDQOtGySqCiq`SLIx*NXh`}_VFcpmQM zoHOU_SbME~f)(T>P>~3cU|?WS!IGj%!0$ca^Nff9{FG?YUjcr-x0BRxgn>cp{`Yy) zXIp3l{E@&(Ox;P@*2Ky6n}acotE(%Mxs9cx;Ws;DCR+#7loNhJ7#LC*u;^En@2SUW zuHUgCw?Edv%%C@z?(f^UzPc-7zr}&i16PrWRc#jhY%}=!HV;V&JL)5^|G39H5!AK- z3}Io?wv*M<$N_T6!IRP9c%#+5dx5c2iW*aGG>?>y=Kx%H=f56l9Y3j8R*N_^{l>mS&J zs)_Evh*g>`iU;$sI&$yCpt-Z14V_!6f%B+OZ%aQs_!t$4CQSW~0u_BYBN*4pi|X^O z>hvTNt(RhPw)z1KMG97eTSv~sBQ>`RmF~3dWG54CMmTp?5l@&Jm4vF`-QnZIG|sQb zvvi(za+8T>qn;EPQZ}c$2t#w}`jy%d8W+DI+IJf1|HL3)MZ2A&h0&2CilNxq>y-YH zqVtX-rl*7L0M!Fsd{57b$J!qR&hx$Xb$!EZJRt=$6SWcnTTWr?Oh=BT{??P)pSy%T zF~Y%#heI5iOS%rXPSUBRQ45nvr|=}?la8hhm;J`CTI(rg{45V+^Vg(4?`+Oc(FZ6f zP88SS?Ro`+CX52S+n?b`hV7&)!p!e;qA-@e6T-i|r2G!lBbW*XC868zP~M53uVb5% zy?ptMVYab{(qFR=*)D+e?rTW6d`VEI1c5K$S7F7K&al;B+WXmzX#;XtVYz(iza)35 z!#u$~Aw4nrD?B}w#t1d1B1MA+u)Y)A5z>?}B%>=3(D*gGA;P+OT+&4qi+9F!;$M|g z$WH9kVuGRkKh%CO`(6xYyOBw2pjkuxWDrR|x=}$WU)NbLWO#KDvj(S2FPWQDamm3@ zzHxMoK?~qr*tn>bdNGIbHW2ApZo@R93sxf16P|6x8CDegt0k88Zft#_lyc!8tkr_u+@n-{qsYS*hXMma-kPGG)}BL z4Mclc^UCZHCR4#}l~4F^{J9KJ`s&y?`>?RKCrJ^%(wqr*0-T|Vo#6mtS|6B?s<;D5 zxlhbV{R1e9g^A8rbr_pbllo%tI-?$y*&xY}jijf=DsT8>(S}A7t0S@X5I5iPSIs`ZcvC1n@(t6(gQerUl>^!=}!u|34 z<|cV`bhMaD{F|H!NzLRMYxLrDXQ#J!AJjGRt0PjSCJA9n9V@g8gldmwo!5p|Od=N=kIW?`c6)R57uM zQcq=X4QzU_u)IPOi5mI`9diGY=a*8<3>IRC?8?fK6*Na&4jXe zu2j-4&f)v_=B1+-VMLogY6lA5^nUsY?RM;u9qGB?h>%{h+P&M~D&jk=7ib{{sm*RN zBLD3`Mbs@Dwtk1-Xn8r)bTO99R({?0LUZduh~02+epnboxo-RKq^X2pZu{t(f{Li< ztUp<4FN(1N@cu=apCf_;=6#Pfl@?0hoZMsxvA$ttz+qSq9zZ#R6BO6tE z<2+wa@5+Yi5y_^(Y2tJG{_bu_WCb3uQlVd8V(IllE^vO*=w-1}!j;i~`Q&lQ z8H6rsXZH#o%?R(QUrRIE4xoTw5<-17I`PRwV<+`BH!vKeE~F&5)`>EMtIcls_Lr|V zc(3+nqUmOaJs3F&;qjQYx4z%G$)z=R#tm^vHvA4L@%h0#W2us@8ICkvq!vI}N4NFY zV&TzsI9%&!{sFzEo@C?38nTK$0z%c8G*gM1&Pw50&>2mIk_y3&V2gL@zDtAigZ*yY zU&HQQsOme1M8@s)m(|1Nv`H4((sZZRv{!?f>N3HLMklb_Leg;w+6z1}(f9-@EM&_twobji>k z86*+a4X&ElXXqOli9-mt94{BaO0FvmEh)JVH3hz(;c-YBF)zlqZV6prOqr3Cc0A!D z2%!uuXM%DYlvIDrZr zF?UVH#C1ZAC)U3Cf2ueB-Db6p5K!b6GxG0@CEsWA-tg`pot*qxdOehg$%hyE6Iiy= z-Q`B~89%iFHmIeAh=iAO=iub{J)QAObknaGJdMw$0tw6u4smMNr8#!`{4O*(*-e1o zbBNm1lna9~9zQw7gMv3sud|E~5#V(nZ!-=NwF-_7B%vTW6$aT6-nb(5B6j1!*dC!w ze8KkB#zsl}a9F^>Dg9jX_6s{Pv%X=LdSp)s~20~7Jm`_e@oZOmG#{}ozFxy_P z9)^p5onI92XIPcVm%WvNjPdfKKcNg)LuR8*9Cl;h-XlSBol^ZqN%#bYOGH}mzOM{M zS^G0ow?E_nF+iSwA*7xpS)E<$IRUWVR6fVJsSgP}?%OEm4Y9U=@t8OIGP739Cx@R_ z%2Qa*pID3uOv%e>+B=L3fzSi*Mm5vw7W3CZ3aWzBBo~Q9`IY!Ev!Ov*V9u{<7*Zuj}-?sc}>&|-K zPkaeX>J1GmC(%?W1TU=`;1el~uKs~7y!Toy35FYcrSW+t$5O4g8!%5#UL!9ucN=Un zX_Y+@1!k!^ZZQTtGmP&y=ijC2`Ly6!FLI6ECJ*6NT=o_gsrKI9+!#H@qMw~#4_@yP z5NL>st%7b$__+)>!O|n7OZuH7GdSq-=u!v|%B}vNzUGA}*K0>Dn0rR68LC$~(Z9P* ztF>6hp^M+Fu?`7dboPGnMgs>I$IjGZ{?D5KAd!*BA;7^qtSVz}>uifeBiTi; z#btO4BrZDpUACFb-Pq;JqgBzp_p3uqB~^ zy6*4WsceXsX1)2Z0)hfvIby!C)FjKs57zf#N-Z{@DTI7*xVSFYjIt~GSaw^4ns7`{ zwVh;>xxASDxywLNz5f{uMiaxYE>!WosL7sp8Lgr@R2-LWyW=m)GIz$lkn^?g1zcrI z9I)HNDb^`yRpdq`n>cfrl+OoK9cYx!E3d}0@|hlqZSdZ7E{!%Afee!HnjdbnGdrTS%)qyVGQy``pX+I)CCN^YRr3s zl=?5HBeVw7{g>MohKAsB6r+;N@6)x8n;~~shjU1+HaAysRx3t21uX{Nf*?!oP){Cr z{25DC_QS=Jgq-_>mJSAY=A(&2#q?9d^IL_9!yhh*n1s_Kyj{b)vTlR`42%SUPswfD zUw)CZtVH`@)roqC)#+(OeV};9YP{RWZf11PGQkEopwEqk@7y)Xc)+BwlqMU$Z0}pQ zls;`bHQGKciU)NkgWg54JeNrJER9fiRWDUO?PzMK>K8_A=CUtZ`ZyR&;7MsHXy@_Q zb!SI22>cNWLKTa@P{W@2YcCdGMM=j`Mf0b7DoxY*=lPja!CI(J*v}#7mG*s$${PSL^RtF&vu2~)*5{b@LPfy!3g=Dub*(X-DaGXu>)xkR`Ny zIK#73h32|af(K_FT7Yn{O$nZq@0V7cic&K#?YSZPFLQq=wU#6f)hrgq`aK z4~^^E^-c1LlZkv2ox{V!@SvdUIBH&J^MlNqy*A1IWOlLiw>a@Kfs>tx>%bN#fWQ{0 z>(H7Ib}X?v{*$yD5_u`ChE2?qrH^Nqmn`)1oLun?^F2>IS@1Sojm^y$+NTj=A0Me0 zHMz$5FR8gSYi&9U)=*Th@+vG$oP(NCV`siyUfkrS5oifUh=A9#QeTxnS3FDO=%;|d z*E+ss>S>BL$-9RX9m%|Zwk&mxjEyhthTPv`zup#V+=t{zg5|cl?k#cq_ng?=pao51&*dkJh?NY9#-{@jfZQfv8X zCpC?lpyWgwKfl0y)5td@%*0^4YPhfcrqVdZf=#d-+P->~7CBYe)v-HvPPC`JLqjnL z;iq~57CSg193*;4l?CjRw7ta=c8R-1^$rd&QZ!0V4aJNKzX>R@f*$hG$;xBV6WhQx&s|4OAjs{74x?wu|r92}xf+t85} zO0;;lBq*191Dg(X7Be)49$w<7@^Q-CZ&Yi`j>({%F=aW-x_veQT+O7KcC`b<0 zi#7ruvmP%a*LQ6e9`k)%X*a@8rZf8<6W;XMwO3Xn2hnL%hF@8ga6kFsz zr`vtM*E^VdF@a%W|$kpzRO zIe3n3G-TA&)I#nb*Z1}eovEOC#LGS*!S}f!cMp$?0kw(`+K}I{uRi#X&%_Y{8On_5 z$DhfmC&Y5t<>zqjztqB_^pk|;L>D+c#-dgE0}!Z4vy9*P_0nd0V?(Z_9yPcXvpamB zyTqU%Ki~0A|7{SuI0n9o_Jm;`a0aWZ>38q$lz_iwXE|N{vRH{A@N7oOA9op@1^c@KnNYs@7n_t};AOT$O zi2RX5emk^@nVC3MYa&uGQ>uU$fu;O3B~@;v>l_Oj33otCi`M94o);07#$foq*3;Rw z%*nQ5LcPtih3C}*eJrh|4yTHDsYaEb(K{L<&CgV{^4amRR4U&~N08CC1!k)k{ndOk zm3j%Tdr74d2A%~Dw$DRlGHO-gvIp`5lItV+a~~LT;9Kg0*iOgla`XA;U;CDpV%p&{ zG7SrMldVu!BILK*xc)2_Y5r`&k9q_ZISezXvBysN8%h%bd+^EgNh$qQx~Pc2pJBOY z7xfWx*a@jpB1mp1mZ;fvF4FF;5*6@af<^_TaJxNi!U;J#J7+!T$hwugN=U^{GjZ4( z?hEdY>XvAD8k;fa1;YX*C^&CT|D+$vJDc!fJ869d{hb1H2I`eGUUuT`9&adS-w0Wu ziajA`5%?Klrf@DZS*O~ty1JUjll14Y7MkP5UBElko)=qE4Wrb zA@6z`b*kDEY`CXU|Gh+`K$$@d3L1<@&z72TP$|+(r<)o4pq=NLZY&*Z$0rz#)yHJ_ zw9f;W-q7{=OhY0U$tR!0QTh5wr~KB0-b=1(lU}rxC6f6V_8?v!`;-W`@31sN_csZcF8yG0+>81L& z%K4OrCifi#HD-yurv5D;gl8(EE($$h$fnHI9gyu~^^IO$?(?8sl$*nhk~Q3@2&MxC z8NtnLfl7(uyB~81PO55hSlX$LH(-#GJwy}a58&IV&@kH>R6;Ul%|-&?IX zMd>Vc<0K^4;sJ$+^Iu4oTL}XH^5)BxxR(8aFt(64+foG1y^ImXSnOZ${U5#z#k|F5 z4sVW_Dbe|64BU}4IfrV(({~Zn=GVzMz~IW$g&IYjmH`;r+;{`NUer9bud%&5yjHRY ztdvD-m4w$sstrpm<-v}o<*eSrYssJ3$1(s;j1`w7w^Tb1K_oB|IJIRh7c-`U;7VsQ z2A$8L{(Khf$Sz{I?D!HR9_i%AHCsp)7~ECwl97896K> zk%XG>FDP*!=dba#ND-t(DJ}HJpR5O!$7=IrG=|g|YhKK=Y=AOZ zx6Y&q3Q}Yp&zLlZrB$wfn9p{L4#FhTbm1GK{Ch9$4clvJvlqvhdc4Xj2SH9PRYPkRRN zSEk(Mmx+9KTZmpvJ5|7YvbPeiJblH+OB@S#jy@FJEcAb8?a zrW1jJEat3-D;oqU3uAvayCzZ>$Z}R`)*VZh)RQc9m=ax@$dm5|I?H++h4n+4Dkw~e zAbqP)6JujX{nPhoM*?4ip)a*oQ$PNo&*ZzL>4%QF;Nn6tmFH^?@wGUKNgtXCYvS^Z zU`(TyUTM2QJBO#9;`;jLz30R2%$$iluS37v_!8MHe+q+T6Q%N|zu5PEjXS@%$kg?- zV0SO13QwL^nl_yxkdz)Ged;Fw<{OxxFlA@%hVuQD{?8ATjA=v|KaY4$+7O zD?^1|?;u&Zu=k0JsN0hm9CoqQvCi*|FCQdpRsRW$WYf-VvOTtD|e)cYC3y z=g^I67mXvu{R!3glA?Nnqdq-}M zhqpd51UWx*?fZ4+VWUlz;KfdytwdR?V+YaI{z2#QZfgS0C#+Ua;zaewLHV~fp zzo8{CrG8t?{nYeb7tCcFliLtwc{2N3aY+K(8XA?SlDOyW8zWi!UIR zs#vvRBy!8tk?{gk*@tMPiEZ}!XY7Z5k8d8)lx5-CerkQ~`%1}nbUd;^9*UbVG{wMi z3!v@rDgla&C`w2kxHzI0M4EvKRwW)7SxLKzblSH10>#)<)`8BcuU2BsbEVJAtOCo-(s@Ukayfw1d8H6ABDU zLmNH5j~@hoY(3kwOQ|cUqzsUf!`c~yY?ge=2a;pN8nvhVU9=YxpeLZgc=tmec72-U zn0kODH8@n_{{8#K9o7nRSu7P&UMiJTEXw9q#h)S?b4)u7jmbrBcOm?~F9Nh}t)BZ2 zPR!|tY=sKa=Gh8E=3StJG`{oUMAkr&M^^)OI@-L9x5UEnHE7MbO@ob8qZFa@h?_j~ZZ3#>)8#b>Cc{<7DFoqw0+4s2IM{9O|1DuRk#9 zq?3O#6ro?p{N<)5vXVe?pf=r^dE3_JwZD33obFwpv8g^P6Aj=s@Z|KgiisRa_X!CR zj#M%d@tfA@H88U44jh4Vo@e=O3IDx}UT+l=oS3E@mY|?u>vKm1{>84u6N?5ac~KT4_ZPsmBCQ-K$M5 z_Vcuk2D&RDyp7E~?c_3hHB$o?#nzm9zrJzcoRPc4SwW^%l`9b~-!UyLsP=bXTjFH9 zv{ef$57Li=GuNciuJ= zeUqRlJ@~@hA(nEJhSNtUweO($BQ%tV8QnVw?;WZdcv5B^RGm8$E$y*Lbe+qfOCFW* zE^>VXmjt_yE(Cb_cN>%`Nhow?Xb(@Fl-q~Z7jYf-CgzF|@ItNgI24AF%&br2jpp>d zRUxc^YG!9|pL5!ob#)>n8Sm5!%gQ#1BigDqY0)2dR41-OtLO17LdaoW#2sG~m7X4$ zkkCL?h3s=viTWALYkF?te&dBBFQ2z3#O6EI`EeV-9*qb`qH=Z0R3-mxKDIK?iQzvqACK zKZeujJ0V}(mi#A;2ieT#gQjigW$YPD$ru>}M!wOj*BA)W(t;aXEY(sZ6O<{^k9nd@ zJAd0W4+Ww8{=8gyHwM9ez(cF2nX`M09z1gpPBvNdDbrT3sq|_iR9=-3hxA zG%nVASbTK0N$LceoEs*3Vp<#qKv=h}w6Q{^vUConmxJin|jkdp}2n#1$*Fndo?+%d+UC{{<%$xMS8c`HNlV$E4=wLfvejy29zS zanc%1Qm82s>eS*{mf zk{I{11WP+Ff8@`mokO`MXU5jXyH9tQ%G56#lL2fl_|f%F$FnI(J{6{CXXxj*4SPj6 z!4@-z7gH6Nce{N;g$hL>Bz>FeDoEv{051A=HO5w?-jqCYa;Okl_b)3v#jmIT|9;{2@532TX0)+v4x(DFAMm0CXPcBEpm^*lOC>Pb z#(3}|_dY!lJnT}ln=cZAtG(Vi1SRpXO-|h%?=fGDKvtR__YY+aNos-2q(-R0mQI4IqA~#hZoaf<1G|Ncu;E-&#S`9a%<+48hk`{Jg<)h8QYU!a9OB!CwfZNLvZ9L=4RPmUJscF^7 zu&W9QiD@sBLw-K!@-T4>2U5F)U5`l?TTpng&rRnN?Fc}NCIUwt#W3KD0RZ_+-Ur~p zN6rMt$6e>6#rCgp>O{f`MVJy2eZ&T~;*QUY#wjaf`6YEMT-k{nUa1!65yZ=~MvzppsK|D(5r$hWRED&}1r_5eOZ16a zNU?UvKSi{)aMB*78Io{%lGZrM8X&MI zPmLqHWDoY*yJ18-TFL@jk&t)*DiQ4fScdpEF2BR;Du67l>({P>T*ecYeHBac>ehBu zx2SGg;emjYlT~=WgN=5_Phd(*x6l?~HG@>Q6BAW9$pZv9Zh#qjPtiTWSuNGZqwqn% zQ?!KG%pW8d9B`~%ghsYgFP2<6<0vU9lLYuU7mmDn#e$%|00yi7@oUtX3q>k}({Q#nH zjsEB<{49b}OnBB1ve>zKT9+?|INHgQTz_^Fu1Ds9h4m08$-VJ~XJ8u#5+2?Xh~jv) zDjJ)JBYC0<6~WIpZD|(EgKcqDgoVhDEag~h_Tu*vTq5_0bO{yBh@+*Bjt&?MuGc_P z5f|s>t1sOeFnyTc_U2vu_SvY$b&g6kaCk@D3A7fTWPVD9^UHjgKK{knnS~Q8dC2Xc zivV{tE&tm3#IU>B7zjBn{VMzPRz`qP+TNN#+Slv5*mrlNUp7s`cnxP&M#HYysluLF z*MuiDF`SQ)3gn(do&&`Se2l)4($mLSr_5EF^se=+DHX_7%p#H@YBj{x(c{@^yT%C*Rv}ErvUKknfJLt$!lkw4qrY(tRv~M%dbgXN}q^0$?E?}_-mnEe>-dY(B z{h84WV7`0tXt3uKl$(l2;2rGmmo#14XU39**O4;r!w4oSYU=85$4Xto2~dk} z0Vl{#s~ywNyik)80woq8qD<{LVOTBv5ny8z@y^iUS^FEe7Ix(n&v54l*o&lejn^S~ zW?N=#fz!U$U2r4l)}C#bH*aYFnVRGSI=Vcsi#q}rm+Ne~W+u0;i8!woXSu;~G5m6q zm$YJO3X5W?Ca2nYVyPGh&uiin*2Eme3{nB1Fh0OJQ9kQ=cdv>}G`*9TXa4BcO0?n* za81FavlSYpH0kC$t|K8GIMN3+h2(n?Wm!kdxcTBVvh%IqaM~Wx|LEiy3h@r!8&3+u zh9syEQv>J%Sgm@82@4qfo-Smxt!q zJ3c*rFu!uARHy03w$bA$ikUAt4=Ms2mXBhCLanM5qE7yh2BM^#9LVYzle1Jys@Tu_ zlwY7tr3f}#a%poY-uV0_uFy{@)kX4nv{me zGRoy|vy4+BJnu$fde2MaFM+VH%22f?70)wmd4}<(SGv;nR@EN^IL8X60<0R_25UMV z&5#T|n6zd=`8gY}v-5Mx4Y^i2`?C1Uo(U1OwNIB__fs;DjyMADZKCsw7yvh6bV_!> z&2n<`@ecRWL!aY&C%x@Ny$IgSsj2gWZW%A?3~>;ZQEow%moyEeWN!D`RWQp%r|(lu z#1_En$@R&NT@2;IPut#Aj+|K5rJ@oF$%M{E7V_ZA&f_;PuZoOM-~&hWm&+>OUE9?a zJ>C5V-aD0P(5kS9H$7iqB_V-BpVSFr*wvZ_~j(M*=7$r~o$K4K8p$ zAIc4I@~$rUdp;CjA=-f#goTmmDKtxnT{hd?+&tN`X6fYBOkEN`X1Y==W@Nm`b02{^wfKW7$Ia0}DsjfQpM+lIdx?z1-E5}VaLH1XcEU9{l^ht{q9HZ~QV zu`L!rcE=Ibwn70MW6=Yjmxnt$abW?FtaYnzKc8eT4x69;)ZMLQapIA^_M78<-B?Sm z@o`|=ZEg#4#Di=*`2$bww#RjWq^Vr&!~@vS-bYx!L4|rxO1Y1GMpKU_CMK26Qx-vG zF9qsy5_J0z48VD?>!IGv?KY=b+zbZ?=A26nOps<`AlgUWM7kJHJ?wDCu(472jdO&m zw>bEURBY16J1%T?5a(wm@Bq0idG%ShdqlRyZw`6)Mec6H3&q0{8$ibsM}ig(e|tWt zGCYS;$!c9r*D#xTcl@>u%LQs>JLj2JihekMhGcV9qtrrbEJwweMm^lEx0iP_?#2%Om zCYjyZI_9z7Be!JbrkfFGYWk1uL06KfUV{#wbjgm>^uNvfw!$jAEz^iK7OU6M^7u)J zY=e_%t|HT8%(r+=npPt6$WW|bFIC86a#ci8#>E8}kNwUAzj}w$yCi<3Zb&$K#(~%3 zE3+fF^X8`+bY0|cMqSK$0%vG7=Fdp!UhN&-Ad^&nmtT~~FY8@yd#5nvVx|=yO&^^D z2$X%41j?J#DFF}>p))>24B*x6nJfyG&<2YI&xoK;p6g?VHlp@d{cn=lE%O%J7{m}M zg3yl)dM7&?9qvUag~{@os7s$g%1-l#Ix;+W7p*KxB#n}Z#O1+)^jJ3$5EwX z*5U5XcAL6i^(4s#IWV>BTwmJf}v9sc4u(Tqf@6RztDVz&-X+i`12Vi!+V!ovKA zbN!GvY;0^Q!RGa;AE>)Dnx?Ce-8BYxLiZYhJo1<0Q1kl!*`vOCW_XGzg{w4s4%yGn zx#qb&Js!xwLe&Xz(yw&bp)#`=^)9q*`4NiGok1Frb^k+RkSwc$2H<>41gQ%cUd(Q; zU2WDj*I!6fSEP}bmzQxU4G_t=J?-%H!U($>KS|(du)JZ?;m;WWGt_K9d2QbI^lHut zj!Z1H8BzcP;(GltV`u$#lz>~nV54sO>Mjn1M5n(G#D>(2mBl1K0n~G}^vCp5+n)+U z!HtXFnvlNO*x1e{wtzinFe8%ZpJoEbG~lb43t{q+HH2slO|6W`YiPCK$&`|&9qlA+cGa3O~4 zC8z37JUJ8nt2!y_byg9@B^AiKGA4(+97emB&q(D;F(2D>^EE@l^-` zArl3`kMWNo9#G!wY@c_to5lLbPEN-fQN3KmW&4Bz@aViNv|{Vvz}Oi@OaC+ee2pU< z;3GTYR~$22L1kLWdt)I9h=7TII#a0OG}s5~3fik^##ZjG^s3mS~>aT4&%T3Esbx?P2%@6vyTdc1z@pREbTb>}PQe-!GC@ z12#XE80ph`CtY!sEeg7%AD=$k;D)EvPrJ(`W7?Aw=X}0+DV(@{_PSV7 z{nhlLw{)$qm@^Q0kG%2wORdHEbPrVGW)Na=cJrurd34#7(G2jb`U{&2o@cYj07OYR zV(6oP+U-}brO+)CClP#pP$QS~XY(0V0eICZ3+?1;AcFe^KA)bfQU6`USzcMW9EKAB z0?MR{4POKBtb6rEeI~I!TfFSyYX6!w{1fytK#eIS=FnNl`%*Vcw`82Kq#An2Y1Y+X zn9uSzwCb%|Sc60MVHA6osY*z)_JujzDTc2MN-Xg@Q z8lw;#m>RmhH9Gu)oRB}JJ1CqvBw9;gZT|N{}>EK4*zKJ zwLT>76Q@5@Lveg~Ss|o=ETW!~1VNiG7Bq*M!alcYpb& z3yoJh!Xr2fUrIN3cjXGo1roWv-oxpBjHrOaYpBk3-TS+P%#G{)<(fr3<-0ATFg{PE zD}9c5YpQA4Z2rj7fdMZ}a+~CLcekmwxloxW3jdQfU7*cWxkO82Bt0!Y5^&PAIa)NfG!*D%8(+50{Zn7mVv+!Npdw_A+x%n}b zaJJ3nW#>9qu3MUC1mxegA7TV$DQpYHaEr6yB}sw{)!VQhfUFh*WQa|_hDRVMRJR2g zI$G7gRb#Ehu0SLCUC}Wlz)xa)&1_{Y*&fPTlOH+N$}{z6+&DQSZiM-9VB#MQRSRCb zoM?etXg6j=@VkuDgTSQ_DHS_u1h(@)t73L1npX^6o8o%F@LLNmL? za=RvPsjpzZc+7Q4X{S>y9t65>JrZMV zn!Vo3f+ms*op<@kb%&Stu2^n2_ivI{a!ApR*X-BUCKjdMN!?Kwz$LTUQ}9B~W7Wv89w$S26+BJ#eNwGaecjIrKcyM`HD!9GFwr3 zAW%m6chT58gF9J~LU;0$5JyjG-{R2`1814u?$pRfEs?|I`OE zPMc99-tTPy77Jp|jt7dLzSEAb6OwPuv4mR)%Lr15j`l!e=c^B__Sh3&tUr|*!_##x zPF;Lz#Bq%QGG7glLsEPWc%6^siY9mFo9$5#%!|-g;!sj8cjkhdc3`b)u9O3htX!;6 zcb>T(*{&NV0!*k@Fq*?Ajjc0?S}m6lc!loyh2&EY&kX*~hgmaq(sUN%d=hct1Ty$W z8`lG?+Y^^4pWFSj;VW^jVS&wb@l}WX*_VOkF;zfY#j-fH4MR9YtC9 zw=xSzho6>53U75c3Ty?(OLc**GbVUhHGN`rYqwYs2M4>(H+Ue!l$V!lH`|HZ%Q)I^ zcj@uIiq#F*uXGv;|8f!jHFEnxI!7ykSj8nVncs<78n0q^w{9co?SmoYb@>Kw)O<=l zN4QsCJdAi`3~P51FzAr2BoM%6mRaE34u9*5SD3NHIVcPVOZ5hAbG<2Z%pG?AO}4i8 zl}xk|BO87EZ5-(N=F*kh=O&ip8&a-A+}|*Ko@8M6)SQ0Y%kz-rIIg-Xixkd49#Wfl=$M)S-i>G*NyG~j18!2n*qKeMG%SSm|LJU&pYTYTc~kl z3*D7%*k{OTV+FK%{=S7NYOD6N7$RZBF7(rH0Dw(SGj&!0frSyX0%I+GEBrB-vf-Opvr{`)zBiJ2GQEXjKEh{2Yy zSU*rK4SRusC@9h}DFJi|Df_vn(<%espU<4773mE~aE0FJlvYk>$@QZVQc5Od%u%x= z(MmI@Ub1v#fbAobt-@PSNLx6+TU|Q0$46PXyMJe<6=21+KyaxCx$b zyN?YK_k|3`USvJeJhYX?IR{)yi1uQP;22D-&xGMTky%g+i5Z|h=pPMDQtj73VN=3Q zPX|s4W-x9erX4H#T+3fI;6&!%m`q{LXat@@x|2)?(lgvofNid~eGl=hxBH!1q}`Nz z&`~u*yx0+Qr&V?HF;F?DI@Z}Sm%pNG9H}RN%t829KQXujMkwpcq@@}&3lMxf&u&5i zsAO^7o-2T-i_`@K7#^#r4nU(fSWM7 zxU;<*mjaWt1#Il1mRvcI;$}i)34ohUSi&e+!t;5U4LWLH{V}R;B_BBSots+vOR95U zJuG3C8vK)=1H7I05uf0W)U=N}?(X52T~U}`R2cwMUvyK6h?G*wC+%>7j6_eCQwkCr z1S=tap!UB*DvV$-|5;a+P0q{)ti1KsT@MKUikZ29=)mH#$q$5D;&Pk6whQV~N=mSi z{4dxYG7l;7*1SNwuB>~}fBPB6V2n!E(;|!*RH#@i6&STB3B^WiMQuf1t1$~G^F~}J6GdwEZ8j{wyBZLA6=eW40L^Yy;OUilbK zB90Tlu;UIVS-7yaCtrD-JEtFBI<(JWQ}RHdtRH~;XCVK^GEbGoP3(UwDZ)P^yZ|Rw zOBui;#BC()Jxml?M#6dor3*U8!aI65nB&}KDj6rT8)~#`D2jcRi_2|=mx1-h5$4`C z57PdpmIIgakWs|Fml+E{s(^9{tfMbx?1olh63}&$*etV0pt#~*Eb#zv+P%N*VknSO z`tv5n_gomU1drf2KzY!7^CQsZg$?oJ1TS7Et=^{xMh}_V7y_o6%$04u*vbgNz?8i) zH&_7}a(c973jOB7P!Re$&bnh}HdNjFrT5^$Nk0AL zMY!Emkd1Zny-r&qK;{GewMq^{+HU2y(pMe_;o3g=LvDC6u~_~Xss;YMg#ToB9%%ay z{uos?%%L&nA3(`+u5ycaHX<>Zq}&H+ZK4BZ4!ytvl}#%KogXC;sN8_$q9aQ0Q;?y-V4c{t)eI?*l&&ZA-BDqXGP3t}jm87eggm|Sh%+w$D{Bloh5!`303lZ450w2flPtp7}9 z_RRx~YC^2Ev~(N&!O_8%HT0IyKD+-`vFR;}$f(z|EaErkdm__pNnlqHzhMhAd`$;G z!l=%9g5~8YoURR4KTd`&O!cKez`qw^=U|BD@CKNS@S^idDg6_f{41O7QrVPf@cf6x zpfv*-2<=~Oc)oFz+t}b&U%s9^aeH$T92^X1u*Cs|Cqu~TmR6X#PdU(x@_3Sgvk9N2 z+`s3!hsQy7nkPAu`bud^rGv^2mkuAHPE~rf#_jzD z>fupCS$1};M0B1NxxYGj$VHcgAoc+~nKa;cSZ9ntWF!V!asD4vj$GPvGBU;iszJ5; zz2~Ho^2uXTP!M&;Yg34U_Y0>MhSGsQ;ur2Sfl3v;YJDdjMp0<4OTTjjcHMY9FnOI< zEFJ<{j&{IclWq)9xrR_W0zz}_@UTzA%|B&Xp8vXa7vM`nHFq>`V1#yC=MEEtnF`{z zB_D9k8mqahbG;By@`QWWwJU-F^1HuF!)l=x0YIU^Kl1yn-6^o~P~zeF^FD)JDbz^D z^J;3~~{<&p`@VN^#m1O+d+zw$x=dlTQJJ+#;re5Vky~JfwYxV&Lkq z^^Zu!uao)c4f$g~$W+R7`PM9FpvuOy9T>t^iWKUK5(&!2|9a~^f2O(_U7xgq!0cX) zr~v7nuNltUq?@B62ebf`EU7;Qt#s}Q|0=V1I~6^#GmQv_62fJ`wkdp_)P2^?aUW^E zeIC~2fy6psoh4xybP)b_Ist|@1crX=87K^XL&#|^*MX;3AYSJFW5=PGVW&VC5pv_> z>>!aaB`yh_#<;`SM{lGHo~Z;o>f&?9$_wEe%wE%hX@Asp&Iu#7l~kaP@6@vJK)unf zdU#lC!@eM%m0@f)w!Kr11s-5QK{Fyu5oO=0W)LYV(cG@DKY5_%(_{~sn}Wc_fvxhq zh?PDmH9n?;0CM?o^HD|-xU2mYunFLPs|7PyR>}LHJB5WtxbHjg;zL1@H{zrTd;J|k z{=7zSkbO`6F-)!Pgh<#XcAOMIU?haqcY&~Ow?UO3V`A5k$gHNG#QMmccu0WDq;!|3 zTjhjo<_Hem^Pd7R)F8hKay~St%KZd^aiZ}e5DZsjlF^30$VtxC!^K3<0n-rxx3MKo ziaD{*nARPBupCj wu=zs?K@xFB59DJ_h+RJS>+nDz>nUVKKq^kuFRP~l;~VsfJ8!umh|4-qXUegFUf literal 0 HcmV?d00001 diff --git a/GalaxyViewer/Assets/Images/Logo.svg b/GalaxyViewer/Assets/Images/Logo.svg new file mode 100644 index 0000000..97b9d96 --- /dev/null +++ b/GalaxyViewer/Assets/Images/Logo.svg @@ -0,0 +1,119 @@ + + + + + + diff --git a/GalaxyViewer/Assets/Localization/LocalizationManager.cs b/GalaxyViewer/Assets/Localization/LocalizationManager.cs index b6fc5bd..e38578e 100644 --- a/GalaxyViewer/Assets/Localization/LocalizationManager.cs +++ b/GalaxyViewer/Assets/Localization/LocalizationManager.cs @@ -2,30 +2,29 @@ using System.Globalization; using System.Resources; -namespace GalaxyViewer.Assets.Localization +namespace GalaxyViewer.Assets.Localization; + +public class LocalizationManager : INotifyPropertyChanged { - public class LocalizationManager : INotifyPropertyChanged + private readonly ResourceManager _resourceManager = new(typeof(Strings)); + private CultureInfo _currentCulture = CultureInfo.CurrentCulture; + + public event PropertyChangedEventHandler? PropertyChanged; + + public string GetString(string name) { - private readonly ResourceManager _resourceManager = new(typeof(Strings)); - private CultureInfo _currentCulture = CultureInfo.CurrentCulture; + return _resourceManager.GetString(name, _currentCulture) ?? $"[{name}]"; + } - public event PropertyChangedEventHandler? PropertyChanged; - - public string GetString(string name) + public void SetCulture(string cultureCode) + { + var newCulture = new CultureInfo(cultureCode); + if (_currentCulture.Name != newCulture.Name) { - return _resourceManager.GetString(name, _currentCulture) ?? $"[{name}]"; - } - - public void SetCulture(string cultureCode) - { - var newCulture = new CultureInfo(cultureCode); - if (_currentCulture.Name != newCulture.Name) - { - _currentCulture = newCulture; - CultureInfo.CurrentCulture = newCulture; - CultureInfo.CurrentUICulture = newCulture; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); - } + _currentCulture = newCulture; + CultureInfo.CurrentCulture = newCulture; + CultureInfo.CurrentUICulture = newCulture; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); } } } \ No newline at end of file diff --git a/GalaxyViewer/Assets/Localization/Strings.en-US.resx b/GalaxyViewer/Assets/Localization/Strings.en-US.resx index e8d3471..3d14c34 100644 --- a/GalaxyViewer/Assets/Localization/Strings.en-US.resx +++ b/GalaxyViewer/Assets/Localization/Strings.en-US.resx @@ -25,12 +25,6 @@ - - GalaxyViewer - - - 0.1 - Welcome to GalaxyViewer 🌌 diff --git a/GalaxyViewer/Assets/Localization/Strings.resx b/GalaxyViewer/Assets/Localization/Strings.resx index 71a4efe..54388f7 100644 --- a/GalaxyViewer/Assets/Localization/Strings.resx +++ b/GalaxyViewer/Assets/Localization/Strings.resx @@ -23,12 +23,6 @@ This is in English (United States) language, as the default. It is in case there System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - GalaxyViewer - - - 0.1 - Welcome to GalaxyViewer 🌌 diff --git a/GalaxyViewer/Assets/grids.xml b/GalaxyViewer/Assets/grids.xml deleted file mode 100644 index f08a750..0000000 --- a/GalaxyViewer/Assets/grids.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - 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 - - - - - 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 - - - - \ No newline at end of file diff --git a/GalaxyViewer/Assets/preferences.xml b/GalaxyViewer/Assets/preferences.xml deleted file mode 100644 index e5bcc7a..0000000 --- a/GalaxyViewer/Assets/preferences.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - Default - Home - Atkinson Hyperlegible - en-US - 1721172337 - \ No newline at end of file diff --git a/GalaxyViewer/Commands/RelayCommand.cs b/GalaxyViewer/Commands/RelayCommand.cs index e81a879..01e1fdc 100644 --- a/GalaxyViewer/Commands/RelayCommand.cs +++ b/GalaxyViewer/Commands/RelayCommand.cs @@ -2,27 +2,26 @@ using System.Threading.Tasks; using System.Windows.Input; -namespace GalaxyViewer.Commands +namespace GalaxyViewer.Commands; + +public class RelayCommand(Func execute, Func? canExecute = null) + : ICommand { - public class RelayCommand(Func execute, Func? canExecute = null) - : ICommand + private readonly Func _execute = + execute ?? throw new ArgumentNullException(nameof(execute)); + + public bool CanExecute(object? parameter) => canExecute?.Invoke() ?? true; + + public async void Execute(object? parameter) => await _execute(); + + public event EventHandler? CanExecuteChanged { - private readonly Func _execute = - execute ?? throw new ArgumentNullException(nameof(execute)); + add => CommandManager.RequerySuggested += value; + remove => CommandManager.RequerySuggested -= value; + } - public bool CanExecute(object? parameter) => canExecute?.Invoke() ?? true; - - public async void Execute(object? parameter) => await _execute(); - - public event EventHandler? CanExecuteChanged - { - add => CommandManager.RequerySuggested += value; - remove => CommandManager.RequerySuggested -= value; - } - - private abstract class CommandManager - { - public static EventHandler? RequerySuggested { get; set; } - } + private abstract class CommandManager + { + public static EventHandler? RequerySuggested { get; set; } } } \ No newline at end of file diff --git a/GalaxyViewer/Controls/EmojiTextBlock.cs b/GalaxyViewer/Controls/EmojiTextBlock.cs index 8f9d018..e6820c8 100644 --- a/GalaxyViewer/Controls/EmojiTextBlock.cs +++ b/GalaxyViewer/Controls/EmojiTextBlock.cs @@ -5,53 +5,52 @@ using Avalonia.Controls; using Avalonia.Controls.Documents; using Avalonia.Media; -namespace GalaxyViewer.Controls +namespace GalaxyViewer.Controls; + +public abstract partial class EmojiTextBlock : TextBlock { - public abstract partial class EmojiTextBlock : TextBlock + private static readonly Regex EmojiRegex = MyRegex(); + + public static readonly StyledProperty EmojiFontFamilyProperty = + AvaloniaProperty.Register(nameof(EmojiFontFamily)); + + public FontFamily EmojiFontFamily { - private static readonly Regex EmojiRegex = MyRegex(); - - public static readonly StyledProperty EmojiFontFamilyProperty = - AvaloniaProperty.Register(nameof(EmojiFontFamily)); - - public FontFamily EmojiFontFamily - { - get => GetValue(EmojiFontFamilyProperty); - set => SetValue(EmojiFontFamilyProperty, value); - } - - public static readonly StyledProperty DefaultFontFamilyProperty = - AvaloniaProperty.Register(nameof(DefaultFontFamily)); - - public FontFamily DefaultFontFamily - { - get => GetValue(DefaultFontFamilyProperty); - set => SetValue(DefaultFontFamilyProperty, value); - } - - protected EmojiTextBlock() - { - this.PropertyChanged += (_, e) => - { - if (e.Property == TextProperty) - { - ApplyFonts(); - } - }; - } - - private void ApplyFonts() - { - if (string.IsNullOrEmpty(Text)) - return; - - var inlines = Text.Select(ch => new Run(ch.ToString()) { FontFamily = EmojiRegex.IsMatch(ch.ToString()) ? EmojiFontFamily : DefaultFontFamily }).Cast().ToList(); - - Inlines?.Clear(); - Inlines?.AddRange(inlines); - } - - [GeneratedRegex(@"[\u203C-\u3299\u1F000-\u1F644\u1F680-\u1F6FF\u1F700-\u1F77F\u1F780-\u1F7FF\u1F800-\u1F8FF\u1F900-\u1F9FF\u1FA00-\u1FA6F\u1FA70-\u1FAFF\u1FB00-\u1FBFF]", RegexOptions.Compiled)] - private static partial Regex MyRegex(); + get => GetValue(EmojiFontFamilyProperty); + set => SetValue(EmojiFontFamilyProperty, value); } + + public static readonly StyledProperty DefaultFontFamilyProperty = + AvaloniaProperty.Register(nameof(DefaultFontFamily)); + + public FontFamily DefaultFontFamily + { + get => GetValue(DefaultFontFamilyProperty); + set => SetValue(DefaultFontFamilyProperty, value); + } + + protected EmojiTextBlock() + { + this.PropertyChanged += (_, e) => + { + if (e.Property == TextProperty) + { + ApplyFonts(); + } + }; + } + + private void ApplyFonts() + { + if (string.IsNullOrEmpty(Text)) + return; + + var inlines = Text.Select(ch => new Run(ch.ToString()) { FontFamily = EmojiRegex.IsMatch(ch.ToString()) ? EmojiFontFamily : DefaultFontFamily }).Cast().ToList(); + + Inlines?.Clear(); + Inlines?.AddRange(inlines); + } + + [GeneratedRegex(@"[\u203C-\u3299\u1F000-\u1F644\u1F680-\u1F6FF\u1F700-\u1F77F\u1F780-\u1F7FF\u1F800-\u1F8FF\u1F900-\u1F9FF\u1FA00-\u1FA6F\u1FA70-\u1FAFF\u1FB00-\u1FBFF]", RegexOptions.Compiled)] + private static partial Regex MyRegex(); } \ No newline at end of file diff --git a/GalaxyViewer/GalaxyViewer.csproj b/GalaxyViewer/GalaxyViewer.csproj index 41c26ac..b79e0da 100644 --- a/GalaxyViewer/GalaxyViewer.csproj +++ b/GalaxyViewer/GalaxyViewer.csproj @@ -10,39 +10,41 @@ - - + + PreserveNewest PreserveNewest - + - + - - - - + + + + - + - - - + + + + + - - + + @@ -72,6 +74,6 @@ - + diff --git a/GalaxyViewer/Models/DebugViewModel.cs b/GalaxyViewer/Models/DebugViewModel.cs new file mode 100644 index 0000000..f793495 --- /dev/null +++ b/GalaxyViewer/Models/DebugViewModel.cs @@ -0,0 +1,10 @@ +using ReactiveUI; + +namespace GalaxyViewer.Models +{ + public class DebugViewModel(IScreen screen) : ReactiveObject, IRoutableViewModel + { + public string UrlPathSegment => "debug"; + public IScreen HostScreen { get; } = screen; + } +} \ No newline at end of file diff --git a/GalaxyViewer/Models/GridModel.cs b/GalaxyViewer/Models/GridModel.cs index af48ec9..b67ed00 100644 --- a/GalaxyViewer/Models/GridModel.cs +++ b/GalaxyViewer/Models/GridModel.cs @@ -1,9 +1,10 @@ -using System; +using LiteDB; namespace GalaxyViewer.Models { - public class Grid + public class GridModel { + [BsonId] public ObjectId Id { get; set; } public string GridNick { get; set; } public string GridName { get; set; } public string Platform { get; set; } @@ -15,20 +16,5 @@ namespace GalaxyViewer.Models public string Register { get; set; } public string Password { get; set; } public string Version { get; set; } - - public Grid(string gridNick, string gridName, string platform, string loginUri, string loginPage, string helperUri, string website, string support, string register, string password, string version) - { - GridNick = !string.IsNullOrEmpty(gridNick) ? gridNick : throw new ArgumentNullException(nameof(gridNick)); - GridName = !string.IsNullOrEmpty(gridName) ? gridName : throw new ArgumentNullException(nameof(gridName)); - Platform = !string.IsNullOrEmpty(platform) ? platform : throw new ArgumentNullException(nameof(platform)); - LoginUri = !string.IsNullOrEmpty(loginUri) ? loginUri : throw new ArgumentNullException(nameof(loginUri)); - LoginPage = !string.IsNullOrEmpty(loginPage) ? loginPage : throw new ArgumentNullException(nameof(loginPage)); - HelperUri = !string.IsNullOrEmpty(helperUri) ? helperUri : throw new ArgumentNullException(nameof(helperUri)); - Website = !string.IsNullOrEmpty(website) ? website : throw new ArgumentNullException(nameof(website)); - Support = !string.IsNullOrEmpty(support) ? support : throw new ArgumentNullException(nameof(support)); - Register = !string.IsNullOrEmpty(register) ? register : throw new ArgumentNullException(nameof(register)); - Password = !string.IsNullOrEmpty(password) ? password : throw new ArgumentNullException(nameof(password)); - Version = !string.IsNullOrEmpty(version) ? version : throw new ArgumentNullException(nameof(version)); - } } } \ No newline at end of file diff --git a/GalaxyViewer/Models/PreferencesModel.cs b/GalaxyViewer/Models/PreferencesModel.cs index 00f1d15..48ca3ec 100644 --- a/GalaxyViewer/Models/PreferencesModel.cs +++ b/GalaxyViewer/Models/PreferencesModel.cs @@ -1,85 +1,16 @@ -using System; using System.Collections.Generic; -using System.Xml.Serialization; +using LiteDB; namespace GalaxyViewer.Models { - [Serializable] public class PreferencesModel { - public PreferencesModel() - { - ThemeOptions = new List { "Light", "Dark", "Default" }; - LoginLocationOptions = new List { "Home", "Last Location" }; - FontOptions = new List { "Inter", "Atkinson Hyperlegible" }; - LanguageOptions = new List { "en-US" }; - } - - [XmlIgnore] - public List ThemeOptions { get; set; } - - [XmlIgnore] - public List LoginLocationOptions { get; set; } - - [XmlIgnore] - public List FontOptions { get; set; } - - [XmlIgnore] - public List LanguageOptions { get; set; } - - // Default values - private string _theme = "Default"; - public string Theme - { - get => _theme; - set - { - if (_theme != value) - { - _theme = value; - } - } - } - - private string _loginLocation = "Home"; - public string LoginLocation - { - get => _loginLocation; - set - { - if (_loginLocation != value) - { - _loginLocation = value; - } - } - } - - private string _font = "Atkinson Hyperlegible"; - public string Font - { - get => _font; - set - { - if (_font != value) - { - _font = value; - } - } - } - - private string _language = "en-US"; - public string Language - { - get => _language; - set - { - if (_language != value) - { - _language = value; - } - } - } - + [BsonId] public ObjectId Id { get; set; } + public string Theme { get; set; } + public string LoginLocation { get; set; } + public string Font { get; set; } + public string Language { get; set; } + public string SelectedGridNick { get; set; } public long LastSavedEpoch { get; set; } } } \ No newline at end of file diff --git a/GalaxyViewer/Models/PreferencesOptions.cs b/GalaxyViewer/Models/PreferencesOptions.cs new file mode 100644 index 0000000..f938e9f --- /dev/null +++ b/GalaxyViewer/Models/PreferencesOptions.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace GalaxyViewer.Models; + +public static class PreferencesOptions +{ + public static readonly List ThemeOptions = ["Light", "Dark", "Default"]; + public static readonly List LoginLocationOptions = ["Home", "Last Location"]; + public static readonly List FontOptions = ["Inter", "Atkinson Hyperlegible"]; + public static readonly List LanguageOptions = ["en-US"]; +} \ No newline at end of file diff --git a/GalaxyViewer/Services/GridService.cs b/GalaxyViewer/Services/GridService.cs index a3ba748..b5c6a5f 100644 --- a/GalaxyViewer/Services/GridService.cs +++ b/GalaxyViewer/Services/GridService.cs @@ -1,40 +1,43 @@ using System; using System.Collections.Generic; using System.IO; -using System.Xml.Linq; +using System.Linq; +using LiteDB; using GalaxyViewer.Models; namespace GalaxyViewer.Services { public class GridService { - public List ParseGridsFromXml() + private readonly string _databasePath; + + public GridService() { - var xmlFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "grids.xml"); - var xDoc = XDocument.Load(xmlFilePath); - var grids = new List(); + var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer"); + Directory.CreateDirectory(appDataPath); // Ensure the directory exists + _databasePath = Path.Combine(appDataPath, "data.db"); + } - if (xDoc.Root == null) return grids; - foreach (var gridElement in xDoc.Root.Elements("grid")) + public List GetAllGrids() + { + try { - var grid = new Grid( - gridElement.Element("gridnick")?.Value ?? string.Empty, - gridElement.Element("gridname")?.Value ?? string.Empty, - gridElement.Element("platform")?.Value ?? string.Empty, - gridElement.Element("loginuri")?.Value ?? string.Empty, - gridElement.Element("loginpage")?.Value ?? string.Empty, - gridElement.Element("helperuri")?.Value ?? string.Empty, - gridElement.Element("website")?.Value ?? string.Empty, - gridElement.Element("support")?.Value ?? string.Empty, - gridElement.Element("register")?.Value ?? string.Empty, - gridElement.Element("password")?.Value ?? string.Empty, - gridElement.Element("version")?.Value ?? string.Empty - ); - - grids.Add(grid); + using var db = new LiteDatabase(_databasePath); + var collection = db.GetCollection("grids"); + return collection.FindAll().ToList(); + } + catch (IOException ex) when (ex.Message.Contains("Read-only file system")) + { + // Handle read-only file system scenario + Console.Error.WriteLine("Error: The file system is read-only. Please check the file system permissions."); + return []; + } + catch (Exception ex) + { + // Handle other exceptions + Console.Error.WriteLine($"An error occurred: {ex.Message}"); + throw; } - - return grids; } } } \ No newline at end of file diff --git a/GalaxyViewer/Services/LiteDbService.cs b/GalaxyViewer/Services/LiteDbService.cs new file mode 100644 index 0000000..b6d2b69 --- /dev/null +++ b/GalaxyViewer/Services/LiteDbService.cs @@ -0,0 +1,158 @@ +using LiteDB; +using System; +using System.IO; +using GalaxyViewer.Models; +using Serilog; + +namespace GalaxyViewer.Services +{ + public class LiteDbService : IDisposable + { + private LiteDatabase? _database; + private readonly string _databasePath; + + public LiteDbService() + { + try + { + _databasePath = GetDatabasePath(); + Directory.CreateDirectory(Path.GetDirectoryName(_databasePath) ?? throw new InvalidOperationException()); // Ensure the directory exists + _database = new LiteDatabase(_databasePath); + Log.Information("LiteDbService initialized with database path: {DbPath}", _databasePath); + + // Call SeedDatabase to ensure the grids table is generated + SeedDatabase(); + } + catch (LiteException ex) + { + Log.Error(ex, "LiteDB exception occurred. Attempting to recreate the database."); + HandleDatabaseCorruption(); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to initialize LiteDbService"); + throw; + } + } + + private static string GetDatabasePath() + { + string appDataPath; + appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer"); + return Path.Combine(appDataPath, "data.db"); + } + + private void HandleDatabaseCorruption() + { + try + { + if (File.Exists(_databasePath)) + { + File.Delete(_databasePath); + } + _database = new LiteDatabase(_databasePath); + SeedDatabase(); + Log.Information("Database recreated successfully."); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to recreate the database."); + throw; + } + } + + public ILiteCollection GetCollection(string name) + { + Log.Information("Retrieving collection: {CollectionName}", name); + return _database.GetCollection(name); + } + + public LiteDatabase? Database() + { + return _database; + } + + private void SeedDatabase() + { + SeedPreferences(); + SeedGrids(); + } + + private void SeedPreferences() + { + try + { + var preferencesCollection = _database.GetCollection("preferences"); + if (preferencesCollection.Count() != 0) return; + var defaultPreferences = new PreferencesModel + { + Theme = "Default", + LoginLocation = "Home", + Font = "Atkinson Hyperlegible", + Language = "en-US", + SelectedGridNick = "agni", + LastSavedEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + }; + preferencesCollection.Insert(defaultPreferences); + Log.Information("Database seeded with default preferences"); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to seed preferences"); + } + } + + private void SeedGrids() + { + try + { + var gridsCollection = _database.GetCollection("grids"); + if (gridsCollection.Count() != 0) return; + var grids = new[] + { + new GridModel + { + GridNick = "agni", + GridName = "Second Life (agni)", + Platform = "SecondLife", + LoginUri = "https://login.agni.lindenlab.com/cgi-bin/login.cgi", + LoginPage = "http://secondlife.com/app/login/?channel=Second+Life+Release", + HelperUri = "https://secondlife.com/helpers/", + Website = "http://secondlife.com/", + Support = "http://secondlife.com/support/", + Register = "http://secondlife.com/registration/", + Password = "http://secondlife.com/account/request.php", + Version = "0" + }, + new GridModel + { + GridNick = "aditi", + GridName = "Second Life Beta (aditi)", + Platform = "SecondLife", + LoginUri = "https://login.aditi.lindenlab.com/cgi-bin/login.cgi", + LoginPage = "http://secondlife.com/app/login/?channel=Second+Life+Beta", + HelperUri = "http://aditi-secondlife.webdev.lindenlab.com/helpers/", + Website = "http://secondlife.com/", + Support = "http://secondlife.com/support/", + Register = "http://secondlife.com/registration/", + Password = "http://secondlife.com/account/request.php", + Version = "1" + } + }; + + gridsCollection.InsertBulk(grids); + Log.Information("Database seeded with default grids"); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to seed grids"); + } + } + + public void Dispose() + { + _database?.Dispose(); + Log.Information("LiteDbService disposed"); + } + } +} \ No newline at end of file diff --git a/GalaxyViewer/Services/NavigationService.cs b/GalaxyViewer/Services/NavigationService.cs index b016d98..9cace2b 100644 --- a/GalaxyViewer/Services/NavigationService.cs +++ b/GalaxyViewer/Services/NavigationService.cs @@ -2,34 +2,33 @@ using System.Collections.Generic; using Avalonia.Controls; -namespace GalaxyViewer.Services +namespace GalaxyViewer.Services; + +public class NavigationService { - public class NavigationService + private readonly Dictionary _routes = new(); + private readonly ContentControl _contentControl; + + public NavigationService(ContentControl contentControl) { - private readonly Dictionary _routes = new Dictionary(); - private readonly ContentControl _contentControl; + _contentControl = contentControl; + } - public NavigationService(ContentControl contentControl) + public void RegisterRoute(string uri, Type viewType) + { + _routes[uri] = viewType; + } + + public void NavigateTo(string uri) + { + if (_routes.TryGetValue(uri, out var viewType)) { - _contentControl = contentControl; + var view = (Control)Activator.CreateInstance(viewType)!; + _contentControl.Content = view; } - - public void RegisterRoute(string uri, Type viewType) + else { - _routes[uri] = viewType; - } - - public void Navigate(string uri) - { - if (_routes.TryGetValue(uri, out var viewType)) - { - var view = (Control)Activator.CreateInstance(viewType)!; - _contentControl.Content = view; - } - else - { - throw new InvalidOperationException($"No view registered for URI: {uri}"); - } + throw new InvalidOperationException($"No view registered for URI: {uri}"); } } } \ No newline at end of file diff --git a/GalaxyViewer/Services/PreferencesManager.cs b/GalaxyViewer/Services/PreferencesManager.cs index 9d4b959..600bbfc 100644 --- a/GalaxyViewer/Services/PreferencesManager.cs +++ b/GalaxyViewer/Services/PreferencesManager.cs @@ -1,77 +1,95 @@ using System; -using System.IO; -using System.Threading.Tasks; -using System.Xml.Serialization; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using LiteDB; using GalaxyViewer.Models; -using Serilog; +using System.Threading.Tasks; namespace GalaxyViewer.Services { - public class PreferencesManager + public sealed class PreferencesManager { - private readonly string _preferencesFilePath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer", - "preferences.xml"); + private readonly ILiteCollection _preferencesCollection; + private readonly ILiteCollection? _gridsCollection; - private bool _isLoadingPreferences; - private PreferencesModel _currentPreferences; - public PreferencesModel CurrentPreferences => _currentPreferences; + public event EventHandler? PreferencesChanged; - public PreferencesManager() + public PreferencesManager(LiteDbService? liteDbService) { - EnsurePreferencesDirectory(); + Debug.Assert(liteDbService != null, nameof(liteDbService) + " != null"); + var database = liteDbService.Database(); + Debug.Assert(database != null, nameof(database) + " != null"); + _preferencesCollection = database.GetCollection("preferences"); + _gridsCollection = database?.GetCollection("grids"); } - private void EnsurePreferencesDirectory() + public PreferencesModel CurrentPreferences { - var directoryPath = Path.GetDirectoryName(_preferencesFilePath); - if (!string.IsNullOrEmpty(directoryPath)) + get { - Directory.CreateDirectory(directoryPath); + var preferences = _preferencesCollection.FindOne(Query.All()); + return preferences ?? new PreferencesModel + { + Id = ObjectId.NewObjectId(), + Theme = "Default", + LoginLocation = "Home", + Font = "Atkinson Hyperlegible", + Language = "en-US", + LastSavedEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + SelectedGridNick = string.Empty + }; } } public async Task LoadPreferencesAsync() { - try + return await Task.Run(() => { - await using var stream = new FileStream(_preferencesFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - var serializer = new XmlSerializer(typeof(PreferencesModel)); - _currentPreferences = (PreferencesModel)serializer.Deserialize(stream)!; - _isLoadingPreferences = false; - return _currentPreferences; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to load preferences"); - _currentPreferences = new PreferencesModel(); - _isLoadingPreferences = false; - return _currentPreferences; - } + var preferences = _preferencesCollection.FindOne(Query.All()); + return preferences ?? new PreferencesModel + { + Id = ObjectId.NewObjectId(), + Theme = "Default", + LoginLocation = "Home", + Font = "Atkinson Hyperlegible", + Language = "en-US", + SelectedGridNick = "agni", + LastSavedEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + }; + }); } - public bool IsLoadingPreferences => _isLoadingPreferences; - - public event EventHandler? PreferencesChanged; - public async Task SavePreferencesAsync(PreferencesModel preferences) { - try + await Task.Run(() => { - await using var stream = new FileStream(_preferencesFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); - var serializer = new XmlSerializer(typeof(PreferencesModel)); - serializer.Serialize(stream, preferences); - _currentPreferences = preferences; - if (!_isLoadingPreferences) - { - PreferencesChanged?.Invoke(this, preferences); - } - } - catch (Exception ex) + _preferencesCollection.Upsert(preferences); + OnPreferencesChanged(preferences); + }); + } + + public Dictionary> GetCurrentPreferencesOptions() + { + return new Dictionary> { - Log.Error(ex, "Failed to save preferences"); - } + { "ThemeOptions", PreferencesOptions.ThemeOptions }, + { "LoginLocationOptions", PreferencesOptions.LoginLocationOptions }, + { "FontOptions", PreferencesOptions.FontOptions }, + { "LanguageOptions", PreferencesOptions.LanguageOptions } + }; + } + + public List GetGridOptions() + { + return _gridsCollection?.FindAll().Select(grid => grid.GridNick).ToList() ?? + []; + } + + private void OnPreferencesChanged(PreferencesModel preferences) + { + PreferencesChanged?.Invoke(this, preferences); } } } \ No newline at end of file diff --git a/GalaxyViewer/ViewModels/LoginViewModel.cs b/GalaxyViewer/ViewModels/LoginViewModel.cs index ce9be40..7a53b2d 100644 --- a/GalaxyViewer/ViewModels/LoginViewModel.cs +++ b/GalaxyViewer/ViewModels/LoginViewModel.cs @@ -1,67 +1,283 @@ -using System.Threading.Tasks; -using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Reactive; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Avalonia.Controls.Notifications; +using GalaxyViewer.Models; +using GalaxyViewer.Services; using ReactiveUI; -using System.ComponentModel; -using GalaxyViewer.Assets.Localization; +using Ursa.Controls; using Serilog; using OpenMetaverse; -namespace GalaxyViewer.ViewModels; - -public abstract partial class LoginViewModel : ReactiveObject +namespace GalaxyViewer.ViewModels { - private string _username; - private string _password; - private readonly GridClient _client = new GridClient(); - private readonly LocalizationManager _localizationManager; - - protected LoginViewModel(string username, string password, - LocalizationManager localizationManager) + public class LoginViewModel : ReactiveObject, IRoutableViewModel { - _username = username; - _password = password; - _localizationManager = localizationManager; - ReactiveCommand.Create(TryLoginAsync); - } + public string UrlPathSegment => "login"; - public string Username - { - get => _username; - set => this.RaiseAndSetIfChanged(ref _username, value); - } - - public string Password - { - get => _password; - set => this.RaiseAndSetIfChanged(ref _password, value); - } - - private static async void TryLoginAsync() - { - /*var loginParams = new LoginParams( - _client, - MyRegex().Split(Username.Trim())[0], // firstName - MyRegex().Split(Username.Trim()).Length > 1 - ? MyRegex().Split(Username.Trim())[1] - : "Resident", // lastName - Password, - ViewerName, - ViewerVersion, - DetermineGridUrl()) + public IScreen HostScreen { - LoginLocation = DetermineStartLocation(), - AgreeToTos = true // Assuming you have a way to get this value - }; - - var loginSuccess = await Task.Run(() => _client.Network.Login(loginParams)); - - if (loginSuccess) - { - Log.Information(LoginSuccess); + get + { + Debug.Assert(_routableViewModelImplementation?.HostScreen != null, + "_routableViewModelImplementation?.HostScreen != null"); + return _routableViewModelImplementation.HostScreen; + } } - else + + private readonly PreferencesViewModel _preferencesViewModel; + private string _username; + private string _password; + private readonly GridClient _client = new(); + private IRoutableViewModel? _routableViewModelImplementation; + private readonly GridService _gridService; + private ObservableCollection _grids; + private GridModel _selectedGrid; + + private const string DefaultGridUri = + "https://login.agni.lindenlab.com/cgi-bin/login.cgi"; + + public WindowToastManager? ToastManager { get; set; } + + public LoginViewModel() { - Log.Logger.Error(LoginFailed); - }*/ + _preferencesViewModel = new PreferencesViewModel(); + _username = string.Empty; + _password = string.Empty; + LoginLocations = _preferencesViewModel.LoginLocationOptions; + SelectedLoginLocation = _preferencesViewModel.SelectedLoginLocation; + TryLoginCommand = ReactiveCommand.CreateFromTask(TryLoginAsync); + _gridService = new GridService(); + _grids = new ObservableCollection(); + + LoadGrids(); + + // Subscribe to the ThrownExceptions property to handle errors + TryLoginCommand.ThrownExceptions.Subscribe(ex => + { + Log.Error("An error occurred during login: {Error}", ex.Message); + // Handle the error (e.g., show a dialog to the user) + _ = ShowLoginErrorAsync("An error occurred during login. Please try again."); + }); + + // Subscribe to the Network.LoginProgress event + _client.Network.LoginProgress += OnLoginProgress; + } + + public ObservableCollection LoginLocations { get; } + + public string Username + { + get => _username; + set => this.RaiseAndSetIfChanged(ref _username, value); + } + + public string Password + { + get => _password; + set => this.RaiseAndSetIfChanged(ref _password, value); + } + + public string SelectedLoginLocation + { + get => _preferencesViewModel.SelectedLoginLocation; + set + { + _preferencesViewModel.SelectedLoginLocation = value; + this.RaisePropertyChanged(); + } + } + + public string LoginStatusMessage { get; set; } + + public ObservableCollection Grids { get; set; } + + public GridModel? SelectedGrid + { + get + { + var selectedGrid = _grids.FirstOrDefault(g => + g.GridNick == _preferencesViewModel.SelectedGridNick); + if (selectedGrid == null) + { + selectedGrid = new GridModel + { + GridNick = "Second Life", + LoginUri = DefaultGridUri + }; + } + + return selectedGrid; + } + set + { + if (value == null) return; + _preferencesViewModel.SelectedGridNick = value.GridNick; + this.RaiseAndSetIfChanged(ref _selectedGrid, value); + } + } + + private void LoadGrids() + { + var grids = _gridService.GetAllGrids(); + _grids = new ObservableCollection(grids); + SelectedGrid = _grids.FirstOrDefault(g => + g.GridNick == _preferencesViewModel.SelectedGridNick); + } + + public ReactiveCommand TryLoginCommand { get; } + + private async Task ShowLoginErrorAsync(string errorMessage) + { + ToastManager?.Show( + new Toast( + errorMessage), + showIcon: true, + showClose: true, + type: NotificationType.Error + ); + await Task.CompletedTask; + } + + private async Task TryLoginAsync() + { + if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password)) + { + Log.Warning("Username or password is empty"); + await ShowLoginErrorAsync("Username or password is empty"); + return; + } + + var platformMap = new Dictionary + { + { OSPlatform.Windows, "Win" }, + { OSPlatform.Linux, "Lin" }, + { OSPlatform.OSX, "Mac" }, + { OSPlatform.Create("ANDROID"), "And" }, + { OSPlatform.Create("IOS"), "iOS" } + }; + + var ourPlatform = + platformMap.FirstOrDefault(kv => RuntimeInformation.IsOSPlatform(kv.Key)).Value ?? + "Unk"; + + var loginParams = _client?.Network?.DefaultLoginParams( + Username.Split(' ')[0], // firstName + Username.Contains(' ') ? Username.Split(' ')[1] : "Resident", // lastName + Password, + "GalaxyViewer", // ViewerName + "0.1.0" // ViewerVersion + ); + + if (loginParams == null) + { + Log.Error("Failed to create login parameters"); + await ShowLoginErrorAsync("Failed to create login parameters"); + return; + } + + loginParams.URI = + SelectedGrid?.LoginUri; // Set the login URI to the selected grid's URI + loginParams.MfaEnabled = true; // Inform the server that we support MFA + loginParams.Platform = ourPlatform; // Set the platform - our operating system + loginParams.PlatformVersion = + Environment.OSVersion.VersionString; // Set the platform version + loginParams.Start = + _preferencesViewModel + ?.SelectedLoginLocation; // Set the start location to the selected login location + loginParams.MfaHash = string.Empty; // Clear the MFA hash + + var loginSuccess = await Task.Run(() => _client?.Network?.Login(loginParams) ?? false); + + if (loginSuccess) + { + Log.Information("Login successful"); + await ProcessCapabilitiesAsync(); + } + else if (_client?.Network?.LoginMessage.Contains("MFA required") == true) + { + Log.Warning("MFA required"); + var mfaCode = await ShowMfaInputDialogAsync(); + if (!string.IsNullOrEmpty(mfaCode)) + { + loginParams.MfaHash = Utils.MD5(mfaCode); + loginSuccess = + await Task.Run(() => _client?.Network?.Login(loginParams) ?? false); + if (loginSuccess) + { + Log.Information("Login successful with MFA"); + await ProcessCapabilitiesAsync(); + } + else + { + Log.Error("Login failed with MFA: {Error}", _client?.Network?.LoginMessage); + await ShowLoginErrorAsync( + $"Login failed with MFA: {_client?.Network?.LoginMessage}"); + } + } + else + { + Log.Warning("MFA code entry was canceled"); + await ShowLoginErrorAsync("MFA code entry was canceled"); + } + } + else + { + Log.Error("Login failed: {Error}", _client?.Network?.LoginMessage); + await ShowLoginErrorAsync($"Login failed: {_client?.Network?.LoginMessage}"); + } + } + + private async Task ProcessCapabilitiesAsync() + { + var capabilities = _client.Network.CurrentSim.Caps; + if (capabilities != null) + { + // TODO: Process the capabilities as needed + } + + await Task.CompletedTask; + } + + private void OnLoginProgress(object? sender, LoginProgressEventArgs e) + { + Log.Information("Login progress: {Status}", e.Status); + switch (e.Status) + { + case LoginStatus.ConnectingToLogin: + LoginStatusMessage = "Connecting to login server..."; + break; + case LoginStatus.ConnectingToSim: + LoginStatusMessage = "Connecting to region..."; + break; + case LoginStatus.Redirecting: + LoginStatusMessage = "Redirecting..."; + break; + case LoginStatus.ReadingResponse: + LoginStatusMessage = "Reading response..."; + break; + case LoginStatus.Success: + LoginStatusMessage = $"Logged in as {_client.Self.Name}"; + break; + case LoginStatus.Failed: + LoginStatusMessage = $"Login failed: {e.Message}"; + break; + case LoginStatus.None: + default: + LoginStatusMessage = $"Unknown login status: {e.Status}"; + break; + } + } + + private async Task ShowMfaInputDialogAsync() + { + // TODO: Implement MFA input dialog + return await Task.FromResult(string.Empty); + } } } \ No newline at end of file diff --git a/GalaxyViewer/ViewModels/MainViewModel.cs b/GalaxyViewer/ViewModels/MainViewModel.cs index 19766c5..96d0f98 100644 --- a/GalaxyViewer/ViewModels/MainViewModel.cs +++ b/GalaxyViewer/ViewModels/MainViewModel.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.IO; using System.Reactive; using System.Threading.Tasks; @@ -7,7 +6,6 @@ using System.Windows.Input; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using GalaxyViewer.Services; using GalaxyViewer.Views; using OpenMetaverse; using ReactiveUI; @@ -17,12 +15,12 @@ namespace GalaxyViewer.ViewModels { public class MainViewModel : ViewModelBase { + private UserControl? _currentView; private bool _isLoggedIn; private readonly GridClient _client = new(); private string? _username; - public string? Username { get => _username; @@ -30,7 +28,6 @@ namespace GalaxyViewer.ViewModels } private string? _password; - public string? Password { get => _password; @@ -38,7 +35,6 @@ namespace GalaxyViewer.ViewModels } private string? _loginLocation; - public string? LoginLocation { get => _loginLocation; @@ -46,44 +42,42 @@ namespace GalaxyViewer.ViewModels } private string? _grid; - public string? Grid { get => _grid; set => this.RaiseAndSetIfChanged(ref _grid, value); } + public UserControl? CurrentView + { + get => _currentView; + set => this.RaiseAndSetIfChanged(ref _currentView, value); + } + public bool IsLoggedIn { get => _isLoggedIn; - set => this.RaiseAndSetIfChanged(ref _isLoggedIn, value); + set + { + this.RaiseAndSetIfChanged(ref _isLoggedIn, value); + CurrentView = value ? new LoggedInView() : new LoginView(); + } } public ReactiveCommand LogoutCommand { get; } public ReactiveCommand LoginCommand { get; } - public ICommand ShowPreferencesCommand { get; } - public ICommand ShowDevViewCommand { get; } - public ICommand ExitCommand { get; } - public MainViewModel(NavigationService navigationService) + public MainViewModel() { + _currentView = new LoginView(); IsLoggedIn = false; // By default you aren't logged in LogoutCommand = ReactiveCommand.Create(Logout); - LoginCommand = ReactiveCommand.Create(() => navigationService.Navigate("login")); - + LoginCommand = ReactiveCommand.CreateFromTask(Login); ShowPreferencesCommand = ReactiveCommand.Create(ShowPreferences); - ShowDevViewCommand = ReactiveCommand.Create(() => navigationService.Navigate("debug")); - ExitCommand = ReactiveCommand.Create(ExitApplication); - - // Navigate to login view on startup - if (!IsLoggedIn) - { - navigationService.Navigate("login"); - } } private void Logout() @@ -93,24 +87,22 @@ namespace GalaxyViewer.ViewModels IsLoggedIn = false; } - public async Task Login(string username, string password) + private async Task Login() { - /*try + try { - if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) + // Validate properties before using them + if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password)) { - await File.AppendAllTextAsync("error.log", - "Invalid login parameters for user: " + username); - return; + // Handle invalid login parameters + await File.AppendAllTextAsync("error.log", $"Invalid login parameters for user: {Username}"); + return; // Exit the method if validation fails } const string userAgent = "GalaxyViewer/0.1.0"; - var libreMetaverseLoginParams = - _client.Network.DefaultLoginParams(username, password, userAgent, LoginLocation, - Grid); + var loginParams = _client.Network.DefaultLoginParams(Username, Password, userAgent, LoginLocation, Grid); - var loginSuccess = - await Task.Run(() => _client.Network.Login(libreMetaverseLoginParams)); + var loginSuccess = await Task.Run(() => _client.Network.Login(loginParams)); if (loginSuccess) { @@ -118,19 +110,21 @@ namespace GalaxyViewer.ViewModels } else { - Log.Error("Failed to login user: {Username}", username); + // Handle failed login + Log.Error("Failed to login user: {Username}", Username); } } catch (Exception ex) { - Log.Error(ex, "An error occurred while logging in user: {Username}", username); - }*/ + // Handle any exceptions that occur during login + Log.Error(ex, "An error occurred while logging in user: {Username}", Username); + } } - private static void ShowPreferences() + private void ShowPreferences() { #if ANDROID - _navigationService.Navigate("preferences"); + CurrentView = new PreferencesView(); #else var preferencesWindow = new PreferencesWindow { @@ -142,11 +136,15 @@ namespace GalaxyViewer.ViewModels private void ExitApplication() { - if (Application.Current?.ApplicationLifetime is not - IClassicDesktopStyleApplicationLifetime - desktopLifetime) return; - Logout(); - desktopLifetime.Shutdown(); + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) + { + desktopLifetime.Shutdown(); + } + } + + public void Dispose() + { + _client.Network.Logout(); } } } \ No newline at end of file diff --git a/GalaxyViewer/ViewModels/PreferencesViewModel.cs b/GalaxyViewer/ViewModels/PreferencesViewModel.cs index 16e8107..0e91df8 100644 --- a/GalaxyViewer/ViewModels/PreferencesViewModel.cs +++ b/GalaxyViewer/ViewModels/PreferencesViewModel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading.Tasks; using System.Windows.Input; @@ -17,15 +18,24 @@ namespace GalaxyViewer.ViewModels { if (App.PreferencesManager != null) _preferencesManager = App.PreferencesManager; _preferences = App.PreferencesManager?.CurrentPreferences ?? new PreferencesModel(); - ThemeOptions = new ObservableCollection(_preferences.ThemeOptions); - LoginLocationOptions = new ObservableCollection(_preferences.LoginLocationOptions); - LanguageOptions = new ObservableCollection(_preferences.LanguageOptions); - FontOptions = new ObservableCollection(_preferences.FontOptions); + var preferencesOptions = _preferencesManager?.GetCurrentPreferencesOptions(); + ThemeOptions = new ObservableCollection(preferencesOptions?["ThemeOptions"] ?? + []); + LoginLocationOptions = new ObservableCollection( + preferencesOptions?["LoginLocationOptions"] ?? + []); + LanguageOptions = new ObservableCollection( + preferencesOptions?["LanguageOptions"] ?? + []); + FontOptions = + new ObservableCollection(preferencesOptions?["FontOptions"] ?? []); _selectedTheme = _preferences.Theme; _selectedLoginLocation = _preferences.LoginLocation; _selectedLanguage = _preferences.Language; _selectedFont = _preferences.Font; + _selectedGridNick = _preferences.SelectedGridNick; SaveCommand = new RelayCommand(async () => await SavePreferencesAsync()); + LoadPreferences(); } private async void LoadPreferences() @@ -34,20 +44,14 @@ namespace GalaxyViewer.ViewModels if (_preferencesManager != null) _preferences = await _preferencesManager.LoadPreferencesAsync(); - // Set selected options from preferences.xml - if (_selectedTheme != _preferences.Theme) - _selectedTheme = _preferences.Theme; - if (_selectedLoginLocation != _preferences.LoginLocation) - _selectedLoginLocation = _preferences.LoginLocation; - if (_selectedLanguage != _preferences.Language) - _selectedLanguage = _preferences.Language; - if (_selectedFont != _preferences.Font) - _selectedFont = _preferences.Font; + SelectedTheme = _preferences.Theme; + SelectedLoginLocation = _preferences.LoginLocation; + SelectedLanguage = _preferences.Language; + SelectedFont = _preferences.Font; + SelectedGridNick = _preferences.SelectedGridNick; + var gridOptions = _preferencesManager?.GetGridOptions(); + GridOptions = new ObservableCollection(gridOptions ?? []); - OnPropertyChanged(nameof(SelectedTheme)); - OnPropertyChanged(nameof(SelectedLoginLocation)); - OnPropertyChanged(nameof(SelectedLanguage)); - OnPropertyChanged(nameof(SelectedFont)); _isLoadingPreferences = false; } @@ -55,22 +59,22 @@ namespace GalaxyViewer.ViewModels public ObservableCollection LoginLocationOptions { get; private set; } public ObservableCollection LanguageOptions { get; private set; } public ObservableCollection FontOptions { get; private set; } + public ObservableCollection GridOptions { get; private set; } private string _selectedLoginLocation; private string _selectedLanguage; private string _selectedFont; private string _selectedTheme; + private string _selectedGridNick; public string SelectedTheme { get => _selectedTheme; set { - if (_selectedTheme != value && !_isLoadingPreferences) - { - _selectedTheme = value; - OnPropertyChanged(nameof(SelectedTheme)); - } + if (_selectedTheme == value || _isLoadingPreferences) return; + _selectedTheme = value; + OnPropertyChanged(nameof(SelectedTheme)); } } @@ -79,11 +83,9 @@ namespace GalaxyViewer.ViewModels get => _selectedLoginLocation; set { - if (_selectedLoginLocation != value && !_isLoadingPreferences) - { - _selectedLoginLocation = value; - OnPropertyChanged(nameof(SelectedLoginLocation)); - } + if (_selectedLoginLocation == value || _isLoadingPreferences) return; + _selectedLoginLocation = value; + OnPropertyChanged(nameof(SelectedLoginLocation)); } } @@ -92,11 +94,9 @@ namespace GalaxyViewer.ViewModels get => _selectedLanguage; set { - if (_selectedLanguage != value && !_isLoadingPreferences) - { - _selectedLanguage = value; - OnPropertyChanged(nameof(SelectedLanguage)); - } + if (_selectedLanguage == value || _isLoadingPreferences) return; + _selectedLanguage = value; + OnPropertyChanged(nameof(SelectedLanguage)); } } @@ -105,11 +105,20 @@ namespace GalaxyViewer.ViewModels get => _selectedFont; set { - if (_selectedFont != value && !_isLoadingPreferences) - { - _selectedFont = value; - OnPropertyChanged(nameof(SelectedFont)); - } + if (_selectedFont == value || _isLoadingPreferences) return; + _selectedFont = value; + OnPropertyChanged(nameof(SelectedFont)); + } + } + + public string SelectedGridNick + { + get => _selectedGridNick; + set + { + if (_selectedGridNick == value || _isLoadingPreferences) return; + _selectedGridNick = value; + OnPropertyChanged(nameof(SelectedGridNick)); } } @@ -119,6 +128,7 @@ namespace GalaxyViewer.ViewModels _preferences.LoginLocation = SelectedLoginLocation; _preferences.Language = SelectedLanguage; _preferences.Font = SelectedFont; + _preferences.SelectedGridNick = SelectedGridNick; if (_preferencesManager != null) await _preferencesManager.SavePreferencesAsync(_preferences); diff --git a/GalaxyViewer/ViewModels/SetProperty.cs b/GalaxyViewer/ViewModels/SetProperty.cs index 1b2c418..8053084 100644 --- a/GalaxyViewer/ViewModels/SetProperty.cs +++ b/GalaxyViewer/ViewModels/SetProperty.cs @@ -1,23 +1,13 @@ using System.ComponentModel; -using System.Runtime.CompilerServices; -namespace GalaxyViewer.ViewModels +namespace GalaxyViewer.ViewModels; + +public abstract partial class ViewModelBase : INotifyPropertyChanged { - public abstract partial class ViewModelBase : INotifyPropertyChanged + public new event PropertyChangedEventHandler? PropertyChanged; + + protected void OnPropertyChanged(string propertyName) { - public new event PropertyChangedEventHandler? PropertyChanged; - - protected bool SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null!) - { - if (Equals(field, value)) return false; - field = value; - OnPropertyChanged(propertyName); - return true; - } - - protected void OnPropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } \ No newline at end of file diff --git a/GalaxyViewer/Views/BaseWindow.cs b/GalaxyViewer/Views/BaseWindow.cs index a6fa4bc..6370d0e 100644 --- a/GalaxyViewer/Views/BaseWindow.cs +++ b/GalaxyViewer/Views/BaseWindow.cs @@ -1,25 +1,53 @@ -using Avalonia.Controls; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; using Avalonia.Styling; using GalaxyViewer.Models; +using Ursa.ReactiveUIExtension; -namespace GalaxyViewer.Views +namespace GalaxyViewer.Views; + +public class BaseWindow : ReactiveUrsaWindow, IStyleable { - public class BaseWindow : Window - { - protected BaseWindow() - { - Icon = new WindowIcon("Assets/galaxy.ico"); - CanResize = true; - App.PreferencesManager!.PreferencesChanged += OnPreferencesChanged; - ApplyTheme(App.PreferencesManager.LoadPreferencesAsync().Result.Theme); - } + Type IStyleable.StyleKey => typeof(Window); - private void OnPreferencesChanged(object? sender, PreferencesModel preferences) + protected BaseWindow() + { + Title = "GalaxyViewer"; + Icon = new WindowIcon("Assets/GalaxyViewerLogo.ico"); + CanResize = true; + if (App.PreferencesManager != null) + App.PreferencesManager.PreferencesChanged += OnPreferencesChanged; + + // Load preferences asynchronously without blocking the UI thread + _ = LoadPreferencesAsync(); + } + + private async Task LoadPreferencesAsync() + { + if (App.PreferencesManager == null) return; + var preferences = await App.PreferencesManager.LoadPreferencesAsync(); + await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => { ApplyTheme(preferences.Theme); - } + FontFamily = new FontFamily(preferences.Font); + }); + } - public void ApplyTheme(string theme) + private void OnPreferencesChanged(object? sender, PreferencesModel preferences) + { + Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => + { + ApplyTheme(preferences.Theme); + FontFamily = new FontFamily(preferences.Font); + }); + } + + internal void ApplyTheme(string theme) + { + Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => { RequestedThemeVariant = theme switch { @@ -27,6 +55,6 @@ namespace GalaxyViewer.Views "Dark" => ThemeVariant.Dark, _ => ThemeVariant.Default }; - } + }); } } \ No newline at end of file diff --git a/GalaxyViewer/Views/DebugView.axaml.cs b/GalaxyViewer/Views/DebugView.axaml.cs index f2d093f..1f6385c 100644 --- a/GalaxyViewer/Views/DebugView.axaml.cs +++ b/GalaxyViewer/Views/DebugView.axaml.cs @@ -1,18 +1,17 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; -namespace GalaxyViewer.Views -{ - public partial class DebugView : UserControl - { - public DebugView() - { - InitializeComponent(); - } +namespace GalaxyViewer.Views; - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } +public partial class DebugView : UserControl +{ + public DebugView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); } } \ No newline at end of file diff --git a/GalaxyViewer/Views/LoggedInView.axaml.cs b/GalaxyViewer/Views/LoggedInView.axaml.cs index 887e771..06541c1 100644 --- a/GalaxyViewer/Views/LoggedInView.axaml.cs +++ b/GalaxyViewer/Views/LoggedInView.axaml.cs @@ -1,18 +1,17 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; -namespace GalaxyViewer.Views -{ - public partial class LoggedInView : UserControl - { - public LoggedInView() - { - InitializeComponent(); - } +namespace GalaxyViewer.Views; - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } +public partial class LoggedInView : UserControl +{ + public LoggedInView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); } } \ No newline at end of file diff --git a/GalaxyViewer/Views/LoginView.axaml b/GalaxyViewer/Views/LoginView.axaml index b603aa3..39d4bc0 100644 --- a/GalaxyViewer/Views/LoginView.axaml +++ b/GalaxyViewer/Views/LoginView.axaml @@ -1,45 +1,49 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + +