From b6931417b8dc52e65409177a75fd384aec6be12f Mon Sep 17 00:00:00 2001 From: Carl de Billy Date: Thu, 31 Oct 2024 15:34:52 -0400 Subject: [PATCH] chore: Moved the Hot Reload Indicator (`HotreloadStatusView`) to another project. --- .../Diagnostics/DiagnosticViewRegistry.cs | 2 +- .../ClientHotReloadProcessor.Common.Status.cs | 18 +- .../HotReload/HotReloadStatusView.Entries.cs | 215 ----------- .../HotReloadStatusView.Resources.cs | 59 --- .../HotReload/HotReloadStatusView.cs | 350 ------------------ .../HotReload/HotReloadStatusView.xaml | 346 ----------------- .../RemoteControlClient.Status.cs | 4 +- .../RemoteControlStatus.cs | 10 +- .../RemoteControlStatusView.cs | 2 +- src/Uno.UI.RemoteControl/Themes/Generic.xaml | 11 +- .../Uno.UI.RemoteControl.Skia.csproj | 4 - .../Frame/HRApp/Tests/Given_TextBlock.cs | 41 +- .../Diagnostics/DiagnosticView.Factories.cs | 2 +- .../DiagnosticView.TView.TState.cs | 2 +- .../Diagnostics/DiagnosticView.TView.cs | 2 +- src/Uno.UI/Diagnostics/DiagnosticView.cs | 2 +- 16 files changed, 62 insertions(+), 1008 deletions(-) delete mode 100644 src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.Entries.cs delete mode 100644 src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.Resources.cs delete mode 100644 src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.cs delete mode 100644 src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.xaml diff --git a/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs b/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs index ed14cec7ab3d..41a3a91a7b64 100644 --- a/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs +++ b/src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs @@ -37,7 +37,7 @@ public static void Register(IDiagnosticView view, DiagnosticViewRegistrationMode internal record DiagnosticViewRegistration(DiagnosticViewRegistrationMode Mode, IDiagnosticView View); -internal enum DiagnosticViewRegistrationMode +public enum DiagnosticViewRegistrationMode { /// /// Diagnostic is being display on at least one window. diff --git a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Common.Status.cs b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Common.Status.cs index c36874286bee..c30dd6017b6b 100644 --- a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Common.Status.cs +++ b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Common.Status.cs @@ -28,7 +28,7 @@ public partial class ClientHotReloadProcessor /// /// The current status of the hot-reload engine. /// - internal Status CurrentStatus => _status.Current; + public Status CurrentStatus => _status.Current; private readonly StatusSink _status; @@ -39,7 +39,7 @@ public enum HotReloadSource Manual } - internal enum HotReloadClientResult + public enum HotReloadClientResult { /// /// Successful hot-reload. @@ -70,9 +70,9 @@ public record Status( private class StatusSink(ClientHotReloadProcessor owner) { -#if HAS_UNO_WINUI - private readonly DiagnosticView _view = DiagnosticView.Register("Hot reload", ctx => new HotReloadStatusView(ctx), static (view, status) => view.OnHotReloadStatusChanged(status)); -#endif +// #if HAS_UNO_WINUI +// private readonly DiagnosticView _view = DiagnosticView.Register("Hot reload", ctx => new HotReloadStatusView(ctx), static (view, status) => view.OnHotReloadStatusChanged(status)); +// #endif private HotReloadState? _serverState; private bool _isFinalServerState; @@ -142,9 +142,9 @@ private void NotifyStatusChanged() var status = BuildStatus(); Current = status; -#if HAS_UNO_WINUI - _view.Update(status); -#endif +// #if HAS_UNO_WINUI +// _view.Update(status); +// #endif owner.StatusChanged?.Invoke(this, status); } @@ -208,7 +208,7 @@ internal HotReloadClientOperation(HotReloadSource source, Type[] types, Action o public Type[] Types { get; private set; } - internal string[] CuratedTypes => _curatedTypes ??= GetCuratedTypes(); + public string[] CuratedTypes => _curatedTypes ??= GetCuratedTypes(); private string[] GetCuratedTypes() { diff --git a/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.Entries.cs b/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.Entries.cs deleted file mode 100644 index 70ede1d818c5..000000000000 --- a/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.Entries.cs +++ /dev/null @@ -1,215 +0,0 @@ -#nullable enable - -using System; -using System.ComponentModel; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using Uno.UI.RemoteControl.HotReload.Messages; -using static Uno.UI.RemoteControl.HotReload.ClientHotReloadProcessor; -using static Uno.UI.RemoteControl.RemoteControlStatus; - -namespace Uno.UI.RemoteControl.HotReload; - -internal record DevServerEntry() : HotReloadLogEntry(EntrySource.DevServer, -1, DateTimeOffset.Now) -{ - public static DevServerEntry? TryCreateNew(RemoteControlStatus? oldStatus, RemoteControlStatus newStatus) - { - if (oldStatus is not null && oldStatus.State == newStatus.State) - { - return null; - } - - var (iconState, desc) = (oldStatus, newStatus) switch - { - (_, { State: ConnectionState.NoServer }) => (EntryIcon.Error, "No endpoint found"), - (not null, { State: ConnectionState.Connecting }) => (EntryIcon.Loading, "Connecting..."), - (null or { State: not ConnectionState.ConnectionTimeout }, { State: ConnectionState.ConnectionTimeout }) => (EntryIcon.Error, "Timeout"), - (null or { State: not ConnectionState.ConnectionFailed }, { State: ConnectionState.ConnectionFailed }) => (EntryIcon.Error, "Connection error"), - - (null or { IsVersionValid: not false }, { IsVersionValid: false }) => (EntryIcon.Warning, "Version mismatch"), - (null or { InvalidFrames.Count: 0 }, { InvalidFrames.Count: > 0 }) => (EntryIcon.Warning, "Unknown messages"), - (null or { MissingRequiredProcessors.IsEmpty: true }, { MissingRequiredProcessors.IsEmpty: false }) => (EntryIcon.Warning, "Processors missing"), - - ({ KeepAlive.State: KeepAliveState.Idle or KeepAliveState.Ok }, { KeepAlive.State: KeepAliveState.Late }) => (EntryIcon.Error, "Connection lost (>1000ms)"), - ({ KeepAlive.State: KeepAliveState.Idle or KeepAliveState.Ok }, { KeepAlive.State: KeepAliveState.Lost }) => (EntryIcon.Error, "Connection lost (>1s)"), - ({ KeepAlive.State: KeepAliveState.Idle or KeepAliveState.Ok }, { KeepAlive.State: KeepAliveState.Aborted }) => (EntryIcon.Error, "Connection lost (keep-alive)"), - ({ State: ConnectionState.Connected }, { State: ConnectionState.Disconnected }) => (EntryIcon.Error, "Connection lost"), - - ({ State: ConnectionState.Connected }, { State: ConnectionState.Reconnecting }) => (EntryIcon.Error, "Connection lost (reconnecting)"), - - _ => (default, default) - }; - - return desc is null - ? null - : new DevServerEntry { Title = desc, Icon = iconState | EntryIcon.Connection }; - } -} - -internal record EngineEntry() : HotReloadLogEntry(EntrySource.Engine, -1, DateTimeOffset.Now) -{ - public static EngineEntry? TryCreateNew(Status? oldStatus, Status status) - => (oldStatus?.State ?? HotReloadState.Initializing, status.State) switch - { - ( < HotReloadState.Ready, HotReloadState.Ready) => new EngineEntry { Title = "Connected", Icon = EntryIcon.Connection | EntryIcon.Success }, - (not HotReloadState.Disabled, HotReloadState.Disabled) => new EngineEntry { Title = "Cannot initialize with debugger attached", Icon = EntryIcon.Connection | EntryIcon.Error }, - _ => null - }; -} - -internal record ServerEntry : HotReloadLogEntry -{ - public ServerEntry(HotReloadServerOperationData srvOp) - : base(EntrySource.Server, srvOp.Id, srvOp.StartTime) - { - Update(srvOp); - } - - /// - /// Indicates if this notification is the final one for the operation, INCLUDING application wide. - /// - public bool IsFinal { get; private set; } - - public void Update(HotReloadServerOperationData srvOp) - { - IsFinal = srvOp.Result is not HotReloadServerResult.Success; - (IsSuccess, Icon) = srvOp.Result switch - { - null => (default, EntryIcon.HotReload | EntryIcon.Loading), - HotReloadServerResult.Success or HotReloadServerResult.NoChanges => (true, EntryIcon.HotReload | EntryIcon.Success), - _ => (false, EntryIcon.HotReload | EntryIcon.Error) - }; - Title = srvOp.Result switch - { - HotReloadServerResult.NoChanges => "No changes detected", - HotReloadServerResult.RudeEdit => "Rude edit detected, restart required", - HotReloadServerResult.Failed => "Compilation errors", - HotReloadServerResult.Aborted => "Operation cancelled", - HotReloadServerResult.InternalError => "An error occured", - _ => null - }; - Description = Join("file", srvOp.FilePaths.Select(Path.GetFileName).ToArray()!); - Duration = srvOp.EndTime is not null ? srvOp.EndTime - srvOp.StartTime : null; - - RaiseChanged(); - } -} - -internal record ApplicationEntry : HotReloadLogEntry -{ - public ApplicationEntry(HotReloadClientOperation localOp) - : base(EntrySource.Application, localOp.Id, localOp.StartTime) - { - Update(localOp); - } - - internal void Update(HotReloadClientOperation localOp) - { - (IsSuccess, Icon) = localOp.Result switch - { - null => (default(bool?), EntryIcon.HotReload | EntryIcon.Loading), - HotReloadClientResult.Success => (true, EntryIcon.HotReload | EntryIcon.Success), - _ => (false, EntryIcon.HotReload | EntryIcon.Error) - }; - Title = localOp.Result switch - { - null => "Processing...", - HotReloadClientResult.Success => "Update successful", - HotReloadClientResult.Failed => "An error occured", - _ => null - }; - Description = Join("type", localOp.CuratedTypes); - Duration = localOp.EndTime is not null ? localOp.EndTime - localOp.StartTime : null; - - RaiseChanged(); - } -} - -public enum EntrySource -{ - DevServer, - Engine, - Server, - Application -} - -[Flags] -public enum EntryIcon -{ - // Kind - Loading = 1 << 0, - Success = 1 << 1, - Warning = 1 << 2, - Error = 1 << 3, - - // Source - Connection = 1 << 8, - HotReload = 2 << 8, -} - - -[Microsoft.UI.Xaml.Data.Bindable] -public record HotReloadLogEntry(EntrySource Source, long Id, DateTimeOffset Timestamp) : INotifyPropertyChanged -{ - /// - public event PropertyChangedEventHandler? PropertyChanged; - - public bool? IsSuccess { get; set; } - public TimeSpan? Duration { get; set; } - public EntryIcon Icon { get; set; } - - public string? Title { get; set; } - public string? Description { get; set; } - public string? ToastDescription => Description ?? Title; - - public string TimeInfo => Duration switch - { - null => $"{Timestamp:T}", - { TotalMilliseconds: < 1000 } ms => $"{ms.TotalMilliseconds:F0} ms - {Timestamp:T}", - { } s => $"{s.TotalSeconds:N0} s - {Timestamp:T}", - }; - - protected void RaiseChanged() - => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("")); - - protected static string? Join(string kind, string[] items, int? total = null, int max = 5) - { - const int maxLength = 70; - - if (items is { Length: 0 } && total is null) - { - return null; - } - - var sb = new StringBuilder(maxLength + 12 /* and xx more*/); - int count; - for (count = 0; count < Math.Min(items.Length, max); count++) - { - var item = items[count]; - if (sb.Length + 2 /*, */ + item.Length < maxLength) - { - if (count is not 0) sb.Append(", "); - sb.Append(item); - } - else - { - break; - } - } - - var remaining = total - count; - if (remaining > 0) - { - sb.Append((count, remaining) switch - { - (0, 1) => $"1 {kind}", - (0, _) => $"{remaining} {kind}s", - _ => $" and {remaining} more" - }); - } - - return sb.ToString(); - } -} diff --git a/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.Resources.cs b/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.Resources.cs deleted file mode 100644 index 9e43ebd2302a..000000000000 --- a/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.Resources.cs +++ /dev/null @@ -1,59 +0,0 @@ -#nullable enable - -using System; -using System.Linq; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Data; -using Uno.Extensions; - -namespace Uno.UI.RemoteControl.HotReload; - -internal sealed class EntryIconToObjectConverter : IValueConverter -{ - public object? SuccessValue { get; set; } - public object? FailedValue { get; set; } - public object? ConnectionSuccessValue { get; set; } - public object? ConnectionFailedValue { get; set; } - public object? ConnectionWarningValue { get; set; } - - public object? Convert(object? value, Type targetType, object parameter, string language) - { - if (value is not null) - { - var ei = (EntryIcon)value; - - if (ei.HasFlag(EntryIcon.Connection)) - { - if (ei.HasFlag(EntryIcon.Success)) return ConnectionSuccessValue; - if (ei.HasFlag(EntryIcon.Error)) return ConnectionFailedValue; - if (ei.HasFlag(EntryIcon.Warning)) return ConnectionWarningValue; - } - else if (ei.HasFlag(EntryIcon.HotReload)) - { - if (ei.HasFlag(EntryIcon.Success)) return SuccessValue; - if (ei.HasFlag(EntryIcon.Error)) return FailedValue; - } - } - - return ConnectionWarningValue; - } - - public object ConvertBack(object value, Type targetType, object parameter, string language) - => throw new NotSupportedException("Only one-way conversion is supported."); -} - -internal sealed class NullStringToCollapsedConverter : IValueConverter -{ - public object? Convert(object? value, Type targetType, object parameter, string language) - { - if (value is string s && !string.IsNullOrEmpty(s)) - { - return Visibility.Visible; - } - - return Visibility.Collapsed; - } - - public object ConvertBack(object value, Type targetType, object parameter, string language) - => throw new NotSupportedException("Only one-way conversion is supported."); -} diff --git a/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.cs b/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.cs deleted file mode 100644 index 92bfb5f3fc84..000000000000 --- a/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.cs +++ /dev/null @@ -1,350 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Runtime.InteropServices; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Uno.Diagnostics.UI; -using static Uno.UI.RemoteControl.HotReload.ClientHotReloadProcessor; - -namespace Uno.UI.RemoteControl.HotReload; - -[TemplateVisualState(GroupName = "Status", Name = StatusUnknownVisualStateName)] -[TemplateVisualState(GroupName = "Status", Name = StatusInitializingVisualStateName)] -[TemplateVisualState(GroupName = "Status", Name = StatusReadyVisualStateName)] -[TemplateVisualState(GroupName = "Status", Name = StatusWarningVisualStateName)] -[TemplateVisualState(GroupName = "Status", Name = StatusErrorVisualStateName)] -[TemplateVisualState(GroupName = "Result", Name = ResultNoneVisualStateName)] -[TemplateVisualState(GroupName = "Result", Name = ResultSuccessVisualStateName)] -[TemplateVisualState(GroupName = "Result", Name = ResultFailedVisualStateName)] -public sealed partial class HotReloadStatusView : Control -{ - private const string StatusUnknownVisualStateName = "Unknown"; - private const string StatusInitializingVisualStateName = "Initializing"; - private const string StatusReadyVisualStateName = "Ready"; - private const string StatusErrorVisualStateName = "Error"; - private const string StatusWarningVisualStateName = "Warning"; - - private const string ResultNoneVisualStateName = "None"; - private const string ResultSuccessVisualStateName = "Success"; - private const string ResultFailedVisualStateName = "Failed"; - - #region HeadLine (DP) - public static DependencyProperty HeadLineProperty { get; } = DependencyProperty.Register( - nameof(HeadLine), - typeof(string), - typeof(HotReloadStatusView), - new PropertyMetadata(default(string), (snd, args) => ToolTipService.SetToolTip(snd, args.NewValue?.ToString()))); - - public string? HeadLine - { - get => (string?)GetValue(HeadLineProperty); - private set => SetValue(HeadLineProperty, value); - } - #endregion - - #region History (DP) - public static DependencyProperty HistoryProperty { get; } = DependencyProperty.Register( - nameof(History), - typeof(ObservableCollection), - typeof(HotReloadStatusView), - new PropertyMetadata(default(ObservableCollection))); - - public ObservableCollection History - { - get => (ObservableCollection)GetValue(HistoryProperty); - private init => SetValue(HistoryProperty, value); - } - #endregion - - #region ProcessingNotification (DP) - public static readonly DependencyProperty ProcessingNotificationProperty = DependencyProperty.Register( - nameof(ProcessingNotification), - typeof(DiagnosticViewNotification), - typeof(HotReloadStatusView), - new PropertyMetadata(default(DiagnosticViewNotification?))); - - public DiagnosticViewNotification? ProcessingNotification - { - get => (DiagnosticViewNotification?)GetValue(ProcessingNotificationProperty); - set => SetValue(ProcessingNotificationProperty, value); - } - #endregion - - #region SuccessNotification (DP) - public static readonly DependencyProperty SuccessNotificationProperty = DependencyProperty.Register( - nameof(SuccessNotification), - typeof(DiagnosticViewNotification), - typeof(HotReloadStatusView), - new PropertyMetadata(default(DiagnosticViewNotification?))); - - public DiagnosticViewNotification? SuccessNotification - { - get => (DiagnosticViewNotification?)GetValue(SuccessNotificationProperty); - set => SetValue(SuccessNotificationProperty, value); - } - #endregion - - #region FailureNotification (DP) - public static readonly DependencyProperty FailureNotificationProperty = DependencyProperty.Register( - nameof(FailureNotification), - typeof(DiagnosticViewNotification), - typeof(HotReloadStatusView), - new PropertyMetadata(default(DiagnosticViewNotification?))); - - public DiagnosticViewNotification? FailureNotification - { - get => (DiagnosticViewNotification?)GetValue(FailureNotificationProperty); - set => SetValue(FailureNotificationProperty, value); - } - #endregion - - private readonly IDiagnosticViewContext _ctx; - private (string state, HotReloadLogEntry? entry) _result = (ResultNoneVisualStateName, null); - - private Status? _hotReloadStatus; - private RemoteControlStatus? _devServerStatus; - - private readonly Dictionary _serverHrEntries = new(); - private readonly Dictionary _appHrEntries = new(); - private readonly ClientHotReloadProcessor? _processor; // Only when used by external tool like HD. - - public static HotReloadStatusView Create(IDiagnosticViewContext ctx) - { - var processor = RemoteControlClient.Instance?.Processors?.OfType().FirstOrDefault(); - if (processor is null) - { - throw new InvalidOperationException("Cannot resolve the hot-reload client."); - } - - return new HotReloadStatusView(ctx, processor); - } - - internal HotReloadStatusView(IDiagnosticViewContext ctx, ClientHotReloadProcessor? processor = null) - { - _ctx = ctx; - _processor = processor; - - DefaultStyleKey = typeof(HotReloadStatusView); - History = []; - - UpdateVisualStates(false); - - Loaded += static (snd, _) => - { - // Make sure to hide the diagnostics overlay when the view is loaded (in case the template was applied while out of the visual tree). - if (snd is HotReloadStatusView { XamlRoot: { } root } that) - { - DiagnosticsOverlay.Get(root).Hide(RemoteControlStatusView.Id); - if (RemoteControlClient.Instance is { } devServer) - { - devServer.StatusChanged += that.OnDevServerStatusChanged; - that.OnDevServerStatusChanged(null, devServer.Status); - } - - if (that._processor is not null) - { - that._processor.StatusChanged += that.OnHotReloadStatusChanged; - that.OnHotReloadStatusChanged(that._processor.CurrentStatus); - } - } - }; - Unloaded += static (snd, _) => - { - if (snd is HotReloadStatusView that) - { - if (RemoteControlClient.Instance is { } devServer) - { - devServer.StatusChanged -= that.OnDevServerStatusChanged; - } - - if (that._processor is not null) - { - that._processor.StatusChanged -= that.OnHotReloadStatusChanged; - } - } - }; - } - - private void OnDevServerStatusChanged(object? sender, RemoteControlStatus devServerStatus) - { - var oldStatus = _devServerStatus; - _devServerStatus = devServerStatus; - - DispatcherQueue.TryEnqueue(() => - { - UpdateLog(oldStatus, devServerStatus); - - UpdateVisualStates(); - }); - } - - private void OnHotReloadStatusChanged(object? sender, Status status) - => OnHotReloadStatusChanged(status); - - internal void OnHotReloadStatusChanged(Status status) - { - var oldStatus = _hotReloadStatus; - _hotReloadStatus = status; - - UpdateLog(oldStatus, status); - - UpdateVisualStates(); - } - - private void UpdateLog(RemoteControlStatus? oldStatus, RemoteControlStatus newStatus) - { - if (DevServerEntry.TryCreateNew(oldStatus, newStatus) is { } entry) - { - Insert(History, entry); - } - } - - private void UpdateLog(Status? oldStatus, Status status) - { - // Add or update the entries for the **operations** (server and the application). - if (status.Server.Operations is { }) // can be null during loading, creating a NRE - { - foreach (var srvOp in status.Server.Operations) - { - ref var entry = ref CollectionsMarshal.GetValueRefOrAddDefault(_serverHrEntries, srvOp.Id, out var exists); - if (exists) - { - entry!.Update(srvOp); - } - else - { - entry = new ServerEntry(srvOp); - } - } - } - - if (status.Local.Operations is { }) // can be null during loading, creating a NRE - { - foreach (var localOp in status.Local.Operations) - { - ref var entry = ref CollectionsMarshal.GetValueRefOrAddDefault(_appHrEntries, localOp.Id, out var exists); - if (exists) - { - entry!.Update(localOp); - } - else - { - entry = new ApplicationEntry(localOp); - } - } - } - - var log = History; - SyncLog(log, _serverHrEntries.Values); - SyncLog(log, _appHrEntries.Values); - - // Add a log entry for the **status** change. - if (EngineEntry.TryCreateNew(oldStatus, status) is { } engineEntry) - { - Insert(log, engineEntry); - } - } - - public void UpdateVisualStates(bool useTransitions = true) - { - var log = History; - - var connectionEntry = log.FirstOrDefault(e => e.Source is EntrySource.Engine or EntrySource.DevServer); - var operationEntries = log.Where(entry => entry.Source is EntrySource.Server or EntrySource.Application).ToList(); - - // Update the "status"(a.k.a. "connection state") visual state. - if (connectionEntry is null) - { - HeadLine = null; - VisualStateManager.GoToState(this, StatusUnknownVisualStateName, useTransitions); - } - else - { - HeadLine = connectionEntry.Description; - var state = (connectionEntry.Icon & ~(EntryIcon.HotReload | EntryIcon.Connection)) switch - { - EntryIcon.Loading => StatusInitializingVisualStateName, - EntryIcon.Success => StatusReadyVisualStateName, - EntryIcon.Warning when operationEntries.Any(op => op.IsSuccess ?? false) => StatusReadyVisualStateName, - EntryIcon.Warning => StatusWarningVisualStateName, - EntryIcon.Error => StatusErrorVisualStateName, - _ => StatusUnknownVisualStateName - }; - VisualStateManager.GoToState(this, state, useTransitions); - } - - // Then the "result" visual state (en send notifications). - var result = operationEntries switch - { - { Count: 0 } => (ResultNoneVisualStateName, default), - _ when operationEntries.Any(op => op.IsSuccess is null) => (ResultNoneVisualStateName, default), - [ServerEntry { IsFinal: true, IsSuccess: true } e, ..] => (ResultSuccessVisualStateName, e), - [ServerEntry { IsFinal: true, IsSuccess: false } e, ..] => (ResultFailedVisualStateName, e), - [ApplicationEntry { IsSuccess: true } e, ..] => (ResultSuccessVisualStateName, e), - [ApplicationEntry { IsSuccess: false } e, ..] => (ResultFailedVisualStateName, e), - _ => (ResultNoneVisualStateName, default(HotReloadLogEntry)) - }; - if (result != _result) - { - _result = result; - VisualStateManager.GoToState(this, _result.state, useTransitions); - - var notif = _result.state switch - { - ResultNoneVisualStateName when operationEntries is { Count: > 0 } => ProcessingNotification, - ResultSuccessVisualStateName => SuccessNotification, - ResultFailedVisualStateName => FailureNotification, - _ => default - }; - if (notif is not null) - { - if (notif.Content is null or HotReloadLogEntry) - { - notif.Content = operationEntries[0]; - } - - _ctx.Notify(notif); - } - } - } - - #region Misc helpers - private static void SyncLog(ObservableCollection history, ICollection entries) - where TEntry : HotReloadLogEntry - { - foreach (var entry in entries) - { - if (entry.Title is null) - { - history.Remove(entry); - } - else if (!history.Contains(entry)) - { - Insert(history, entry); - } - } - } - - private static void Insert(ObservableCollection history, HotReloadLogEntry entry) - { - history.Insert(FindIndex(entry.Timestamp), entry); - - int FindIndex(DateTimeOffset date) - { - for (var i = 0; i < history.Count; i++) - { - if (history[i].Timestamp > date) - { - return i; - } - } - - return 0; - } - } - #endregion -} diff --git a/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.xaml b/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.xaml deleted file mode 100644 index 5b8e1e6af2a3..000000000000 --- a/src/Uno.UI.RemoteControl/HotReload/HotReloadStatusView.xaml +++ /dev/null @@ -1,346 +0,0 @@ - - - - - #000000 - #F9F9F9 - #EBEBEB - - - #FFFFFF - #282828 - #1C1C1C - - - - #C42B1C - #09B509 - #FD9E0F - #8A8A8A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Uno.UI.RemoteControl/RemoteControlClient.Status.cs b/src/Uno.UI.RemoteControl/RemoteControlClient.Status.cs index 33ffe9cb172c..f7a9dd9543bd 100644 --- a/src/Uno.UI.RemoteControl/RemoteControlClient.Status.cs +++ b/src/Uno.UI.RemoteControl/RemoteControlClient.Status.cs @@ -13,9 +13,9 @@ namespace Uno.UI.RemoteControl; public partial class RemoteControlClient { - internal event EventHandler? StatusChanged; + public event EventHandler? StatusChanged; - internal RemoteControlStatus Status => _status.BuildStatus(); + public RemoteControlStatus Status => _status.BuildStatus(); private class StatusSink : DevServerDiagnostics.ISink { diff --git a/src/Uno.UI.RemoteControl/RemoteControlStatus.cs b/src/Uno.UI.RemoteControl/RemoteControlStatus.cs index 1aa2846f45ba..87de6eb8d095 100644 --- a/src/Uno.UI.RemoteControl/RemoteControlStatus.cs +++ b/src/Uno.UI.RemoteControl/RemoteControlStatus.cs @@ -5,7 +5,7 @@ namespace Uno.UI.RemoteControl; -internal record RemoteControlStatus( +public record RemoteControlStatus( RemoteControlStatus.ConnectionState State, bool? IsVersionValid, (RemoteControlStatus.KeepAliveState State, long RoundTrip) KeepAlive, @@ -82,9 +82,9 @@ internal string GetDescription() return details.ToString(); } - internal record struct MissingProcessor(string TypeFullName, string Version, string Details, string? Error = null); + public readonly record struct MissingProcessor(string TypeFullName, string Version, string Details, string? Error = null); - internal enum KeepAliveState + public enum KeepAliveState { Idle, Ok, // Got ping/pong in expected delays @@ -93,7 +93,7 @@ internal enum KeepAliveState Aborted // KeepAlive was aborted } - internal enum ConnectionState + public enum ConnectionState { /// /// Client as not been started yet @@ -140,7 +140,7 @@ internal enum ConnectionState Disconnected } - internal enum Classification + public enum Classification { Ok, Info, diff --git a/src/Uno.UI.RemoteControl/RemoteControlStatusView.cs b/src/Uno.UI.RemoteControl/RemoteControlStatusView.cs index d8b79d89a947..35e46939435f 100644 --- a/src/Uno.UI.RemoteControl/RemoteControlStatusView.cs +++ b/src/Uno.UI.RemoteControl/RemoteControlStatusView.cs @@ -11,7 +11,7 @@ namespace Uno.UI.RemoteControl; -internal sealed partial class RemoteControlStatusView : Ellipse +public sealed partial class RemoteControlStatusView : Ellipse { #if __ANDROID__ public new const string Id = nameof(RemoteControlStatusView); diff --git a/src/Uno.UI.RemoteControl/Themes/Generic.xaml b/src/Uno.UI.RemoteControl/Themes/Generic.xaml index f56651e9b43a..585844b453c4 100644 --- a/src/Uno.UI.RemoteControl/Themes/Generic.xaml +++ b/src/Uno.UI.RemoteControl/Themes/Generic.xaml @@ -1,10 +1,7 @@ - + - - - - + + + diff --git a/src/Uno.UI.RemoteControl/Uno.UI.RemoteControl.Skia.csproj b/src/Uno.UI.RemoteControl/Uno.UI.RemoteControl.Skia.csproj index 700f5c7d46bb..f2b9a0b0c4d0 100644 --- a/src/Uno.UI.RemoteControl/Uno.UI.RemoteControl.Skia.csproj +++ b/src/Uno.UI.RemoteControl/Uno.UI.RemoteControl.Skia.csproj @@ -68,10 +68,6 @@ - - - - $(MSBuildThisFileDirectory)**\*.xaml diff --git a/src/Uno.UI.RuntimeTests/Tests/HotReload/Frame/HRApp/Tests/Given_TextBlock.cs b/src/Uno.UI.RuntimeTests/Tests/HotReload/Frame/HRApp/Tests/Given_TextBlock.cs index 41784dacaac8..c87b9915f418 100644 --- a/src/Uno.UI.RuntimeTests/Tests/HotReload/Frame/HRApp/Tests/Given_TextBlock.cs +++ b/src/Uno.UI.RuntimeTests/Tests/HotReload/Frame/HRApp/Tests/Given_TextBlock.cs @@ -70,13 +70,15 @@ public async Task When_Changing_TextBlock_UsingHRClient() { var ct = new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token; + var content = new HR_Frame_Pages_Page2(); + UnitTestsUIContentHelper.Content = new ContentControl { - Content = new HR_Frame_Pages_Page2() + Content = content }; var hr = Uno.UI.RemoteControl.RemoteControlClient.Instance?.Processors.OfType().Single(); - var ctx = Uno.UI.RuntimeTests.Tests.HotReload.FrameworkElementExtensions.GetDebugParseContext(new HR_Frame_Pages_Page2()); + var ctx = Uno.UI.RuntimeTests.Tests.HotReload.FrameworkElementExtensions.GetDebugParseContext(content); var req = new Uno.UI.RemoteControl.HotReload.ClientHotReloadProcessor.UpdateRequest( ctx.FileName, SecondPageTextBlockOriginalText, @@ -94,20 +96,49 @@ public async Task When_Changing_TextBlock_UsingHRClient() await hr.UpdateFileAsync(req.Undo(waitForHotReload: false), CancellationToken.None); } } - + + /// + /// Ensure that UpdateFileAsync() completes when no changes are made to the file. + /// + [TestMethod] + public async Task When_Changing_TextBlock_UsingHRClient_NoChanges() + { + var ct = new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token; + + var content = new HR_Frame_Pages_Page2(); + + UnitTestsUIContentHelper.Content = new ContentControl + { + Content = content + }; + + var hr = Uno.UI.RemoteControl.RemoteControlClient.Instance?.Processors.OfType().Single(); + var ctx = Uno.UI.RuntimeTests.Tests.HotReload.FrameworkElementExtensions.GetDebugParseContext(content); + var req = new Uno.UI.RemoteControl.HotReload.ClientHotReloadProcessor.UpdateRequest( + ctx.FileName, + SecondPageTextBlockOriginalText, + SecondPageTextBlockOriginalText + Environment.NewLine, + true) + .WithExtendedTimeouts(); // Required for CI + + await hr.UpdateFileAsync(req, ct); + } + // Another version of the test above, but pausing the TypeMapping before calling the file update [TestMethod] public async Task When_Changing_TextBlock_UsingHRClient_PausingTypeMapping() { var ct = new CancellationTokenSource(TimeSpan.FromSeconds(25)).Token; + var content = new HR_Frame_Pages_Page1(); + UnitTestsUIContentHelper.Content = new ContentControl { - Content = new HR_Frame_Pages_Page1() + Content = content }; var hr = Uno.UI.RemoteControl.RemoteControlClient.Instance?.Processors.OfType().Single(); - var ctx = Uno.UI.RuntimeTests.Tests.HotReload.FrameworkElementExtensions.GetDebugParseContext(new HR_Frame_Pages_Page1()); + var ctx = Uno.UI.RuntimeTests.Tests.HotReload.FrameworkElementExtensions.GetDebugParseContext(content); var req = new Uno.UI.RemoteControl.HotReload.ClientHotReloadProcessor.UpdateRequest( ctx.FileName, FirstPageTextBlockOriginalText, diff --git a/src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs b/src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs index 7e14c271fb0e..d67177d8ce0f 100644 --- a/src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs +++ b/src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs @@ -7,7 +7,7 @@ namespace Uno.Diagnostics.UI; -internal partial class DiagnosticView +partial class DiagnosticView { /// /// Registers a dedicated diagnostic view to be displayed by the diagnostic overlay. diff --git a/src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs b/src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs index 3a87abcd3071..a2bcbf6b0851 100644 --- a/src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs +++ b/src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs @@ -10,7 +10,7 @@ namespace Uno.Diagnostics.UI; /// /// A generic diagnostic view that can be updated with a state. /// -internal class DiagnosticView( +public class DiagnosticView( string id, string name, Func factory, diff --git a/src/Uno.UI/Diagnostics/DiagnosticView.TView.cs b/src/Uno.UI/Diagnostics/DiagnosticView.TView.cs index 5f833c7b6a4c..3f0cf8997cb1 100644 --- a/src/Uno.UI/Diagnostics/DiagnosticView.TView.cs +++ b/src/Uno.UI/Diagnostics/DiagnosticView.TView.cs @@ -10,7 +10,7 @@ namespace Uno.Diagnostics.UI; /// /// A generic diagnostic view. /// -internal class DiagnosticView( +public class DiagnosticView( string id, string name, Func factory, diff --git a/src/Uno.UI/Diagnostics/DiagnosticView.cs b/src/Uno.UI/Diagnostics/DiagnosticView.cs index fd1711cb6234..9849ba1e2294 100644 --- a/src/Uno.UI/Diagnostics/DiagnosticView.cs +++ b/src/Uno.UI/Diagnostics/DiagnosticView.cs @@ -10,7 +10,7 @@ namespace Uno.Diagnostics.UI; /// /// A generic diagnostic view. /// -internal partial class DiagnosticView( +public partial class DiagnosticView( string id, string name, Func factory,