🚧 Progress towards working Android and Desktop builds, various Android tweaks, additions to ReadMe
@@ -1,27 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-android34.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationId>com.GalaxyViewer.GalaxyViewer</ApplicationId>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
||||
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-android34.0</TargetFramework>
|
||||
<AndroidUseLatestPlatformSdk>true</AndroidUseLatestPlatformSdk>
|
||||
<AndroidSdkVersion>30</AndroidSdkVersion>
|
||||
<SupportedOSPlatformVersion>24.0</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationId>com.GalaxyViewer.GalaxyViewer</ApplicationId>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
||||
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Icon.png">
|
||||
<Link>Resources\drawable\Icon.png</Link>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Android" Version="11.2.1"/>
|
||||
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.13"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Android" Version="11.2.1" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.13" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GalaxyViewer\GalaxyViewer.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GalaxyViewer\GalaxyViewer.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,23 +1,74 @@
|
||||
using Android.App;
|
||||
using Android;
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using AndroidX.Core.App;
|
||||
using AndroidX.Core.Content;
|
||||
using Avalonia;
|
||||
using Avalonia.Android;
|
||||
using Avalonia.ReactiveUI;
|
||||
using GalaxyViewer.Services;
|
||||
|
||||
namespace GalaxyViewer.Android;
|
||||
|
||||
[Activity(
|
||||
Label = "GalaxyViewer.Android",
|
||||
Theme = "@style/MyTheme.NoActionBar",
|
||||
Icon = "@drawable/icon",
|
||||
MainLauncher = true,
|
||||
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
|
||||
public class MainActivity : AvaloniaMainActivity<App>
|
||||
namespace GalaxyViewer.Android
|
||||
{
|
||||
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
|
||||
[Activity(
|
||||
Label = "GalaxyViewer.Android",
|
||||
Theme = "@style/MyTheme.NoActionBar",
|
||||
Icon = "@drawable/icon",
|
||||
MainLauncher = true,
|
||||
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
|
||||
public class MainActivity : AvaloniaMainActivity<App>
|
||||
{
|
||||
return base.CustomizeAppBuilder(builder)
|
||||
.WithInterFont()
|
||||
.UseReactiveUI();
|
||||
const int RequestStorageId = 0;
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
|
||||
{
|
||||
ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.WriteExternalStorage }, RequestStorageId);
|
||||
}
|
||||
else
|
||||
{
|
||||
InitializeLiteDbService();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
||||
{
|
||||
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if (requestCode == RequestStorageId)
|
||||
{
|
||||
if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
|
||||
{
|
||||
// Permission granted, proceed with file operations
|
||||
InitializeLiteDbService();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Permission denied, show a message to the user
|
||||
var message = "Permission to create data storage file denied. The application will not be able to function.";
|
||||
var toast = Toast.MakeText(this, message, ToastLength.Long);
|
||||
toast.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeLiteDbService()
|
||||
{
|
||||
// Initialize LiteDbService here
|
||||
var liteDbService = new LiteDbService();
|
||||
// Use liteDbService as needed
|
||||
}
|
||||
|
||||
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
|
||||
{
|
||||
return base.CustomizeAppBuilder(builder)
|
||||
.WithInterFont()
|
||||
.UseReactiveUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-sdk android:targetSdkVersion="21" />
|
||||
<application android:label="GalaxyViewer" android:icon="@drawable/Icon" />
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto"
|
||||
package="com.example.yourapp">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
|
||||
<application
|
||||
android:label="GalaxyViewer"
|
||||
android:icon="@drawable/icon"
|
||||
android:allowBackup="true"
|
||||
android:supportsRtl="true">
|
||||
<!-- Your activities and other components -->
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="splash_background">#212121</color>
|
||||
<color name="splash_background">#0f1729</color>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="splash_background">#FFFFFF</color>
|
||||
<color name="splash_background">#0f1729</color>
|
||||
</resources>
|
||||
|
||||
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 16 KiB |
@@ -8,8 +8,11 @@
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="GalaxyViewerLogo1.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
sodipodi:docname="Logo.svg"
|
||||
inkscape:export-filename="Logo.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -23,15 +26,15 @@
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="2.3874278"
|
||||
inkscape:cx="432.05495"
|
||||
inkscape:cy="516.24598"
|
||||
inkscape:window-width="3697"
|
||||
inkscape:window-height="2130"
|
||||
inkscape:window-x="132"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:zoom="3.3763328"
|
||||
inkscape:cx="81.745497"
|
||||
inkscape:cy="150.01483"
|
||||
inkscape:window-width="2510"
|
||||
inkscape:window-height="1412"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:current-layer="g7"
|
||||
inkscape:export-bgcolor="#ffffff00" /><defs
|
||||
id="defs1" /><g
|
||||
inkscape:label="Layer 1"
|
||||
@@ -79,19 +82,24 @@
|
||||
d="M 51.751,23.435 V 5.474 H 44.645 V 31.449 H 40.562 V 19.958 H 30.103 V 12.096 H 20.906 V 30.538 H 18.624 V 24.796 H 7.902 V 16.934 H 0 v 7.862 5.742 24.195 h 16.371 2.253 2.282 7.561 1.636 5.268 5.191 19.645 V 38.826 31.449 23.434 H 51.751 Z M 4.843,26.899 H 2.239 v -6.183 h 2.604 z m 6.25,12.021 H 8.489 V 28.439 h 2.604 z m 4.297,-4.297 h -2.604 v -6.184 h 2.604 z m 9.765,-4.632 h -2.604 v -6.183 h 2.604 z m 0,-8.887 H 22.551 V 14.92 h 2.604 z m 4.428,17.528 H 26.979 V 32.45 h 2.604 z M 34.4,29.991 H 31.796 V 23.808 H 34.4 Z m 14.713,5.549 h -2.604 v -6.184 h 2.604 z m 0,-11.345 h -2.604 v -6.183 h 2.604 z m 0,-10.31 H 46.509 V 7.703 h 2.604 z m 5.469,26.894 H 51.978 V 30.3 h 2.604 z"
|
||||
id="path1-19" />
|
||||
</g></g><g
|
||||
style="fill:#000000"
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
id="g4-4"
|
||||
transform="matrix(0.31699982,0.16860553,0,0.35510983,13.001511,229.21359)"><g
|
||||
id="SVGRepo_bgCarrier-9-3"
|
||||
stroke-width="0" /><g
|
||||
stroke-width="0"
|
||||
style="fill:#000000;fill-opacity:1" /><g
|
||||
id="SVGRepo_tracerCarrier-7-6"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" /><g
|
||||
id="SVGRepo_iconCarrier-7-4"> <g
|
||||
id="g1-99-1"> <path
|
||||
stroke-linejoin="round"
|
||||
style="fill:#000000;fill-opacity:1" /><g
|
||||
id="SVGRepo_iconCarrier-7-4"
|
||||
style="fill:#000000;fill-opacity:1"> <g
|
||||
id="g1-99-1"
|
||||
style="fill:#000000;fill-opacity:1"> <path
|
||||
fill="#231f20"
|
||||
d="m 91.963,80.982 0.023,-0.013 -7.285,-12.617 h 2.867 v -0.013 c 0.598,0 1.083,-0.484 1.083,-1.082 0,-0.185 -0.059,-0.351 -0.14,-0.503 L 88.53,66.743 81.793,55.074 h 1.639 v -0.009 c 0.427,0 0.773,-0.347 0.773,-0.772 0,-0.132 -0.042,-0.25 -0.1,-0.359 l 0.013,-0.008 -9.802,-16.979 -0.01,0.006 c -0.216,-0.442 -0.66,-0.754 -1.186,-0.754 -0.524,0 -0.968,0.311 -1.185,0.752 l -0.005,-0.003 -9.802,16.978 0.002,10e-4 c -0.061,0.11 -0.105,0.231 -0.105,0.366 0,0.426 0.346,0.772 0.773,0.772 v 0.009 h 1.661 l -6.737,11.669 0.003,0.001 c -0.085,0.155 -0.147,0.324 -0.147,0.513 0,0.598 0.485,1.082 1.083,1.082 v 0.013 h 2.894 l -2.1,3.638 -8.399,-14.548 h 4.046 v -0.018 c 0.844,0 1.528,-0.685 1.528,-1.528 0,-0.26 -0.071,-0.502 -0.186,-0.717 L 56.459,55.17 46.952,38.703 h 2.313 v -0.012 c 0.603,0 1.091,-0.488 1.091,-1.092 0,-0.186 -0.059,-0.353 -0.141,-0.506 L 50.234,37.082 36.4,13.125 36.395,13.128 c -0.305,-0.625 -0.94,-1.06 -1.683,-1.06 -0.758,0 -1.408,0.452 -1.704,1.1 l -13.807,23.914 0.003,0.002 c -0.086,0.156 -0.148,0.326 -0.148,0.516 0,0.604 0.488,1.092 1.09,1.092 v 0.012 h 2.345 l -9.395,16.272 c -0.195,0.257 -0.316,0.573 -0.316,0.92 0,0.844 0.685,1.528 1.528,1.528 v 0.018 h 4.084 L 8.252,75.007 c -0.24,0.314 -0.387,0.702 -0.387,1.128 0,1.032 0.838,1.87 1.871,1.87 v 0.021 h 19.779 v 8.43 c 0,0.815 0.661,1.477 1.476,1.477 h 7.383 c 0.815,0 1.477,-0.661 1.477,-1.477 v -8.43 h 16.12 l -1.699,2.943 0.003,0.002 c -0.104,0.189 -0.18,0.396 -0.18,0.628 0,0.732 0.593,1.325 1.325,1.325 v 0.015 h 14.016 v 3.941 c 0,0.578 0.469,1.046 1.046,1.046 h 5.232 c 0.578,0 1.046,-0.468 1.046,-1.046 v -3.941 h 14.05 v -0.015 c 0.732,0 1.326,-0.593 1.326,-1.325 -10e-4,-0.227 -0.072,-0.431 -0.173,-0.617 z"
|
||||
id="path1-7-6" /> </g> </g></g><g
|
||||
id="path1-7-6"
|
||||
style="fill:#000000;fill-opacity:1" /> </g> </g></g><g
|
||||
style="fill:#a2aac3;fill-opacity:1"
|
||||
id="g6"
|
||||
transform="matrix(0.04838256,0,0.0277314,0.02641433,19.746211,217.7951)"><g
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -1,26 +1,43 @@
|
||||
using LiteDB;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using LiteDB;
|
||||
using GalaxyViewer.Models;
|
||||
|
||||
namespace GalaxyViewer.Services
|
||||
{
|
||||
public class GridService
|
||||
{
|
||||
private const string _databasePath = "Filename=Grids.db; Connection=shared";
|
||||
private readonly string _databasePath;
|
||||
|
||||
public void AddGrid(GridModel grid)
|
||||
public GridService()
|
||||
{
|
||||
using var db = new LiteDatabase(_databasePath);
|
||||
var grids = db.GetCollection<GridModel>("grids");
|
||||
grids.Insert(grid);
|
||||
var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer");
|
||||
Directory.CreateDirectory(appDataPath); // Ensure the directory exists
|
||||
_databasePath = Path.Combine(appDataPath, "data.db");
|
||||
}
|
||||
|
||||
public List<GridModel> GetAllGrids()
|
||||
{
|
||||
using var db = new LiteDatabase(_databasePath);
|
||||
var grids = db.GetCollection<GridModel>("grids");
|
||||
return grids.FindAll().ToList();
|
||||
try
|
||||
{
|
||||
using var db = new LiteDatabase(_databasePath);
|
||||
var collection = db.GetCollection<GridModel>("grids");
|
||||
return collection.FindAll().ToList();
|
||||
}
|
||||
catch (IOException ex) when (ex.Message.Contains("Read-only file system"))
|
||||
{
|
||||
// Handle read-only file system scenario
|
||||
Console.Error.WriteLine("Error: The file system is read-only. Please check the file system permissions.");
|
||||
return [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle other exceptions
|
||||
Console.Error.WriteLine($"An error occurred: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using LiteDB;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using GalaxyViewer.Models;
|
||||
using Serilog;
|
||||
|
||||
@@ -9,25 +8,26 @@ namespace GalaxyViewer.Services
|
||||
{
|
||||
public class LiteDbService : IDisposable
|
||||
{
|
||||
private readonly LiteDatabase? _database;
|
||||
private LiteDatabase? _database;
|
||||
private readonly string _databasePath;
|
||||
|
||||
public LiteDbService()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dbPath =
|
||||
Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"GalaxyViewer", "data.db");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(dbPath) ??
|
||||
throw new
|
||||
InvalidOperationException()); // Ensure the directory exists
|
||||
_database = new LiteDatabase(dbPath);
|
||||
Log.Information("LiteDbService initialized with database path: {DbPath}", dbPath);
|
||||
_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");
|
||||
@@ -35,6 +35,32 @@ namespace GalaxyViewer.Services
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDatabasePath()
|
||||
{
|
||||
string appDataPath;
|
||||
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GalaxyViewer");
|
||||
return Path.Combine(appDataPath, "data.db");
|
||||
}
|
||||
|
||||
private void HandleDatabaseCorruption()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_databasePath))
|
||||
{
|
||||
File.Delete(_databasePath);
|
||||
}
|
||||
_database = new LiteDatabase(_databasePath);
|
||||
SeedDatabase();
|
||||
Log.Information("Database recreated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to recreate the database.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public ILiteCollection<T> GetCollection<T>(string name)
|
||||
{
|
||||
Log.Information("Retrieving collection: {CollectionName}", name);
|
||||
@@ -56,8 +82,7 @@ namespace GalaxyViewer.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
var preferencesCollection =
|
||||
_database.GetCollection<PreferencesModel>("preferences");
|
||||
var preferencesCollection = _database.GetCollection<PreferencesModel>("preferences");
|
||||
if (preferencesCollection.Count() != 0) return;
|
||||
var defaultPreferences = new PreferencesModel
|
||||
{
|
||||
@@ -91,8 +116,7 @@ namespace GalaxyViewer.Services
|
||||
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",
|
||||
LoginPage = "http://secondlife.com/app/login/?channel=Second+Life+Release",
|
||||
HelperUri = "https://secondlife.com/helpers/",
|
||||
Website = "http://secondlife.com/",
|
||||
Support = "http://secondlife.com/support/",
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace GalaxyViewer.ViewModels
|
||||
private ObservableCollection<GridModel?> _grids;
|
||||
private GridModel _selectedGrid;
|
||||
|
||||
private const string DefaultGridUri =
|
||||
"https://login.agni.lindenlab.com/cgi-bin/login.cgi";
|
||||
|
||||
public WindowToastManager? ToastManager { get; set; }
|
||||
|
||||
public LoginViewModel()
|
||||
@@ -50,6 +53,7 @@ namespace GalaxyViewer.ViewModels
|
||||
SelectedLoginLocation = _preferencesViewModel.SelectedLoginLocation;
|
||||
TryLoginCommand = ReactiveCommand.CreateFromTask(TryLoginAsync);
|
||||
_gridService = new GridService();
|
||||
_grids = new ObservableCollection<GridModel?>();
|
||||
|
||||
LoadGrids();
|
||||
|
||||
@@ -60,6 +64,9 @@ namespace GalaxyViewer.ViewModels
|
||||
// Handle the error (e.g., show a dialog to the user)
|
||||
_ = ShowLoginErrorAsync("An error occurred during login. Please try again.");
|
||||
});
|
||||
|
||||
// Subscribe to the Network.LoginProgress event
|
||||
_client.Network.LoginProgress += OnLoginProgress;
|
||||
}
|
||||
|
||||
public ObservableCollection<string> LoginLocations { get; }
|
||||
@@ -86,15 +93,27 @@ namespace GalaxyViewer.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<GridModel?> Grids
|
||||
{
|
||||
get => _grids;
|
||||
set => this.RaiseAndSetIfChanged(ref _grids, value);
|
||||
}
|
||||
public string LoginStatusMessage { get; set; }
|
||||
|
||||
public ObservableCollection<GridModel> Grids { get; set; }
|
||||
|
||||
public GridModel? SelectedGrid
|
||||
{
|
||||
get => _grids.FirstOrDefault(g => g.GridNick == _preferencesViewModel.SelectedGridNick);
|
||||
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;
|
||||
@@ -106,8 +125,9 @@ namespace GalaxyViewer.ViewModels
|
||||
private void LoadGrids()
|
||||
{
|
||||
var grids = _gridService.GetAllGrids();
|
||||
Grids = new ObservableCollection<GridModel?>(grids);
|
||||
SelectedGrid = Grids.FirstOrDefault(g => g != null && g.GridNick == _preferencesViewModel.SelectedGridNick);
|
||||
_grids = new ObservableCollection<GridModel?>(grids);
|
||||
SelectedGrid = _grids.FirstOrDefault(g =>
|
||||
g.GridNick == _preferencesViewModel.SelectedGridNick);
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> TryLoginCommand { get; }
|
||||
@@ -146,7 +166,7 @@ namespace GalaxyViewer.ViewModels
|
||||
platformMap.FirstOrDefault(kv => RuntimeInformation.IsOSPlatform(kv.Key)).Value ??
|
||||
"Unk";
|
||||
|
||||
var loginParams = _client.Network.DefaultLoginParams(
|
||||
var loginParams = _client?.Network?.DefaultLoginParams(
|
||||
Username.Split(' ')[0], // firstName
|
||||
Username.Contains(' ') ? Username.Split(' ')[1] : "Resident", // lastName
|
||||
Password,
|
||||
@@ -154,39 +174,50 @@ namespace GalaxyViewer.ViewModels
|
||||
"0.1.0" // ViewerVersion
|
||||
);
|
||||
|
||||
loginParams.URI = SelectedGrid.LoginUri; // Set the login URI to the selected grid's URI
|
||||
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
|
||||
?.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));
|
||||
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"))
|
||||
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));
|
||||
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);
|
||||
Log.Error("Login failed with MFA: {Error}", _client?.Network?.LoginMessage);
|
||||
await ShowLoginErrorAsync(
|
||||
$"Login failed with MFA: {_client.Network.LoginMessage}");
|
||||
$"Login failed with MFA: {_client?.Network?.LoginMessage}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -197,8 +228,49 @@ namespace GalaxyViewer.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Login failed: {Error}", _client.Network.LoginMessage);
|
||||
await ShowLoginErrorAsync($"Login failed: {_client.Network.LoginMessage}");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,17 +38,16 @@
|
||||
ItemsSource="{Binding LoginLocations}"
|
||||
SelectedItem="{Binding SelectedLoginLocation}"
|
||||
Margin="5,0" />
|
||||
<!--
|
||||
<ComboBox Grid.Column="5" Name="GridSelection"
|
||||
ItemsSource="{Binding Grids}"
|
||||
SelectedItem="{Binding SelectedGrid}"
|
||||
Margin="5,0" />
|
||||
-->
|
||||
<!-- TODO: Fix the visibility of the GridSelection ComboBox options -->
|
||||
<Button Grid.Column="6" Name="ButtonLogin"
|
||||
Content="{Binding Converter={StaticResource LocalizedStringConverter}, ConverterParameter=LoginScreenLoginButton}"
|
||||
Command="{Binding TryLoginCommand}"
|
||||
Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="0" Text="{Binding LoginStatusMessage}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -62,6 +62,13 @@ Features unique to this viewer (compared to the stock viewer) will include:
|
||||
- [ ] RLV Support
|
||||
- [ ] Automation Support
|
||||
- [ ] Accessibility
|
||||
- [ ] Screen Reader Support
|
||||
- [ ] High Contrast Mode
|
||||
- [ ] Keyboard Navigation
|
||||
- [ ] Voice Commands
|
||||
- [ ] Text-to-Speech
|
||||
- [ ] Speech-to-Text
|
||||
- [x] Localization
|
||||
- [ ] Discord Rich Presence (Desktop only)
|
||||
|
||||
## Installation
|
||||
@@ -89,7 +96,7 @@ Also, please note the [Code of Conduct](CODE_OF_CONDUCT.md) for this project.
|
||||
|
||||
Please note that while the viewer is in the early development stage, it is not recommended for everyday use and no support will be given. This ReadMe will be updated with support information once the viewer is in a more stable state.
|
||||
|
||||
If you have any questions or concerns, please contact [Galaxy Littlepaws](mailto:support@galaxyviewer.com) and include "Galaxy Viewer" in the subject line.
|
||||
If you have any questions or concerns, please feel free to use our [Discussion](https://github.com/GalaxyViewer/GalaxyViewer/discussions) forum.
|
||||
|
||||
## License
|
||||
|
||||
|
||||