diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5c7247b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/GalaxyViewer/Models/GridModel.cs b/GalaxyViewer/Models/GridModel.cs new file mode 100644 index 0000000..af48ec9 --- /dev/null +++ b/GalaxyViewer/Models/GridModel.cs @@ -0,0 +1,34 @@ +using System; + +namespace GalaxyViewer.Models +{ + public class Grid + { + public string GridNick { get; set; } + public string GridName { get; set; } + public string Platform { get; set; } + public string LoginUri { get; set; } + public string LoginPage { get; set; } + public string HelperUri { get; set; } + public string Website { get; set; } + public string Support { get; set; } + 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 new file mode 100644 index 0000000..7e8e1a4 --- /dev/null +++ b/GalaxyViewer/Models/PreferencesModel.cs @@ -0,0 +1,61 @@ +using System; + +namespace GalaxyViewer.Models +{ + public enum ThemeOptions + { + Light, + Dark, + System + } + + public enum LoginLocationOptions + { + Home, + LastLocation + } + + [Serializable] + public class PreferencesModel + { + private string _theme = Enum.TryParse(typeof(ThemeOptions), "System", out _) ? "System" : "Light"; + private string _loginLocation = Enum.TryParse(typeof(LoginLocationOptions), "LastLocation", out _) ? "LastLocation" : "Home"; + public long LastSavedEpoch { get; set; } // Hidden from UI, but stored + + public string Theme + { + get => _theme; + set + { + if (IsValidTheme(value)) + { + _theme = value; + } + else + { + throw new ArgumentException($"Invalid theme value: {value}"); + } + } + } + + public string LoginLocation + { + get => _loginLocation; + set + { + if (IsValidLoginLocation(value)) + { + _loginLocation = value; + } + else + { + throw new ArgumentException($"Invalid login location value: {value}"); + } + } + } + + // Assuming ThemeOptions and LoginLocationOptions are enums or similar + private bool IsValidTheme(string theme) => Enum.TryParse(typeof(ThemeOptions), theme, out _); + private bool IsValidLoginLocation(string location) => Enum.TryParse(typeof(LoginLocationOptions), location, out _); + } +} \ No newline at end of file diff --git a/GalaxyViewer/ViewModels/MainViewModel.cs b/GalaxyViewer/ViewModels/MainViewModel.cs index 3dfa5f1..e6999f8 100644 --- a/GalaxyViewer/ViewModels/MainViewModel.cs +++ b/GalaxyViewer/ViewModels/MainViewModel.cs @@ -1,8 +1,15 @@ -using Avalonia.Controls; +using System; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using GalaxyViewer.Views; using ReactiveUI; using System.Reactive; using System.Threading.Tasks; +using OpenMetaverse; +using System.IO; +using System.Windows.Input; +using Serilog; +using Avalonia; namespace GalaxyViewer.ViewModels { @@ -11,6 +18,14 @@ namespace GalaxyViewer.ViewModels private UserControl? _currentView; private bool _isLoggedIn; + private GridClient _client = new GridClient(); + + // Define properties for Username, Password, LoginLocation, and Grid. + public string? Username { get; set; } + public string? Password { get; set; } + public string? LoginLocation { get; set; } + public string? Grid { get; set; } + public UserControl? CurrentView { get => _currentView; @@ -29,30 +44,91 @@ namespace GalaxyViewer.ViewModels public ReactiveCommand LogoutCommand { get; } public ReactiveCommand LoginCommand { get; } + public ReactiveCommand LoginWithCurrentValues { get; } + + public ICommand ShowPreferencesCommand { get; } + + public ICommand ExitCommand { get; } public MainViewModel() { _currentView = new LoginView(); - IsLoggedIn = false; // Set this to true when the user logs in + IsLoggedIn = false; // By default you aren't logged in LogoutCommand = ReactiveCommand.Create(Logout); - LoginCommand = ReactiveCommand.CreateFromTask(Login); + LoginCommand = ReactiveCommand.Create(DisplayLoginView); + LoginWithCurrentValues = ReactiveCommand.CreateFromTask(Login); + + ShowPreferencesCommand = ReactiveCommand.Create(ShowPreferences); + + ExitCommand = ReactiveCommand.Create(ExitApplication); } private void Logout() { // Perform logout operation here + _client.Network.Logout(); IsLoggedIn = false; } - private Task Login() + private void DisplayLoginView() { - // Perform login operation here - // If login is successful, set IsLoggedIn to true - IsLoggedIn = true; - // If it doesn't work, we will return an error message + CurrentView = new LoginView(); + } - return Task.CompletedTask; + public async Task Login() + { + try + { + // Validate properties before using them + if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password)) + { + // Handle invalid login parameters + // For example, you might want to show an error message + // or log the invalid login attempt to a file + File.AppendAllText("error.log", "Invalid login parameters for user: " + Username); + } + + string userAgent = "GalaxyViewer/0.1.0"; + LoginParams libreMetaverseLoginParams = _client.Network.DefaultLoginParams(Username, Password, userAgent, LoginLocation, Grid); + + bool loginSuccess = await Task.Run(() => _client.Network.Login(libreMetaverseLoginParams)); + + if (loginSuccess) + { + IsLoggedIn = true; + } + else + { + // Handle failed login + Log.Error("Failed to login user: {Username}", Username); + } + } + catch (Exception ex) + { + // Handle any exceptions that occur during login + Log.Error(ex, "An error occurred while logging in user: {Username}", Username); + } + } + private void ShowPreferences() + { + CurrentView = new PreferencesView + { + DataContext = new PreferencesViewModel() + }; + } + + private void ExitApplication() + { + 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 new file mode 100644 index 0000000..d19ecba --- /dev/null +++ b/GalaxyViewer/ViewModels/PreferencesViewModel.cs @@ -0,0 +1,124 @@ +using GalaxyViewer.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Threading.Tasks; +using System.Xml.Serialization; +using ReactiveUI; +using System.Reactive; + +namespace GalaxyViewer.ViewModels +{ + public class PreferencesViewModel : ReactiveObject + { + public IEnumerable ThemeOptions => Enum.GetNames(typeof(ThemeOptions)); + + public IEnumerable LoginLocationOptions => Enum.GetNames(typeof(LoginLocationOptions)); + + private Lazy _lazyPreferences; + private readonly string _preferencesFilePath; + public ReactiveCommand SaveCommand { get; } + + public PreferencesViewModel() + { + _preferencesFilePath = GetPreferencesFilePath(); + _lazyPreferences = new Lazy(LoadOrCreatePreferences); + SaveCommand = ReactiveCommand.CreateFromTask(SavePreferencesAsync); + } + + public static async Task CreateAsync() + { + var viewModel = new PreferencesViewModel(); + await viewModel.LoadPreferencesAsync(); + return viewModel; + } + + private PreferencesModel Preferences => _lazyPreferences.Value; + + private static string GetPreferencesFilePath() + { + var appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var applicationFolder = Path.Combine(appDataFolder, "GalaxyViewer"); + Directory.CreateDirectory(applicationFolder); // CreateDirectory is no-op if exists + return Path.Combine(applicationFolder, "preferences.xml"); + } + + private PreferencesModel LoadOrCreatePreferences() + { + if (File.Exists(_preferencesFilePath)) + { + using var stream = new FileStream(_preferencesFilePath, FileMode.Open); + var serializer = new XmlSerializer(typeof(PreferencesModel)); + return serializer.Deserialize(stream) as PreferencesModel ?? new PreferencesModel(); + } + return new PreferencesModel + { + Theme = Enum.TryParse(typeof(ThemeOptions), "System", out object themeResult) ? themeResult.ToString() : Models.ThemeOptions.Light.ToString(), + LoginLocation = Enum.TryParse(typeof(LoginLocationOptions), "LastLocation", out object loginLocationResult) ? loginLocationResult.ToString() : Models.LoginLocationOptions.Home.ToString() + }; + } + + private string _theme; + public string Theme + { + get => Preferences.Theme; + set + { + if (Preferences.Theme != value) + { + Preferences.Theme = value; + this.RaisePropertyChanged(nameof(Theme)); + } + } + } + + private string _loginLocation; + public string LoginLocation + { + get => Preferences.LoginLocation; + set + { + if (Preferences.LoginLocation != value) + { + Preferences.LoginLocation = value; + this.RaisePropertyChanged(nameof(LoginLocation)); + } + } + } + + private string _statusMessage = string.Empty; + public string StatusMessage + { + get => _statusMessage; + set => this.RaiseAndSetIfChanged(ref _statusMessage, value); + } + + public async Task LoadPreferencesAsync() + { + await Task.Run(() => + { + _lazyPreferences = new Lazy(LoadOrCreatePreferences); + }); + } + + public async Task SavePreferencesAsync() + { + try + { + Preferences.LastSavedEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + + using var stream = new FileStream(_preferencesFilePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true); + var serializer = new XmlSerializer(typeof(PreferencesModel)); + + await Task.Run(() => serializer.Serialize(stream, Preferences)); + + StatusMessage = "Preferences saved"; + } + catch (Exception ex) + { + StatusMessage = $"Error saving preferences: {ex.Message}"; + } + } + } +} \ No newline at end of file diff --git a/GalaxyViewer/Views/LoginView.axaml.cs b/GalaxyViewer/Views/LoginView.axaml.cs index d51ba32..956b9d7 100644 --- a/GalaxyViewer/Views/LoginView.axaml.cs +++ b/GalaxyViewer/Views/LoginView.axaml.cs @@ -1,7 +1,6 @@ -using System; using Avalonia.Controls; using Avalonia.Markup.Xaml; -using GalaxyViewer.ViewModels; +using Avalonia.ReactiveUI; namespace GalaxyViewer.Views { @@ -17,10 +16,9 @@ namespace GalaxyViewer.Views AvaloniaXamlLoader.Load(this); } - private void LoginButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + private void LoginButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) { - var viewModel = (MainViewModel)DataContext!; - viewModel.LoginCommand.Execute().Subscribe(); + // Your event handling code here, which attempts the Login method } } } \ No newline at end of file diff --git a/GalaxyViewer/Views/MainView.axaml b/GalaxyViewer/Views/MainView.axaml index 5849a27..52c60d3 100644 --- a/GalaxyViewer/Views/MainView.axaml +++ b/GalaxyViewer/Views/MainView.axaml @@ -7,29 +7,27 @@ x:Class="GalaxyViewer.Views.MainView" x:DataType="vm:MainViewModel"> - - + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -55,6 +53,6 @@ - - + + diff --git a/GalaxyViewer/Views/PreferencesView.axaml b/GalaxyViewer/Views/PreferencesView.axaml new file mode 100644 index 0000000..9ee3c5f --- /dev/null +++ b/GalaxyViewer/Views/PreferencesView.axaml @@ -0,0 +1,17 @@ + + + + + + +