diff --git a/.claude/skills/avalonia-mvvm.md b/.claude/skills/avalonia-mvvm/SKILL.md similarity index 98% rename from .claude/skills/avalonia-mvvm.md rename to .claude/skills/avalonia-mvvm/SKILL.md index c319f63..a0ab4db 100644 --- a/.claude/skills/avalonia-mvvm.md +++ b/.claude/skills/avalonia-mvvm/SKILL.md @@ -1,4 +1,5 @@ --- +name: avalonia-mvvm description: Guide for using MVVM Community Toolkit with Avalonia in RemoteViewer.Client. Use when working with ViewModels, data binding, commands, or Avalonia UI patterns. --- @@ -9,7 +10,7 @@ The RemoteViewer.Client uses **CommunityToolkit.Mvvm v8.4.0** with Avalonia's co ## Core Patterns ### ViewModels -- **Base class**: `ViewModelBase` extends `ObservableObject` from MVVM Toolkit +- **Base class**: `ObservableObject` from CommunityToolkit.Mvvm - **Location**: Co-located with views in `Views/{Feature}/` directories - **Creation**: Use `IViewModelFactory` with dependency injection for instantiation - **Constructor injection**: All dependencies injected via constructor (services, logger, etc.) diff --git a/.claude/skills/design-system/SKILL.md b/.claude/skills/design-system/SKILL.md new file mode 100644 index 0000000..d0622f4 --- /dev/null +++ b/.claude/skills/design-system/SKILL.md @@ -0,0 +1,86 @@ +--- +name: design-system +description: RemoteViewer.Client design system with spacing, colors, typography, and components. Use when building UI, styling components, or working with Avalonia XAML. +--- + +# Design System + +The client uses a comprehensive design system with markup extensions, design tokens, and reusable components. All UI should use these tokens instead of hardcoded values. + +## Markup Extensions + +Located in `Themes/` folder. Add `xmlns:theme="using:RemoteViewer.Client.Themes"` to use. + +### Spacing (Margin/Padding) +```xml +Padding="{theme:Spacing MD}" +Margin="{theme:Spacing X=LG, Y=SM}" +Margin="{theme:Spacing Top=XL, Right=MD}" +``` + +Values: `None=0, XXS=2, XS=4, SM=8, MD=12, LG=16, XL=24, XXL=32` + +The same extension works for `Spacing` (double) and `GridLength` properties: +```xml + + +``` + +## Design Tokens + +### Colors (use DynamicResource for theme support) +- **Muted text**: `SystemControlDisabledBaseMediumLowBrush` (Avalonia built-in) +- **Accent**: `AccentButtonBackground`, `AccentButtonForeground` (Avalonia built-in) +- **Surfaces**: `SurfaceElevatedBrush`, `SurfaceOverlayBrush`, `CardBackgroundBrush` +- **Semantic**: `SuccessBrush`, `ErrorBrush`, `WarningBrush` + +## Typography Classes + +Apply via `Classes="class-name"` on TextBlock: + +- **Headings**: `h1` (22px Bold), `h2` (15px SemiBold), `h3` (13px SemiBold) +- **Small text**: `m1` (12px), `m2` (10px) +- **Special**: `credential` (18px Bold monospace) +- **Color modifier**: `muted` + +## Button Styles + +All buttons automatically get `CornerRadius="6"` from the base style. + +```xml + + diff --git a/src/RemoteViewer.Client/Controls/Dialogs/FileTransferConfirmationDialogViewModel.cs b/src/RemoteViewer.Client/Controls/Dialogs/FileTransferConfirmationDialogViewModel.cs index ffefde4..12b8031 100644 --- a/src/RemoteViewer.Client/Controls/Dialogs/FileTransferConfirmationDialogViewModel.cs +++ b/src/RemoteViewer.Client/Controls/Dialogs/FileTransferConfirmationDialogViewModel.cs @@ -1,8 +1,8 @@ -using RemoteViewer.Client.Views; +using CommunityToolkit.Mvvm.ComponentModel; namespace RemoteViewer.Client.Controls.Dialogs; -public class FileTransferConfirmationDialogViewModel : ViewModelBase +public class FileTransferConfirmationDialogViewModel : ObservableObject { public string SenderDisplayName { get; } public string FileName { get; } diff --git a/src/RemoteViewer.Client/Controls/Dialogs/ViewerSelectionDialog.axaml b/src/RemoteViewer.Client/Controls/Dialogs/ViewerSelectionDialog.axaml index a12f9d6..b99c8be 100644 --- a/src/RemoteViewer.Client/Controls/Dialogs/ViewerSelectionDialog.axaml +++ b/src/RemoteViewer.Client/Controls/Dialogs/ViewerSelectionDialog.axaml @@ -1,8 +1,10 @@ - + MaxWidth="400"> - - - - - - + - - - + + - - + + + + + + Classes="h3" + TextTrimming="CharacterEllipsis" + ToolTip.Tip="{Binding FileName}"/> + Classes="m1"/> - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + - - + diff --git a/src/RemoteViewer.Client/Controls/Dialogs/ViewerSelectionDialogViewModel.cs b/src/RemoteViewer.Client/Controls/Dialogs/ViewerSelectionDialogViewModel.cs index 83d7a40..8d321cd 100644 --- a/src/RemoteViewer.Client/Controls/Dialogs/ViewerSelectionDialogViewModel.cs +++ b/src/RemoteViewer.Client/Controls/Dialogs/ViewerSelectionDialogViewModel.cs @@ -1,9 +1,9 @@ -using RemoteViewer.Client.Views; +using CommunityToolkit.Mvvm.ComponentModel; using RemoteViewer.Client.Views.Presenter; namespace RemoteViewer.Client.Controls.Dialogs; -public class ViewerSelectionDialogViewModel : ViewModelBase +public class ViewerSelectionDialogViewModel : ObservableObject { public IReadOnlyList Viewers { get; } public string FileName { get; } diff --git a/src/RemoteViewer.Client/Controls/DisplayMiniMap.axaml b/src/RemoteViewer.Client/Controls/DisplayMiniMap.axaml index 3b665d9..81ef8dc 100644 --- a/src/RemoteViewer.Client/Controls/DisplayMiniMap.axaml +++ b/src/RemoteViewer.Client/Controls/DisplayMiniMap.axaml @@ -14,7 +14,7 @@ diff --git a/src/RemoteViewer.Client/Controls/DisplayMiniMap.axaml.cs b/src/RemoteViewer.Client/Controls/DisplayMiniMap.axaml.cs index 2ed3422..02c0159 100644 --- a/src/RemoteViewer.Client/Controls/DisplayMiniMap.axaml.cs +++ b/src/RemoteViewer.Client/Controls/DisplayMiniMap.axaml.cs @@ -1,8 +1,7 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; using Avalonia; using Avalonia.Controls; using Material.Icons; -using Material.Icons.Avalonia; using RemoteViewer.Shared; namespace RemoteViewer.Client.Controls; @@ -113,11 +112,10 @@ private void UpdateMap() private static StackPanel CreateButtonContent(DisplayInfo display) { - var icon = new MaterialIcon + var icon = new Icon { Kind = display.IsPrimary ? MaterialIconKind.MonitorStar : MaterialIconKind.Monitor, - Width = 20, - Height = 20 + Size = IconSize.SM }; if (display.IsPrimary) @@ -133,7 +131,7 @@ private static StackPanel CreateButtonContent(DisplayInfo display) { icon, new TextBlock { Text = display.FriendlyName }, - new TextBlock { Text = $"{display.Width} {display.Height}", Classes = { "dimensions" } } + new TextBlock { Text = $"{display.Width} × {display.Height}", Classes = { "dimensions" } } } }; } diff --git a/src/RemoteViewer.Client/Controls/DropOverlay.axaml b/src/RemoteViewer.Client/Controls/DropOverlay.axaml index 571fa0f..af6e67d 100644 --- a/src/RemoteViewer.Client/Controls/DropOverlay.axaml +++ b/src/RemoteViewer.Client/Controls/DropOverlay.axaml @@ -1,46 +1,44 @@ + Padding="{theme:Spacing LG}"> - - + + - + - + - + - + diff --git a/src/RemoteViewer.Client/Controls/Icon.axaml b/src/RemoteViewer.Client/Controls/Icon.axaml new file mode 100644 index 0000000..590931b --- /dev/null +++ b/src/RemoteViewer.Client/Controls/Icon.axaml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RemoteViewer.Client/Controls/Icon.axaml.cs b/src/RemoteViewer.Client/Controls/Icon.axaml.cs new file mode 100644 index 0000000..b574394 --- /dev/null +++ b/src/RemoteViewer.Client/Controls/Icon.axaml.cs @@ -0,0 +1,61 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Material.Icons; + +namespace RemoteViewer.Client.Controls; + +public enum IconSize +{ + XXS = 12, + XS = 16, + SM = 20, + MD = 24, + LG = 32, + XL = 40, + XXL = 48 +} + +public partial class Icon : UserControl +{ + public static readonly StyledProperty KindProperty = + AvaloniaProperty.Register(nameof(Kind), MaterialIconKind.Star); + + public static readonly StyledProperty SizeProperty = + AvaloniaProperty.Register(nameof(Size), IconSize.MD); + + public static readonly StyledProperty ShowAsBadgeProperty = + AvaloniaProperty.Register(nameof(ShowAsBadge), false); + + public static readonly StyledProperty BadgeBackgroundProperty = + AvaloniaProperty.Register(nameof(BadgeBackground)); + + public MaterialIconKind Kind + { + get => this.GetValue(KindProperty); + set => this.SetValue(KindProperty, value); + } + + public IconSize Size + { + get => this.GetValue(SizeProperty); + set => this.SetValue(SizeProperty, value); + } + + public bool ShowAsBadge + { + get => this.GetValue(ShowAsBadgeProperty); + set => this.SetValue(ShowAsBadgeProperty, value); + } + + public IBrush? BadgeBackground + { + get => this.GetValue(BadgeBackgroundProperty); + set => this.SetValue(BadgeBackgroundProperty, value); + } + + public Icon() + { + this.InitializeComponent(); + } +} diff --git a/src/RemoteViewer.Client/Controls/Toasts/ToastsView.axaml b/src/RemoteViewer.Client/Controls/Toasts/ToastsView.axaml index 95d3a60..eb29954 100644 --- a/src/RemoteViewer.Client/Controls/Toasts/ToastsView.axaml +++ b/src/RemoteViewer.Client/Controls/Toasts/ToastsView.axaml @@ -1,17 +1,18 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mik="using:Material.Icons" + xmlns:controls="using:RemoteViewer.Client.Controls" + xmlns:toasts="using:RemoteViewer.Client.Controls.Toasts" + xmlns:ft="using:RemoteViewer.Client.Services.FileTransfer" + xmlns:conv="using:RemoteViewer.Client.Converters" + xmlns:theme="using:RemoteViewer.Client.Themes" + x:Class="RemoteViewer.Client.Controls.Toasts.ToastsView" + x:DataType="toasts:ToastsViewModel"> - + @@ -47,14 +48,13 @@ Width="32" Height="32" CornerRadius="8" - Margin="14,14,12,14" + Margin="{theme:Spacing Left=MD, Right=MD, Y=MD}" VerticalAlignment="Center" Background="{DynamicResource ToastIconBadgeBrush}"> - - + + @@ -64,8 +64,8 @@ - - + + @@ -75,8 +75,8 @@ - - + + @@ -85,19 +85,18 @@ VerticalAlignment="Center" TextWrapping="Wrap" Foreground="{DynamicResource ToastForegroundBrush}" - FontSize="13" FontWeight="SemiBold" - Margin="0,14,8,14"/> + Margin="{theme:Spacing Y=MD, Right=SM}"/> @@ -151,14 +149,13 @@ Width="32" Height="32" CornerRadius="8" - Margin="14,14,12,14" + Margin="{theme:Spacing Left=MD, Right=MD, Y=MD}" VerticalAlignment="Top" Background="{DynamicResource ToastIconBadgeBrush}"> - - + + @@ -168,8 +165,8 @@ - - + + @@ -179,21 +176,20 @@ - - + + - + @@ -233,10 +228,10 @@ Command="{Binding DismissCommand}" Background="Transparent" BorderThickness="0" - Width="28" - Height="28" + Width="32" + Height="32" Padding="0" - Margin="0,10,10,0" + Margin="{theme:Spacing Top=SM, Right=SM}" VerticalAlignment="Top" Cursor="Hand" CornerRadius="6"> @@ -244,15 +239,14 @@ - - - + + + @@ -275,7 +269,7 @@ Background="{StaticResource ToastTransferAccentBrush}"/> - + @@ -284,29 +278,27 @@ Height="32" CornerRadius="8" Background="{DynamicResource ToastIconBadgeBrush}" - Margin="0,0,10,0" + Margin="{theme:Spacing Right=SM}" VerticalAlignment="Center"> - + + FontWeight="SemiBold" /> @@ -333,8 +324,8 @@ Text="{Binding Transfer.FileName}" TextTrimming="CharacterEllipsis" Foreground="{DynamicResource ToastSecondaryTextBrush}" - FontSize="12" - Margin="0,8,0,10" + Classes="m1" + Margin="{theme:Spacing Top=SM, Bottom=SM}" ToolTip.Tip="{Binding Transfer.FileName}"/> @@ -361,9 +352,9 @@ + Margin="{theme:Spacing Top=SM}"> @@ -378,7 +369,7 @@ + Margin="{theme:Spacing Top=SM}"> @@ -391,12 +382,12 @@ + Classes="m1"/> diff --git a/src/RemoteViewer.Client/Themes/ButtonStyles.axaml b/src/RemoteViewer.Client/Themes/ButtonStyles.axaml new file mode 100644 index 0000000..301eb0c --- /dev/null +++ b/src/RemoteViewer.Client/Themes/ButtonStyles.axaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/src/RemoteViewer.Client/Themes/Colors.axaml b/src/RemoteViewer.Client/Themes/Colors.axaml new file mode 100644 index 0000000..9855195 --- /dev/null +++ b/src/RemoteViewer.Client/Themes/Colors.axaml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RemoteViewer.Client/Themes/SpacingExtension.cs b/src/RemoteViewer.Client/Themes/SpacingExtension.cs new file mode 100644 index 0000000..0c7a935 --- /dev/null +++ b/src/RemoteViewer.Client/Themes/SpacingExtension.cs @@ -0,0 +1,63 @@ +using System.Reflection; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace RemoteViewer.Client.Themes; + +public enum SpacingSize +{ + None = 0, + XXS = 2, + XS = 4, + SM = 8, + MD = 12, + LG = 16, + XL = 24, + XXL = 32 +} + +public class SpacingExtension : MarkupExtension +{ + public SpacingSize All { get; set; } = SpacingSize.None; + public SpacingSize? X { get; set; } + public SpacingSize? Y { get; set; } + public SpacingSize? Top { get; set; } + public SpacingSize? Bottom { get; set; } + public SpacingSize? Left { get; set; } + public SpacingSize? Right { get; set; } + + public SpacingExtension() { } + + public SpacingExtension(SpacingSize all) => this.All = all; + + public override object ProvideValue(IServiceProvider serviceProvider) + { + var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; + var targetProperty = target?.TargetProperty; + + Type? targetType = null; + if (targetProperty is PropertyInfo pi) + targetType = pi.PropertyType; + else if (targetProperty is AvaloniaProperty ap) + targetType = ap.PropertyType; + + if (targetType == typeof(double)) + return (double)this.All; + + if (targetType == typeof(GridLength)) + return new GridLength((double)this.All, GridUnitType.Pixel); + + if (targetType == typeof(Thickness)) + { + var top = (double)(this.Top ?? this.Y ?? this.All); + var bottom = (double)(this.Bottom ?? this.Y ?? this.All); + var left = (double)(this.Left ?? this.X ?? this.All); + var right = (double)(this.Right ?? this.X ?? this.All); + + return new Thickness(left, top, right, bottom); + } + + throw new InvalidOperationException($"Cannot convert SpacingExtension to target type '{targetType}'"); + } +} diff --git a/src/RemoteViewer.Client/Themes/Typography.axaml b/src/RemoteViewer.Client/Themes/Typography.axaml new file mode 100644 index 0000000..7d7bfc6 --- /dev/null +++ b/src/RemoteViewer.Client/Themes/Typography.axaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RemoteViewer.Client/ViewLocator.cs b/src/RemoteViewer.Client/ViewLocator.cs index 78c10f7..a399395 100644 --- a/src/RemoteViewer.Client/ViewLocator.cs +++ b/src/RemoteViewer.Client/ViewLocator.cs @@ -1,7 +1,7 @@ using System; using Avalonia.Controls; using Avalonia.Controls.Templates; -using RemoteViewer.Client.Views; +using CommunityToolkit.Mvvm.ComponentModel; namespace RemoteViewer.Client; @@ -26,6 +26,6 @@ public class ViewLocator : IDataTemplate public bool Match(object? data) { - return data is ViewModelBase; + return data is ObservableObject; } } diff --git a/src/RemoteViewer.Client/Views/About/AboutView.axaml b/src/RemoteViewer.Client/Views/About/AboutView.axaml index 0182f76..1357374 100644 --- a/src/RemoteViewer.Client/Views/About/AboutView.axaml +++ b/src/RemoteViewer.Client/Views/About/AboutView.axaml @@ -1,103 +1,137 @@ - - - - + + - + + + + - + + - - + - - - - + + + - - - - - - - + + + + + + + - + + + + + + + - - - - - - - - - - - - + + + - - + + diff --git a/src/RemoteViewer.Client/Views/About/AboutViewModel.cs b/src/RemoteViewer.Client/Views/About/AboutViewModel.cs index 8e8a7f6..0778ae6 100644 --- a/src/RemoteViewer.Client/Views/About/AboutViewModel.cs +++ b/src/RemoteViewer.Client/Views/About/AboutViewModel.cs @@ -1,8 +1,9 @@ -using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; namespace RemoteViewer.Client.Views.About; -public partial class AboutViewModel : ViewModelBase +public partial class AboutViewModel : ObservableObject { public string ApplicationName => "Remote Viewer"; diff --git a/src/RemoteViewer.Client/Views/Chat/ChatView.axaml b/src/RemoteViewer.Client/Views/Chat/ChatView.axaml index 405ad87..5d6e7fa 100644 --- a/src/RemoteViewer.Client/Views/Chat/ChatView.axaml +++ b/src/RemoteViewer.Client/Views/Chat/ChatView.axaml @@ -1,8 +1,10 @@ - + - + TextWrapping="Wrap"/> + Margin="{theme:Spacing Top=XS}"> diff --git a/src/RemoteViewer.Client/Views/Main/ConnectionStatus.cs b/src/RemoteViewer.Client/Views/Main/ConnectionStatus.cs new file mode 100644 index 0000000..27e8219 --- /dev/null +++ b/src/RemoteViewer.Client/Views/Main/ConnectionStatus.cs @@ -0,0 +1,8 @@ +namespace RemoteViewer.Client.Views.Main; + +public enum ConnectionStatus +{ + Connecting, + Connected, + VersionMismatch +} diff --git a/src/RemoteViewer.Client/Views/Main/MainView.axaml b/src/RemoteViewer.Client/Views/Main/MainView.axaml index af6ed40..df6588b 100644 --- a/src/RemoteViewer.Client/Views/Main/MainView.axaml +++ b/src/RemoteViewer.Client/Views/Main/MainView.axaml @@ -1,10 +1,12 @@ - + + - - - + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + diff --git a/src/RemoteViewer.Client/Views/Main/MainViewModel.cs b/src/RemoteViewer.Client/Views/Main/MainViewModel.cs index f4a4f86..3c5208d 100644 --- a/src/RemoteViewer.Client/Views/Main/MainViewModel.cs +++ b/src/RemoteViewer.Client/Views/Main/MainViewModel.cs @@ -1,4 +1,4 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Logging; using RemoteViewer.Client.Controls.Toasts; @@ -10,7 +10,7 @@ namespace RemoteViewer.Client.Views.Main; -public partial class MainViewModel : ViewModelBase +public partial class MainViewModel : ObservableObject { private readonly ConnectionHubClient _hubClient; private readonly IDispatcher _dispatcher; @@ -34,17 +34,27 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private string? _targetPassword; - [ObservableProperty] - private bool _isConnected; - - [ObservableProperty] - private string _statusText = "Connecting..."; + public ConnectionStatus Status => this._hubClient switch + { + { HasVersionMismatch: true } => ConnectionStatus.VersionMismatch, + { IsConnected: true } => ConnectionStatus.Connected, + _ => ConnectionStatus.Connecting + }; - [ObservableProperty] - private bool _hasVersionMismatch; + public string? VersionTooltipText + { + get + { + if (this._hubClient.HasVersionMismatch is false) + return null; - [ObservableProperty] - private string _versionMismatchText = string.Empty; + return $""" + Version Mismatch + Server: v{this._hubClient.ServerVersion} + Client: v{ThisAssembly.AssemblyInformationalVersion} + """; + } + } public event EventHandler? RequestHideMainView; public event EventHandler? RequestShowMainView; @@ -62,21 +72,10 @@ public MainViewModel(ConnectionHubClient hubClient, IDispatcher dispatcher, IVie { this._dispatcher.Post(() => { - this.IsConnected = this._hubClient.IsConnected; - this.HasVersionMismatch = this._hubClient.HasVersionMismatch; - - if (this._hubClient.HasVersionMismatch) - { - this.VersionMismatchText = $""" - Version mismatch! - Server v{this._hubClient.ServerVersion} - Client v{ThisAssembly.AssemblyInformationalVersion} - """; - } - - this.StatusText = this._hubClient.IsConnected ? "Connected" : "Connecting..."; + this.OnPropertyChanged(nameof(this.Status)); + this.OnPropertyChanged(nameof(this.VersionTooltipText)); - this._logger.HubConnectionStatusChanged(this._hubClient.IsConnected, this.StatusText); + this._logger.HubConnectionStatusChanged(this._hubClient.IsConnected, this.Status.ToString()); this.YourUsername = this._hubClient.IsConnected ? this._hubClient.Username : "..."; this.YourPassword = this._hubClient.IsConnected ? this._hubClient.Password : "..."; @@ -93,58 +92,22 @@ Client v{ThisAssembly.AssemblyInformationalVersion} }); }; - this.IsConnected = this._hubClient.IsConnected; - - // Handle viewer connections - open viewer window when connected as viewer - this._hubClient.ConnectionStarted += this.OnConnectionStarted; - } - - private void OnConnectionStarted(object? sender, ConnectionStartedEventArgs e) - { - this._dispatcher.Post(() => + this._hubClient.ConnectionStarted += (_, e) => { - if (e.Connection.IsPresenter) - { - this._logger.ConnectionSuccessful("Presenter"); - this.OpenPresenterWindow(e.Connection); - } - else + this._dispatcher.Post(() => { - this._logger.ConnectionSuccessful("Viewer"); - this.OpenViewerWindow(e.Connection); - } - }); - } - - private void OpenPresenterWindow(Connection connection) - { - this.RequestHideMainView?.Invoke(this, EventArgs.Empty); - - var viewModel = this._viewModelFactory.CreatePresenterViewModel(connection); - this._sessionWindowHandle = this._dialogService.ShowPresenterWindow(viewModel); - this._sessionWindowHandle.Closed += this.OnSessionWindowClosed; - } - - private void OpenViewerWindow(Connection connection) - { - this.RequestHideMainView?.Invoke(this, EventArgs.Empty); - - var viewModel = this._viewModelFactory.CreateViewerViewModel(connection); - this._sessionWindowHandle = this._dialogService.ShowViewerWindow(viewModel); - this._sessionWindowHandle.Closed += this.OnSessionWindowClosed; - } - - private void OnSessionWindowClosed(object? sender, EventArgs e) - { - this._logger.SessionWindowClosed(); - - if (this._sessionWindowHandle is not null) - { - this._sessionWindowHandle.Closed -= this.OnSessionWindowClosed; - this._sessionWindowHandle = null; - } - - this.RequestShowMainView?.Invoke(this, EventArgs.Empty); + if (e.Connection.IsPresenter) + { + this._logger.ConnectionSuccessful("Presenter"); + this.OpenPresenterWindow(e.Connection); + } + else + { + this._logger.ConnectionSuccessful("Viewer"); + this.OpenViewerWindow(e.Connection); + } + }); + }; } [RelayCommand] @@ -206,4 +169,31 @@ private async Task ShowAboutAsync() { await this._dialogService.ShowAboutDialogAsync(); } + + private void OpenPresenterWindow(Connection connection) + { + this.RequestHideMainView?.Invoke(this, EventArgs.Empty); + + var viewModel = this._viewModelFactory.CreatePresenterViewModel(connection); + this._sessionWindowHandle = this._dialogService.ShowPresenterWindow(viewModel); + this._sessionWindowHandle.Closed += this.OnSessionWindowClosed; + } + + private void OpenViewerWindow(Connection connection) + { + this.RequestHideMainView?.Invoke(this, EventArgs.Empty); + + var viewModel = this._viewModelFactory.CreateViewerViewModel(connection); + this._sessionWindowHandle = this._dialogService.ShowViewerWindow(viewModel); + this._sessionWindowHandle.Closed += this.OnSessionWindowClosed; + } + + private void OnSessionWindowClosed(object? sender, EventArgs e) + { + this._logger.SessionWindowClosed(); + this._sessionWindowHandle?.Closed -= this.OnSessionWindowClosed; + this._sessionWindowHandle = null; + + this.RequestShowMainView?.Invoke(this, EventArgs.Empty); + } } diff --git a/src/RemoteViewer.Client/Views/Presenter/PresenterView.axaml b/src/RemoteViewer.Client/Views/Presenter/PresenterView.axaml index b0c692b..b62061c 100644 --- a/src/RemoteViewer.Client/Views/Presenter/PresenterView.axaml +++ b/src/RemoteViewer.Client/Views/Presenter/PresenterView.axaml @@ -5,8 +5,8 @@ xmlns:toastControls="using:RemoteViewer.Client.Controls.Toasts" xmlns:controls="using:RemoteViewer.Client.Controls" xmlns:chatControls="using:RemoteViewer.Client.Views.Chat" - xmlns:mi="using:Material.Icons.Avalonia" xmlns:mik="using:Material.Icons" + xmlns:theme="using:RemoteViewer.Client.Themes" x:Class="RemoteViewer.Client.Views.Presenter.PresenterView" x:DataType="vm:PresenterViewModel" @@ -15,7 +15,7 @@ Width="300" CanResize="False" SizeToContent="Height" - WindowStartupLocation="CenterScreen" + WindowStartupLocation="CenterOwner" DragDrop.AllowDrop="True" DataContextChanged="Window_DataContextChanged" @@ -24,31 +24,30 @@ - + Background="{DynamicResource SurfaceElevatedBrush}" + Padding="{theme:Spacing X=SM, Y=SM}"> + - + - + + Classes="h3" + Margin="{theme:Spacing Bottom=MD}"/> - + + Classes="m1 muted"/> + Classes="credential" + Margin="{theme:Spacing Y=SM}"/> - + - + Classes="m1 muted"/> + - + - + Classes="h3" + Margin="{theme:Spacing Bottom=MD}"/> - - + + @@ -239,14 +221,9 @@ @@ -254,15 +231,15 @@ - - + + + VerticalAlignment="Bottom" + Margin="{theme:Spacing Right=SM, Bottom=SM}"/> @@ -60,31 +61,28 @@ - + + Margin="{theme:Spacing Y=XS}" + Background="{DynamicResource SystemControlForegroundBaseLowBrush}"/> @@ -244,10 +231,10 @@ + Margin="{theme:Spacing X=LG}"> - + @@ -261,11 +248,13 @@ Opacity="0.6" ToolTip.Tip="{Binding FriendlyName, StringFormat='Navigate to {0} (Ctrl+Left)'}"> - - - + + + @@ -275,10 +264,10 @@ + Margin="{theme:Spacing X=LG}"> - + @@ -292,11 +281,13 @@ Opacity="0.6" ToolTip.Tip="{Binding FriendlyName, StringFormat='Navigate to {0} (Ctrl+Right)'}"> - - - + + + @@ -306,10 +297,10 @@ + Margin="{theme:Spacing Y=LG}"> - + @@ -323,11 +314,13 @@ Opacity="0.6" ToolTip.Tip="{Binding FriendlyName, StringFormat='Navigate to {0} (Ctrl+Up)'}"> - - - + + + @@ -337,10 +330,10 @@ + Margin="{theme:Spacing Y=LG}"> - + @@ -354,11 +347,13 @@ Opacity="0.6" ToolTip.Tip="{Binding FriendlyName, StringFormat='Navigate to {0} (Ctrl+Down)'}"> - - - + + + @@ -369,9 +364,9 @@ + VerticalAlignment="Bottom" + Margin="{theme:Spacing Right=SM, Bottom=SM}"/>