diff --git a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml b/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml
new file mode 100644
index 0000000..ae7a314
--- /dev/null
+++ b/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="AvaloniaProject">
+ <option name="projectPerEditor">
+ <map>
+ <entry key="MatrixRoomUtils.Desktop/App.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" />
+ <entry key="MatrixRoomUtils.Desktop/LoginWindow.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" />
+ <entry key="MatrixRoomUtils.Desktop/MainWindow.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" />
+ <entry key="MatrixRoomUtils.Desktop/RoomListEntry.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" />
+ </map>
+ </option>
+ </component>
+</project>
\ No newline at end of file
diff --git a/MatrixRoomUtils.Desktop/App.axaml b/MatrixRoomUtils.Desktop/App.axaml
new file mode 100644
index 0000000..9c99838
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/App.axaml
@@ -0,0 +1,10 @@
+<Application xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ x:Class="MatrixRoomUtils.Desktop.App"
+ RequestedThemeVariant="Default">
+ <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
+
+ <Application.Styles>
+ <FluentTheme />
+ </Application.Styles>
+</Application>
\ No newline at end of file
diff --git a/MatrixRoomUtils.Desktop/App.axaml.cs b/MatrixRoomUtils.Desktop/App.axaml.cs
new file mode 100644
index 0000000..3dfcdee
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/App.axaml.cs
@@ -0,0 +1,43 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using MatrixRoomUtils.Core.Services;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace MatrixRoomUtils.Desktop;
+
+public partial class App : Application {
+ public IHost host { get; set; }
+
+ public override void Initialize() {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted() {
+ host = Host.CreateDefaultBuilder().ConfigureServices((ctx, services) => {
+ services.AddScoped<MRUDesktopConfiguration>();
+ services.AddScoped<TieredStorageService>(x =>
+ new(
+ cacheStorageProvider: new FileStorageProvider(x.GetService<MRUDesktopConfiguration>().CacheStoragePath),
+ dataStorageProvider: new FileStorageProvider(x.GetService<MRUDesktopConfiguration>().CacheStoragePath)
+ )
+ );
+ services.AddRoryLibMatrixServices();
+ // foreach (var commandClass in new ClassCollector<ICommand>().ResolveFromAllAccessibleAssemblies()) {
+ // Console.WriteLine($"Adding command {commandClass.Name}");
+ // services.AddScoped(typeof(ICommand), commandClass);
+ // }
+ services.AddScoped<MRUStorageWrapper>();
+ services.AddScoped<MainWindow>();
+ services.AddSingleton(this);
+ }).UseConsoleLifetime().Build();
+
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
+ var scopeFac = host.Services.GetService<IServiceScopeFactory>();
+ var scope = scopeFac.CreateScope();
+ desktop.MainWindow = scope.ServiceProvider.GetRequiredService<MainWindow>();
+ }
+ base.OnFrameworkInitializationCompleted();
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/FileStorageProvider.cs b/MatrixRoomUtils.Desktop/FileStorageProvider.cs
new file mode 100644
index 0000000..36025eb
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/FileStorageProvider.cs
@@ -0,0 +1,35 @@
+using System.Text.Json;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces.Services;
+using Microsoft.Extensions.Logging;
+
+namespace MatrixRoomUtils.Desktop;
+
+public class FileStorageProvider : IStorageProvider {
+ private readonly ILogger<FileStorageProvider> _logger;
+
+ public string TargetPath { get; }
+
+ /// <summary>
+ /// Creates a new instance of <see cref="FileStorageProvider" />.
+ /// </summary>
+ /// <param name="targetPath"></param>
+ public FileStorageProvider(string targetPath) {
+ new Logger<FileStorageProvider>(new LoggerFactory()).LogInformation("test");
+ Console.WriteLine($"Initialised FileStorageProvider with path {targetPath}");
+ TargetPath = targetPath;
+ if(!Directory.Exists(targetPath)) {
+ Directory.CreateDirectory(targetPath);
+ }
+ }
+
+ public async Task SaveObjectAsync<T>(string key, T value) => await File.WriteAllTextAsync(Path.Join(TargetPath, key), ObjectExtensions.ToJson(value));
+
+ public async Task<T?> LoadObjectAsync<T>(string key) => JsonSerializer.Deserialize<T>(await File.ReadAllTextAsync(Path.Join(TargetPath, key)));
+
+ public async Task<bool> ObjectExistsAsync(string key) => File.Exists(Path.Join(TargetPath, key));
+
+ public async Task<List<string>> GetAllKeysAsync() => Directory.GetFiles(TargetPath).Select(Path.GetFileName).ToList();
+
+ public async Task DeleteObjectAsync(string key) => File.Delete(Path.Join(TargetPath, key));
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Desktop/LoginWindow.axaml b/MatrixRoomUtils.Desktop/LoginWindow.axaml
new file mode 100644
index 0000000..d61bfd3
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/LoginWindow.axaml
@@ -0,0 +1,15 @@
+<Window xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:desktop="clr-namespace:MatrixRoomUtils.Desktop"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="MatrixRoomUtils.Desktop.LoginWindow"
+ Title="LoginWindow"
+ x:DataType="desktop:LoginWindow">
+ <StackPanel>
+ <TextBox Text="{Binding Username, Mode=TwoWay}" />
+ <MaskedTextBox Text="{Binding Password, Mode=TwoWay}" />
+ <Button Click="Login">Login</Button>
+ </StackPanel>
+</Window>
diff --git a/MatrixRoomUtils.Desktop/LoginWindow.axaml.cs b/MatrixRoomUtils.Desktop/LoginWindow.axaml.cs
new file mode 100644
index 0000000..1f31b05
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/LoginWindow.axaml.cs
@@ -0,0 +1,36 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace MatrixRoomUtils.Desktop;
+
+public partial class LoginWindow : Window {
+ private readonly MRUStorageWrapper _storage;
+
+ public LoginWindow(MRUStorageWrapper storage) {
+ _storage = storage;
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ }
+
+ private void InitializeComponent() {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public string Username { get; set; }
+ public string Password { get; set; }
+ // ReSharper disable once AsyncVoidMethod
+ private async void Login(object? sender, RoutedEventArgs e) {
+ var res = await _storage.Login(Username.Split(':')[1], Username.Split(':')[0][1..], Password);
+ if (res is not null) {
+ await _storage.AddToken(res);
+ Close();
+ }
+ else {
+ Password = "";
+ }
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/MRUDesktopConfiguration.cs b/MatrixRoomUtils.Desktop/MRUDesktopConfiguration.cs
new file mode 100644
index 0000000..d321591
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/MRUDesktopConfiguration.cs
@@ -0,0 +1,44 @@
+using System.Collections;
+using ArcaneLibs.Extensions;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace MatrixRoomUtils.Desktop;
+
+public class MRUDesktopConfiguration {
+ private static ILogger<MRUDesktopConfiguration> _logger;
+
+ public MRUDesktopConfiguration(ILogger<MRUDesktopConfiguration> logger, IConfiguration config, HostBuilderContext host) {
+ _logger = logger;
+ logger.LogInformation($"Loading configuration for environment: {host.HostingEnvironment.EnvironmentName}...");
+ config.GetSection("MRUDesktop").Bind(this);
+ DataStoragePath = ExpandPath(DataStoragePath);
+ CacheStoragePath = ExpandPath(CacheStoragePath);
+ }
+
+ public string DataStoragePath { get; set; } = "";
+ public string CacheStoragePath { get; set; } = "";
+
+ private static string ExpandPath(string path, bool retry = true) {
+ _logger.LogInformation($"Expanding path `{path}`");
+
+ if (path.StartsWith("~")) {
+ path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), path[1..]);
+ }
+
+ Environment.GetEnvironmentVariables().Cast<DictionaryEntry>().OrderByDescending(x => x.Key.ToString().Length).ToList().ForEach(x => {
+ path = path.Replace($"${x.Key}", x.Value.ToString());
+ });
+
+ _logger.LogInformation($"Expanded path to `{path}`");
+ int tries = 0;
+ while(retry && path.ContainsAnyOf("~$".Split())) {
+ if(tries++ > 100)
+ throw new Exception($"Path `{path}` contains unrecognised environment variables");
+ path = ExpandPath(path, false);
+ }
+
+ return path;
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs b/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs
new file mode 100644
index 0000000..27403dc
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs
@@ -0,0 +1,132 @@
+using MatrixRoomUtils.Core;
+using MatrixRoomUtils.Core.Responses;
+using MatrixRoomUtils.Core.Services;
+
+namespace MatrixRoomUtils.Desktop;
+
+public class MRUStorageWrapper {
+ private readonly TieredStorageService _storageService;
+ private readonly HomeserverProviderService _homeserverProviderService;
+
+ public MRUStorageWrapper(
+ TieredStorageService storageService,
+ HomeserverProviderService homeserverProviderService
+ ) {
+ _storageService = storageService;
+ _homeserverProviderService = homeserverProviderService;
+ }
+
+ public async Task<List<LoginResponse>?> GetAllTokens() {
+ if(!await _storageService.DataStorageProvider.ObjectExistsAsync("mru.tokens")) {
+ return null;
+ }
+ return await _storageService.DataStorageProvider.LoadObjectAsync<List<LoginResponse>>("mru.tokens") ??
+ new List<LoginResponse>();
+ }
+
+ public async Task<LoginResponse?> GetCurrentToken() {
+ if(!await _storageService.DataStorageProvider.ObjectExistsAsync("token")) {
+ return null;
+ }
+ var currentToken = await _storageService.DataStorageProvider.LoadObjectAsync<LoginResponse>("token");
+ var allTokens = await GetAllTokens();
+ if (allTokens is null or { Count: 0 }) {
+ await SetCurrentToken(null);
+ return null;
+ }
+
+ if (currentToken is null) {
+ await SetCurrentToken(currentToken = allTokens[0]);
+ }
+
+ if (!allTokens.Any(x => x.AccessToken == currentToken.AccessToken)) {
+ await SetCurrentToken(currentToken = allTokens[0]);
+ }
+
+ return currentToken;
+ }
+
+ public async Task AddToken(LoginResponse loginResponse) {
+ var tokens = await GetAllTokens();
+ if (tokens == null) {
+ tokens = new List<LoginResponse>();
+ }
+
+ tokens.Add(loginResponse);
+ await _storageService.DataStorageProvider.SaveObjectAsync("mru.tokens", tokens);
+ }
+
+ private async Task<AuthenticatedHomeServer?> GetCurrentSession() {
+ var token = await GetCurrentToken();
+ if (token == null) {
+ return null;
+ }
+
+ return await _homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken);
+ }
+
+ public async Task<AuthenticatedHomeServer?> GetCurrentSessionOrPrompt() {
+ AuthenticatedHomeServer? session = null;
+
+ try {
+ //catch if the token is invalid
+ session = await GetCurrentSession();
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode == "M_UNKNOWN_TOKEN") {
+ var token = await GetCurrentToken();
+ // _navigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken);
+ return null;
+ }
+
+ throw;
+ }
+
+ if (session is null) {
+ // _navigationManager.NavigateTo("/Login");
+ var wnd = new LoginWindow(this);
+ wnd.Show();
+ while (wnd.IsVisible) await Task.Delay(100);
+ session = await GetCurrentSession();
+ }
+
+ return session;
+ }
+
+ public class Settings {
+ public DeveloperSettings DeveloperSettings { get; set; } = new();
+ }
+
+ public class DeveloperSettings {
+ public bool EnableLogViewers { get; set; } = false;
+ public bool EnableConsoleLogging { get; set; } = true;
+ public bool EnablePortableDevtools { get; set; } = false;
+ }
+
+ public async Task RemoveToken(LoginResponse auth) {
+ var tokens = await GetAllTokens();
+ if (tokens == null) {
+ return;
+ }
+
+ tokens.RemoveAll(x => x.AccessToken == auth.AccessToken);
+ await _storageService.DataStorageProvider.SaveObjectAsync("mru.tokens", tokens);
+ }
+
+ public async Task SetCurrentToken(LoginResponse? auth) {
+ _storageService.DataStorageProvider.SaveObjectAsync("token", auth);
+ }
+
+ public async Task<LoginResponse?> Login(string homeserver, string username, string password) {
+ try {
+ return await _homeserverProviderService.Login(homeserver, username, password);
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode == "M_FORBIDDEN") {
+ return null;
+ }
+
+ throw;
+ }
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/MainWindow.axaml b/MatrixRoomUtils.Desktop/MainWindow.axaml
new file mode 100644
index 0000000..bc01bee
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/MainWindow.axaml
@@ -0,0 +1,17 @@
+<Window xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="MatrixRoomUtils.Desktop.MainWindow"
+ Title="Rory&::MatrixRoomUtils">
+ <!-- <Interaction.Behaviors> -->
+ <!-- <EventTriggerBehavior EventName="Loaded"> -->
+ <!-- <InvokeCommandAction Command="{Binding LoadedCommand}"></InvokeCommandAction> -->
+ <!-- </EventTriggerBehavior> -->
+ <!-- </Interaction.Behaviors> -->
+ <StackPanel>
+ <Label FontSize="24">Rooms</Label>
+ <StackPanel x:Name="roomList" Orientation="Vertical"/>
+ </StackPanel>
+</Window>
diff --git a/MatrixRoomUtils.Desktop/MainWindow.axaml.cs b/MatrixRoomUtils.Desktop/MainWindow.axaml.cs
new file mode 100644
index 0000000..41e0888
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/MainWindow.axaml.cs
@@ -0,0 +1,41 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace MatrixRoomUtils.Desktop;
+
+public partial class MainWindow : Window {
+ private readonly ILogger<MainWindow> _logger;
+ private readonly MRUStorageWrapper _storageWrapper;
+ private readonly MRUDesktopConfiguration _configuration;
+
+ public MainWindow(ILogger<MainWindow> logger, IServiceScopeFactory scopeFactory) {
+ _logger = logger;
+ _configuration = scopeFactory.CreateScope().ServiceProvider.GetRequiredService<MRUDesktopConfiguration>();
+ _storageWrapper = scopeFactory.CreateScope().ServiceProvider.GetRequiredService<MRUStorageWrapper>();
+ _logger.LogInformation("Initialising MainWindow");
+
+ InitializeComponent();
+
+ _logger.LogInformation("Cache location: " + _configuration.CacheStoragePath);
+ _logger.LogInformation("Data location: " + _configuration.DataStoragePath);
+
+
+ for (int i = 0; i < 100; i++) {
+ roomList.Children.Add(new RoomListEntry());
+ }
+ }
+
+ // ReSharper disable once AsyncVoidMethod
+ protected override async void OnLoaded(RoutedEventArgs e) {
+ _logger.LogInformation("async onloaded override");
+ var hs = await _storageWrapper.GetCurrentSessionOrPrompt();
+ base.OnLoaded(e);
+ }
+
+ // public Command
+ // protected void LoadedCommand() {
+ // _logger.LogInformation("async command");
+ // }
+}
diff --git a/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj b/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj
new file mode 100644
index 0000000..5b6d3f6
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj
@@ -0,0 +1,49 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>WinExe</OutputType>
+ <TargetFramework>net7.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
+ <ApplicationManifest>app.manifest</ApplicationManifest>
+ <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
+
+ <LangVersion>preview</LangVersion>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <InvariantGlobalization>true</InvariantGlobalization>
+ <PublishTrimmed>true</PublishTrimmed>
+ <PublishReadyToRun>true</PublishReadyToRun>
+ <PublishSingleFile>true</PublishSingleFile>
+ <PublishReadyToRunShowWarnings>true</PublishReadyToRunShowWarnings>
+ <PublishTrimmedShowLinkerSizeComparison>true</PublishTrimmedShowLinkerSizeComparison>
+ <PublishTrimmedShowLinkerSizeComparisonWarnings>true</PublishTrimmedShowLinkerSizeComparisonWarnings>
+ </PropertyGroup>
+
+
+ <ItemGroup>
+ <PackageReference Include="Avalonia" Version="11.0.0" />
+ <PackageReference Include="Avalonia.Desktop" Version="11.0.0" />
+ <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0" />
+ <PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.0" />
+ <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
+ <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0" />
+ </ItemGroup>
+
+
+ <ItemGroup>
+ <ProjectReference Include="..\MatrixRoomUtils.Core\MatrixRoomUtils.Core.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ArcaneLibs" Version="1.0.0-preview3020494760.012ed3f" />
+ <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0.1" />
+ <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="appsettings*.json">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
+ <Content Update="appsettings.Local.json">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
+</Project>
diff --git a/MatrixRoomUtils.Desktop/Program.cs b/MatrixRoomUtils.Desktop/Program.cs
new file mode 100644
index 0000000..74ab579
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/Program.cs
@@ -0,0 +1,31 @@
+using Avalonia;
+using Microsoft.Extensions.Hosting;
+using Tmds.DBus.Protocol;
+
+namespace MatrixRoomUtils.Desktop;
+
+internal class Program {
+ private static IHost appHost;
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static async Task Main(string[] args) {
+ try {
+ BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+ }
+ catch (DBusException e) { }
+ catch (Exception e) {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure<App>()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace();
+}
diff --git a/MatrixRoomUtils.Desktop/Properties/launchSettings.json b/MatrixRoomUtils.Desktop/Properties/launchSettings.json
new file mode 100644
index 0000000..997e294
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/Properties/launchSettings.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "Default": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+
+ }
+ },
+ "Development": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "DOTNET_ENVIRONMENT": "Development"
+ }
+ },
+ "Local config": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "DOTNET_ENVIRONMENT": "Local"
+ }
+ }
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/RoomListEntry.axaml b/MatrixRoomUtils.Desktop/RoomListEntry.axaml
new file mode 100644
index 0000000..c80ef2f
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/RoomListEntry.axaml
@@ -0,0 +1,11 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="32"
+ x:Class="MatrixRoomUtils.Desktop.RoomListEntry">
+ <StackPanel Orientation="Horizontal">
+ <Image MaxWidth="64" x:Name="RoomIcon"></Image>
+ <Label x:Name="RoomName"></Label>
+ </StackPanel>
+</UserControl>
diff --git a/MatrixRoomUtils.Desktop/RoomListEntry.axaml.cs b/MatrixRoomUtils.Desktop/RoomListEntry.axaml.cs
new file mode 100644
index 0000000..490316d
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/RoomListEntry.axaml.cs
@@ -0,0 +1,17 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Media.Imaging;
+
+namespace MatrixRoomUtils.Desktop;
+
+public partial class RoomListEntry : UserControl {
+ public RoomListEntry() {
+ InitializeComponent();
+ }
+
+ protected override void OnLoaded(RoutedEventArgs e) {
+ base.OnLoaded(e);
+ RoomName.Content = "asdf";
+ RoomIcon.Source = new Bitmap("/home/root@Rory/giphy.gif");
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/app.manifest b/MatrixRoomUtils.Desktop/app.manifest
new file mode 100644
index 0000000..35ffb0d
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/app.manifest
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <!-- This manifest is used on Windows only.
+ Don't remove it as it might cause problems with window transparency and embeded controls.
+ For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
+ <assemblyIdentity version="1.0.0.0" name="MatrixRoomUtils.Desktop.Desktop"/>
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- A list of the Windows versions that this application has been tested on
+ and is designed to work with. Uncomment the appropriate elements
+ and Windows will automatically select the most compatible environment. -->
+
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+ </application>
+ </compatibility>
+</assembly>
diff --git a/MatrixRoomUtils.Desktop/appsettings.Development.json b/MatrixRoomUtils.Desktop/appsettings.Development.json
new file mode 100644
index 0000000..20b09a7
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/appsettings.Development.json
@@ -0,0 +1,13 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ },
+ "MRUDesktop": {
+ "DataStoragePath": "mru-desktop/data",
+ "CacheStoragePath": "mru-desktop/cache"
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/appsettings.json b/MatrixRoomUtils.Desktop/appsettings.json
new file mode 100644
index 0000000..4164e87
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/appsettings.json
@@ -0,0 +1,13 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ },
+ "MRUDesktop": {
+ "DataStoragePath": "~/.local/share/mru-desktop",
+ "CacheStoragePath": "~/.cache/mru-desktop"
+ }
+}
diff --git a/MatrixRoomUtils.sln b/MatrixRoomUtils.sln
index a29cf89..ff16a2b 100755
--- a/MatrixRoomUtils.sln
+++ b/MatrixRoomUtils.sln
@@ -10,6 +10,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixRoomUtils.Web.Server"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixRoomUtils.Bot", "MatrixRoomUtils.Bot\MatrixRoomUtils.Bot.csproj", "{B397700A-4ABB-4CAF-8DB8-06E01F44514B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixRoomUtils.DebugDataValidationApi", "MatrixRoomUtils.DebugDataValidationApi\MatrixRoomUtils.DebugDataValidationApi.csproj", "{FB0CF653-FD25-4701-9477-1E80221346DB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixRoomUtils.Desktop", "MatrixRoomUtils.Desktop\MatrixRoomUtils.Desktop.csproj", "{27C08A4F-5AF0-4C2C-AFCB-050E3388C116}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -36,5 +40,13 @@ Global
{B397700A-4ABB-4CAF-8DB8-06E01F44514B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B397700A-4ABB-4CAF-8DB8-06E01F44514B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B397700A-4ABB-4CAF-8DB8-06E01F44514B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FB0CF653-FD25-4701-9477-1E80221346DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FB0CF653-FD25-4701-9477-1E80221346DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FB0CF653-FD25-4701-9477-1E80221346DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FB0CF653-FD25-4701-9477-1E80221346DB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
|