From 1dc9282a9fd078fc0423921bcc5325007d1494ca Mon Sep 17 00:00:00 2001 From: Adolfo Marinucci Date: Wed, 11 Dec 2024 00:43:52 +0100 Subject: [PATCH] Added MauiReactor features, working on Balance template --- samples/Calculator/MauiProgram.cs | 2 +- samples/ChartApp/MauiProgram.cs | 2 +- samples/Contentics/MauiProgram.cs | 2 +- samples/IntegrationTest/MauiProgram.cs | 2 +- samples/MauiReactor.Calendar/MauiProgram.cs | 2 +- samples/MauiReactor.MapsDemo/MauiProgram.cs | 2 +- samples/MauiReactor.TestApp/MauiProgram.cs | 22 +- .../MauiReactor.TestApp.csproj | 4 + .../MauiReactor.TestAppWindow/MauiProgram.cs | 2 +- .../MauiProgram.cs | 2 +- samples/SlidingPuzzle/MauiProgram.cs | 2 +- src/MauiReactor.Blazor.TestApp/MauiProgram.cs | 2 +- src/MauiReactor.FigmaPlugin/MauiProgram.cs | 2 +- src/MauiReactor/Grid.partial.cs | 26 +- .../HotReload/HotReloadTypeLoader.cs | 117 +++++++ src/MauiReactor/HotReload/ITypeLoader.cs | 39 --- src/MauiReactor/HotReload/LocalTypeLoader.cs | 40 +-- src/MauiReactor/HotReload/RemoteTypeLoader.cs | 171 ++++++----- src/MauiReactor/Integration/ComponentHost.cs | 20 +- .../Internals/ComponentServicesAttribute.cs | 9 - .../Internals/MauiReactorFeatures.cs | 20 ++ .../Internals/ServiceCollectionProvider.cs | 23 +- src/MauiReactor/LinearItemsLayout.partial.cs | 7 + src/MauiReactor/PageHost.cs | 27 +- src/MauiReactor/ReactorApplication.cs | 82 +++-- .../Components/Main/ProjectCardView.cs | 2 +- .../Components/Main/TaskView.cs | 5 + .../Components/Projects/ProjectDetailPage.cs | 288 ++++++++++++++++++ .../Components/Projects/ProjectListPage.cs | 46 ++- .../MauiProgram.cs | 5 - ...uiReactorTemplate.StartupSampleXaml.csproj | 4 + .../Models/Tag.cs | 74 ++--- 32 files changed, 763 insertions(+), 290 deletions(-) create mode 100644 src/MauiReactor/HotReload/HotReloadTypeLoader.cs delete mode 100644 src/MauiReactor/HotReload/ITypeLoader.cs delete mode 100644 src/MauiReactor/Internals/ComponentServicesAttribute.cs create mode 100644 src/MauiReactor/Internals/MauiReactorFeatures.cs create mode 100644 templates/MauiReactorTemplate.StartupSampleXaml/Components/Projects/ProjectDetailPage.cs diff --git a/samples/Calculator/MauiProgram.cs b/samples/Calculator/MauiProgram.cs index c5f629d6..8d6d1a0d 100644 --- a/samples/Calculator/MauiProgram.cs +++ b/samples/Calculator/MauiProgram.cs @@ -16,7 +16,7 @@ public static MauiApp CreateMauiApp() app.UseTheme(); }) #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { diff --git a/samples/ChartApp/MauiProgram.cs b/samples/ChartApp/MauiProgram.cs index ecc93d66..0a76d64e 100644 --- a/samples/ChartApp/MauiProgram.cs +++ b/samples/ChartApp/MauiProgram.cs @@ -33,7 +33,7 @@ public static MauiApp CreateMauiApp() //.AddLightTheme() ); }) - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); diff --git a/samples/Contentics/MauiProgram.cs b/samples/Contentics/MauiProgram.cs index cf5c1cff..769f1871 100644 --- a/samples/Contentics/MauiProgram.cs +++ b/samples/Contentics/MauiProgram.cs @@ -12,7 +12,7 @@ public static MauiApp CreateMauiApp() builder .UseMauiReactorApp
() #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { diff --git a/samples/IntegrationTest/MauiProgram.cs b/samples/IntegrationTest/MauiProgram.cs index dbd58c93..c77763e6 100644 --- a/samples/IntegrationTest/MauiProgram.cs +++ b/samples/IntegrationTest/MauiProgram.cs @@ -18,7 +18,7 @@ public static MauiApp CreateMauiApp() #if DEBUG - builder.EnableMauiReactorHotReload(); + //builder.EnableMauiReactorHotReload(); builder.Logging.AddDebug(); #endif diff --git a/samples/MauiReactor.Calendar/MauiProgram.cs b/samples/MauiReactor.Calendar/MauiProgram.cs index fbaf1594..ee0ea18b 100644 --- a/samples/MauiReactor.Calendar/MauiProgram.cs +++ b/samples/MauiReactor.Calendar/MauiProgram.cs @@ -18,7 +18,7 @@ public static MauiApp CreateMauiApp() app.SetWindowsSpecificAssetsDirectory("Assets"); }) #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { diff --git a/samples/MauiReactor.MapsDemo/MauiProgram.cs b/samples/MauiReactor.MapsDemo/MauiProgram.cs index 589989bc..6239beb8 100644 --- a/samples/MauiReactor.MapsDemo/MauiProgram.cs +++ b/samples/MauiReactor.MapsDemo/MauiProgram.cs @@ -12,7 +12,7 @@ public static MauiApp CreateMauiApp() builder .UseMauiReactorApp() #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { diff --git a/samples/MauiReactor.TestApp/MauiProgram.cs b/samples/MauiReactor.TestApp/MauiProgram.cs index 33e896d6..cb3ef83d 100644 --- a/samples/MauiReactor.TestApp/MauiProgram.cs +++ b/samples/MauiReactor.TestApp/MauiProgram.cs @@ -12,27 +12,7 @@ public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder - .UseMauiApp() - //.UseMauiReactorApp(app => - //{ - // //we can mix styles from xaml dictionary with... - // app.AddResource("Resources/Styles/DefaultTheme.xaml"); - - // app.SetWindowsSpecificAssetsDirectory("Assets"); - - // //... the MauiReactor theming, but often it's easier to just manage styles in either XAML or c# - // app.UseTheme(); - //}) -#if DEBUG - .EnableMauiReactorHotReload() - .OnMauiReactorUnhandledException((e) => - { - System.Diagnostics.Debug.WriteLine(e.ExceptionObject); - }) - //This will enable the FrameRateIndicator widget - //Disable before publishing the app - .EnableFrameRateIndicator() -#endif + .UseMauiApp() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); diff --git a/samples/MauiReactor.TestApp/MauiReactor.TestApp.csproj b/samples/MauiReactor.TestApp/MauiReactor.TestApp.csproj index d1410713..94163757 100644 --- a/samples/MauiReactor.TestApp/MauiReactor.TestApp.csproj +++ b/samples/MauiReactor.TestApp/MauiReactor.TestApp.csproj @@ -49,6 +49,10 @@ + + + + diff --git a/samples/MauiReactor.TestAppWindow/MauiProgram.cs b/samples/MauiReactor.TestAppWindow/MauiProgram.cs index c10453c3..8e6bd7d2 100644 --- a/samples/MauiReactor.TestAppWindow/MauiProgram.cs +++ b/samples/MauiReactor.TestAppWindow/MauiProgram.cs @@ -18,7 +18,7 @@ public static MauiApp CreateMauiApp() app.SetWindowsSpecificAssetsDirectory("Assets"); }) #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { diff --git a/samples/MauiReactor.WeatherTwentyOne/MauiProgram.cs b/samples/MauiReactor.WeatherTwentyOne/MauiProgram.cs index 7efbb501..ba0bd2e5 100644 --- a/samples/MauiReactor.WeatherTwentyOne/MauiProgram.cs +++ b/samples/MauiReactor.WeatherTwentyOne/MauiProgram.cs @@ -17,7 +17,7 @@ public static MauiApp CreateMauiApp() app.SetWindowsSpecificAssetsDirectory("Assets"); }) #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { fonts.AddFont("fa-solid-900.ttf", "FontAwesome"); diff --git a/samples/SlidingPuzzle/MauiProgram.cs b/samples/SlidingPuzzle/MauiProgram.cs index fe83b1ab..59df894f 100644 --- a/samples/SlidingPuzzle/MauiProgram.cs +++ b/samples/SlidingPuzzle/MauiProgram.cs @@ -15,7 +15,7 @@ public static MauiApp CreateMauiApp() app.SetWindowsSpecificAssetsDirectory("Assets"); }) #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { diff --git a/src/MauiReactor.Blazor.TestApp/MauiProgram.cs b/src/MauiReactor.Blazor.TestApp/MauiProgram.cs index a093d415..c6f0ac87 100644 --- a/src/MauiReactor.Blazor.TestApp/MauiProgram.cs +++ b/src/MauiReactor.Blazor.TestApp/MauiProgram.cs @@ -11,7 +11,7 @@ public static MauiApp CreateMauiApp() builder .UseMauiReactorApp() #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { diff --git a/src/MauiReactor.FigmaPlugin/MauiProgram.cs b/src/MauiReactor.FigmaPlugin/MauiProgram.cs index b9e58edd..70c23389 100644 --- a/src/MauiReactor.FigmaPlugin/MauiProgram.cs +++ b/src/MauiReactor.FigmaPlugin/MauiProgram.cs @@ -11,7 +11,7 @@ public static MauiApp CreateMauiApp() builder .UseMauiReactorApp() #if DEBUG - .EnableMauiReactorHotReload() + //.EnableMauiReactorHotReload() #endif .ConfigureFonts(fonts => { diff --git a/src/MauiReactor/Grid.partial.cs b/src/MauiReactor/Grid.partial.cs index b3ce2aa2..c22d7f7b 100644 --- a/src/MauiReactor/Grid.partial.cs +++ b/src/MauiReactor/Grid.partial.cs @@ -32,13 +32,17 @@ public Grid(IEnumerable rows, IEnumerable colum string? IGrid.ColumnDefinitions { get; set; } string? IGrid.RowDefinitions { get; set; } - private static readonly BindablePropertyKey _mauiReactorGridRowsKey = BindableProperty.CreateAttachedReadOnly( - nameof(_mauiReactorGridRowsKey), - typeof(HashSet), typeof(Grid), null); - - private static readonly BindablePropertyKey _mauiReactorGridColumnsKey = BindableProperty.CreateAttachedReadOnly( - nameof(_mauiReactorGridColumnsKey), - typeof(HashSet), typeof(Grid), null); + private static readonly BindableProperty _mauiReactorGridRows = BindableProperty.CreateAttached( + nameof(_mauiReactorGridRows), + typeof(string), + typeof(Grid), + null); + + private static readonly BindableProperty _mauiReactorGridColumns = BindableProperty.CreateAttached( + nameof(_mauiReactorGridColumns), + typeof(string), + typeof(Grid), + null); protected override void OnUpdate() { @@ -54,18 +58,18 @@ protected override void OnUpdate() // grid.RowDefinitions.Add(rowDefinition); //} - var rowsOnNativeControl = (string?)NativeControl.GetValue(_mauiReactorGridRowsKey.BindableProperty); + var rowsOnNativeControl = (string?)NativeControl.GetValue(_mauiReactorGridRows); if (rowsOnNativeControl != thisAsIGrid.RowDefinitions) { GridExtensions.SetRowDefinitions(NativeControl, thisAsIGrid.RowDefinitions); - NativeControl.SetValue(_mauiReactorGridColumnsKey.BindableProperty, thisAsIGrid.RowDefinitions); + NativeControl.SetValue(_mauiReactorGridColumns, thisAsIGrid.RowDefinitions); } - var columnsOnNativeControl = (string?)NativeControl.GetValue(_mauiReactorGridColumnsKey.BindableProperty); + var columnsOnNativeControl = (string?)NativeControl.GetValue(_mauiReactorGridColumns); if (columnsOnNativeControl != thisAsIGrid.ColumnDefinitions) { GridExtensions.SetColumnDefinitions(NativeControl, thisAsIGrid.ColumnDefinitions); - NativeControl.SetValue(_mauiReactorGridColumnsKey.BindableProperty, thisAsIGrid.ColumnDefinitions); + NativeControl.SetValue(_mauiReactorGridColumns, thisAsIGrid.ColumnDefinitions); } base.OnUpdate(); diff --git a/src/MauiReactor/HotReload/HotReloadTypeLoader.cs b/src/MauiReactor/HotReload/HotReloadTypeLoader.cs new file mode 100644 index 00000000..c7d7ea74 --- /dev/null +++ b/src/MauiReactor/HotReload/HotReloadTypeLoader.cs @@ -0,0 +1,117 @@ +using MauiReactor.Internals; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Metadata; +using System.Text; + +[assembly: MetadataUpdateHandler(typeof(MauiReactor.HotReload.HotReloadTypeLoader))] + +namespace MauiReactor.HotReload; + +internal interface ITypeLoaderEventConsumer +{ + void OnAssemblyChanged(); +} + +internal class HotReloadTypeLoader +{ + private readonly HotReloadServer _server; + + private Assembly? _assembly; + + private bool _running; + + private static HotReloadTypeLoader? _instance; + + public HotReloadTypeLoader() + { + _server = new HotReloadServer(ReceivedAssemblyFromHost); + } + + public static HotReloadTypeLoader Instance + { + get => MauiReactorFeatures.HotReloadIsEnabled ? (_instance ??= new()) : throw new InvalidOperationException(); + } + + public Assembly? LastLoadedAssembly => _assembly; + + public Action? OnHotReloadCompleted { get; set; } + + public WeakProducer? AssemblyChangedEvent { get; } = new(); + + + public T LoadObject(Type type) + { + if (_assembly == null) + { + return (T)(Activator.CreateInstance(type) ?? throw new InvalidOperationException()); + } + + var objectTypeInAssembly = _assembly.GetType(type.FullName ?? throw new InvalidOperationException()) ?? throw new InvalidOperationException(); + + try + { + return (T)(Activator.CreateInstance(objectTypeInAssembly) ?? throw new InvalidOperationException()); + } + catch (Exception ex) + { + Debug.WriteLine($"[MauiReactor] Unable to hot reload component {objectTypeInAssembly.FullName}:{Environment.NewLine}{ex}"); + throw new InvalidOperationException($"Unable to hot reload component {objectTypeInAssembly.FullName}", ex); + } + } + + private void ReceivedAssemblyFromHost(Assembly? newAssembly) + { + _assembly = newAssembly; + AssemblyChangedEvent?.Raise(consumer => consumer.OnAssemblyChanged()); + OnHotReloadCompleted?.Invoke(); + } + + public void Run() + { + if (_running) + { + return; + } + + _running = true; + DeviceDisplay.Current.MainDisplayInfoChanged += OnMainDisplayInfoChanged; + _server.Run(); + } + + static float? _lastRefreshRate; + + private void OnMainDisplayInfoChanged(object? sender, DisplayInfoChangedEventArgs e) + { + if (_lastRefreshRate == null || + _lastRefreshRate != e.DisplayInfo.RefreshRate) + { + _lastRefreshRate = e.DisplayInfo.RefreshRate; + Debug.WriteLine($"[MauiReactor] FPS: {_lastRefreshRate}"); + } + } + + public void Stop() + { + DeviceDisplay.Current.MainDisplayInfoChanged -= OnMainDisplayInfoChanged; + _server.Stop(); + _running = false; + } + +#pragma warning disable IDE0051 // Remove unused private members + static void UpdateApplication(Type[]? _) +#pragma warning restore IDE0051 // Remove unused private members + { + if (Instance == null) + { + Debug.WriteLine($"[MauiReactor] Hot-Reload is not enabled, please call EnableMauiReactorHotReload() on your AppBuilder"); + return; + } + + //Debug.WriteLine($"[MauiReactor] Hot-Reload triggered"); + + Instance.ReceivedAssemblyFromHost(null); + } +} diff --git a/src/MauiReactor/HotReload/ITypeLoader.cs b/src/MauiReactor/HotReload/ITypeLoader.cs deleted file mode 100644 index f5b6b055..00000000 --- a/src/MauiReactor/HotReload/ITypeLoader.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MauiReactor.Internals; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; - -namespace MauiReactor.HotReload -{ - internal interface ITypeLoaderEventConsumer - { - void OnAssemblyChanged(); - } - - internal interface ITypeLoader - { - WeakProducer? AssemblyChangedEvent { get; } - - Assembly? LastLoadedAssembly { get; } - - T LoadObject(Type type); - - void Run(); - - void Stop(); - } - - internal abstract class TypeLoader - { - private static RemoteTypeLoader? _remoteComponentLoader; - private static LocalTypeLoader? _localComponentLoader; - - public static bool UseRemoteLoader { get; set; } - - public static ITypeLoader Instance - => UseRemoteLoader ? _remoteComponentLoader ??= new RemoteTypeLoader() : _localComponentLoader ??= new LocalTypeLoader(); - - public static Action? OnHotReloadCompleted { get; set; } - } -} diff --git a/src/MauiReactor/HotReload/LocalTypeLoader.cs b/src/MauiReactor/HotReload/LocalTypeLoader.cs index 78ceaa4f..54d17244 100644 --- a/src/MauiReactor/HotReload/LocalTypeLoader.cs +++ b/src/MauiReactor/HotReload/LocalTypeLoader.cs @@ -4,28 +4,28 @@ using System.Reflection; using System.Text; -namespace MauiReactor.HotReload -{ - internal class LocalTypeLoader : ITypeLoader - { -#pragma warning disable CS0067 - public WeakProducer? AssemblyChangedEvent { get; } - public Assembly? LastLoadedAssembly { get; } -#pragma warning restore CS0067 +//namespace MauiReactor.HotReload +//{ +// internal class LocalTypeLoader : ITypeLoader +// { +//#pragma warning disable CS0067 +// public WeakProducer? AssemblyChangedEvent { get; } +// public Assembly? LastLoadedAssembly { get; } +//#pragma warning restore CS0067 - public T LoadObject(Type type) - { - return (T)(Activator.CreateInstance(type) ?? throw new InvalidOperationException()); - } +// public T LoadObject(Type type) +// { +// return (T)(Activator.CreateInstance(type) ?? throw new InvalidOperationException()); +// } - public void Run() - { +// public void Run() +// { - } +// } - public void Stop() - { +// public void Stop() +// { - } - } -} +// } +// } +//} diff --git a/src/MauiReactor/HotReload/RemoteTypeLoader.cs b/src/MauiReactor/HotReload/RemoteTypeLoader.cs index deea22cc..10b3def1 100644 --- a/src/MauiReactor/HotReload/RemoteTypeLoader.cs +++ b/src/MauiReactor/HotReload/RemoteTypeLoader.cs @@ -4,101 +4,100 @@ using System.Reflection; using System.Reflection.Metadata; -[assembly: MetadataUpdateHandler(typeof(MauiReactor.HotReload.RemoteTypeLoader))] namespace MauiReactor.HotReload; -internal class RemoteTypeLoader : ITypeLoader -{ - public WeakProducer? AssemblyChangedEvent { get; } = new(); +//internal class RemoteTypeLoader : ITypeLoader +//{ +// public WeakProducer? AssemblyChangedEvent { get; } = new(); - private readonly HotReloadServer _server; +// private readonly HotReloadServer _server; - private static RemoteTypeLoader? _instance; +// private static RemoteTypeLoader? _instance; - private Assembly? _assembly; +// private Assembly? _assembly; - private bool _running; +// private bool _running; - public RemoteTypeLoader() - { - _instance = this; - _server = new HotReloadServer(ReceivedAssemblyFromHost); - } +// public RemoteTypeLoader() +// { +// _instance = this; +// _server = new HotReloadServer(ReceivedAssemblyFromHost); +// } - public Assembly? LastLoadedAssembly => _assembly; +// public Assembly? LastLoadedAssembly => _assembly; - public T LoadObject(Type type) - { - if (_assembly == null) - { - return (T)(Activator.CreateInstance(type) ?? throw new InvalidOperationException()); - } +// public T LoadObject(Type type) +// { +// if (_assembly == null) +// { +// return (T)(Activator.CreateInstance(type) ?? throw new InvalidOperationException()); +// } - var objectTypeInAssembly = _assembly.GetType(type.FullName ?? throw new InvalidOperationException()) ?? throw new InvalidOperationException(); - - try - { - return (T)(Activator.CreateInstance(objectTypeInAssembly) ?? throw new InvalidOperationException()); - } - catch (Exception ex) - { - Debug.WriteLine($"[MauiReactor] Unable to hot reload component {objectTypeInAssembly.FullName}:{Environment.NewLine}{ex}"); - throw new InvalidOperationException($"Unable to hot reload component {objectTypeInAssembly.FullName}", ex); - } - } - - private void ReceivedAssemblyFromHost(Assembly? newAssembly) - { - _assembly = newAssembly; - AssemblyChangedEvent?.Raise(consumer => consumer.OnAssemblyChanged()); - TypeLoader.OnHotReloadCompleted?.Invoke(); - } - - public void Run() - { - if (_running) - { - return; - } - - _running = true; - DeviceDisplay.Current.MainDisplayInfoChanged += OnMainDisplayInfoChanged; - _server.Run(); - } - - static float? _lastRefreshRate; - - private void OnMainDisplayInfoChanged(object? sender, DisplayInfoChangedEventArgs e) - { - if (_lastRefreshRate == null || - _lastRefreshRate != e.DisplayInfo.RefreshRate) - { - _lastRefreshRate = e.DisplayInfo.RefreshRate; - Debug.WriteLine($"[MauiReactor] FPS: {_lastRefreshRate}"); - } - } - - public void Stop() - { - DeviceDisplay.Current.MainDisplayInfoChanged -= OnMainDisplayInfoChanged; - _server.Stop(); - _running = false; - } - -#pragma warning disable IDE0051 // Remove unused private members - static void UpdateApplication(Type[]? _) -#pragma warning restore IDE0051 // Remove unused private members - { - if (_instance == null) - { - Debug.WriteLine($"[MauiReactor] Hot-Reload is not enabled, please call EnableMauiReactorHotReload() on your AppBuilder"); - return; - } - - //Debug.WriteLine($"[MauiReactor] Hot-Reload triggered"); - - _instance.ReceivedAssemblyFromHost(null); - } -} \ No newline at end of file +// var objectTypeInAssembly = _assembly.GetType(type.FullName ?? throw new InvalidOperationException()) ?? throw new InvalidOperationException(); + +// try +// { +// return (T)(Activator.CreateInstance(objectTypeInAssembly) ?? throw new InvalidOperationException()); +// } +// catch (Exception ex) +// { +// Debug.WriteLine($"[MauiReactor] Unable to hot reload component {objectTypeInAssembly.FullName}:{Environment.NewLine}{ex}"); +// throw new InvalidOperationException($"Unable to hot reload component {objectTypeInAssembly.FullName}", ex); +// } +// } + +// private void ReceivedAssemblyFromHost(Assembly? newAssembly) +// { +// _assembly = newAssembly; +// AssemblyChangedEvent?.Raise(consumer => consumer.OnAssemblyChanged()); +// TypeLoader.OnHotReloadCompleted?.Invoke(); +// } + +// public void Run() +// { +// if (_running) +// { +// return; +// } + +// _running = true; +// DeviceDisplay.Current.MainDisplayInfoChanged += OnMainDisplayInfoChanged; +// _server.Run(); +// } + +// static float? _lastRefreshRate; + +// private void OnMainDisplayInfoChanged(object? sender, DisplayInfoChangedEventArgs e) +// { +// if (_lastRefreshRate == null || +// _lastRefreshRate != e.DisplayInfo.RefreshRate) +// { +// _lastRefreshRate = e.DisplayInfo.RefreshRate; +// Debug.WriteLine($"[MauiReactor] FPS: {_lastRefreshRate}"); +// } +// } + +// public void Stop() +// { +// DeviceDisplay.Current.MainDisplayInfoChanged -= OnMainDisplayInfoChanged; +// _server.Stop(); +// _running = false; +// } + +//#pragma warning disable IDE0051 // Remove unused private members +// static void UpdateApplication(Type[]? _) +//#pragma warning restore IDE0051 // Remove unused private members +// { +// if (_instance == null) +// { +// Debug.WriteLine($"[MauiReactor] Hot-Reload is not enabled, please call EnableMauiReactorHotReload() on your AppBuilder"); +// return; +// } + +// //Debug.WriteLine($"[MauiReactor] Hot-Reload triggered"); + +// _instance.ReceivedAssemblyFromHost(null); +// } +//} \ No newline at end of file diff --git a/src/MauiReactor/Integration/ComponentHost.cs b/src/MauiReactor/Integration/ComponentHost.cs index 49bc4980..0810b0cc 100644 --- a/src/MauiReactor/Integration/ComponentHost.cs +++ b/src/MauiReactor/Integration/ComponentHost.cs @@ -33,11 +33,17 @@ public ComponentHostNode(ComponentHost host) public void OnAssemblyChanged() { + if (!MauiReactorFeatures.HotReloadIsEnabled) + { + throw new InvalidOperationException(); + } + Validate.EnsureNotNull(_component); + try { - var newComponent = TypeLoader.Instance.LoadObject(_component.GetType()); + var newComponent = HotReloadTypeLoader.Instance.LoadObject(_component.GetType()); if (newComponent != null) { _component = newComponent; @@ -162,8 +168,11 @@ IHostElement IHostElement.Run() { _sleeping = false; - TypeLoader.Instance.Run(); - TypeLoader.Instance.AssemblyChangedEvent?.AddListener(this); + if (MauiReactorFeatures.HotReloadIsEnabled) + { + HotReloadTypeLoader.Instance.Run(); + HotReloadTypeLoader.Instance.AssemblyChangedEvent?.AddListener(this); + } OnLayout(); @@ -172,7 +181,10 @@ IHostElement IHostElement.Run() void IHostElement.Stop() { - TypeLoader.Instance.AssemblyChangedEvent?.RemoveListener(this); + if (MauiReactorFeatures.HotReloadIsEnabled) + { + HotReloadTypeLoader.Instance.AssemblyChangedEvent?.RemoveListener(this); + } _sleeping = true; } diff --git a/src/MauiReactor/Internals/ComponentServicesAttribute.cs b/src/MauiReactor/Internals/ComponentServicesAttribute.cs deleted file mode 100644 index 611dddbe..00000000 --- a/src/MauiReactor/Internals/ComponentServicesAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MauiReactor.HotReload; - -/// -/// Attribute to mark a method as a component service method: the method is called to register services when the assembly is hot-reloaded. -/// -[AttributeUsage(AttributeTargets.Method)] -public class ComponentServicesAttribute : Attribute -{ -} \ No newline at end of file diff --git a/src/MauiReactor/Internals/MauiReactorFeatures.cs b/src/MauiReactor/Internals/MauiReactorFeatures.cs new file mode 100644 index 00000000..4710a655 --- /dev/null +++ b/src/MauiReactor/Internals/MauiReactorFeatures.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MauiReactor.Internals; + +internal static class MauiReactorFeatures +{ + [FeatureSwitchDefinition("MauiReactor.HotReload")] + public static bool HotReloadIsEnabled + => AppContext.TryGetSwitch("MauiReactor.HotReload", out var isEnabled) && isEnabled; + + [FeatureSwitchDefinition("MauiReactor.FrameRate")] + public static bool FrameRateIsEnabled + => AppContext.TryGetSwitch("MauiReactor.FrameRate", out var isEnabled) && isEnabled; + +} diff --git a/src/MauiReactor/Internals/ServiceCollectionProvider.cs b/src/MauiReactor/Internals/ServiceCollectionProvider.cs index 075167ff..058ed4a7 100644 --- a/src/MauiReactor/Internals/ServiceCollectionProvider.cs +++ b/src/MauiReactor/Internals/ServiceCollectionProvider.cs @@ -13,18 +13,23 @@ public static IServiceProvider ServiceProvider get => _serviceProvider; set { - if (value != null && EnableHotReload) + if (value != null) { - _serviceProvider = new ServiceProviderWithHotReloadedServices(value); + if (MauiReactorFeatures.HotReloadIsEnabled) + { + _serviceProvider = new ServiceProviderWithHotReloadedServices(value); + } + else + { + _serviceProvider = value; + } } else { - _serviceProvider = value!; + _serviceProvider = null!; } } } - - public static bool EnableHotReload { get; set; } } internal class ServiceProviderWithHotReloadedServices : IServiceProvider @@ -43,7 +48,7 @@ public ServiceProviderWithHotReloadedServices(IServiceProvider serviceProvider) var service = _serviceProvider.GetService(serviceType); if (service == null) { - if (ServiceCollectionProvider.EnableHotReload) + if (MauiReactorFeatures.HotReloadIsEnabled) { //1. find the first static method in the assembly that has the attribute ComponentServicesAttribute //2. if it exists it must accept a IServiceCollection as a parameter @@ -52,10 +57,10 @@ public ServiceProviderWithHotReloadedServices(IServiceProvider serviceProvider) //5. save the save provider and the assembly as local class field so to not recreate it every time //6. return the service from the new service provider - if (TypeLoader.Instance.LastLoadedAssembly != null && - _lastParseAssembly != TypeLoader.Instance.LastLoadedAssembly) + if (HotReloadTypeLoader.Instance.LastLoadedAssembly != null && + _lastParseAssembly != HotReloadTypeLoader.Instance.LastLoadedAssembly) { - _lastParseAssembly = TypeLoader.Instance.LastLoadedAssembly; + _lastParseAssembly = HotReloadTypeLoader.Instance.LastLoadedAssembly; var servicesBuilderMethods = _lastParseAssembly .GetTypes() diff --git a/src/MauiReactor/LinearItemsLayout.partial.cs b/src/MauiReactor/LinearItemsLayout.partial.cs index 4dd345cc..d6e5bd88 100644 --- a/src/MauiReactor/LinearItemsLayout.partial.cs +++ b/src/MauiReactor/LinearItemsLayout.partial.cs @@ -34,6 +34,13 @@ public VerticalLinearItemsLayout(Action new(); + public static VerticalLinearItemsLayout VerticalLinearItemsLayout() => new(); + + } } namespace MauiReactor.Internals diff --git a/src/MauiReactor/PageHost.cs b/src/MauiReactor/PageHost.cs index 117ecd78..dda77aff 100644 --- a/src/MauiReactor/PageHost.cs +++ b/src/MauiReactor/PageHost.cs @@ -250,10 +250,18 @@ protected virtual Component InitializeComponent(Component component) public IHostElement Run() { _sleeping = false; - _component ??= InitializeComponent(TypeLoader.Instance.LoadObject(typeof(T))); - TypeLoader.Instance.Run(); - TypeLoader.Instance.AssemblyChangedEvent?.AddListener(this); + if (MauiReactorFeatures.HotReloadIsEnabled) + { + _component ??= InitializeComponent(HotReloadTypeLoader.Instance.LoadObject(typeof(T))); + + HotReloadTypeLoader.Instance.Run(); + HotReloadTypeLoader.Instance.AssemblyChangedEvent?.AddListener(this); + } + else + { + _component ??= InitializeComponent(new T()); + } OnLayout(); @@ -275,9 +283,14 @@ protected override void OnRecyclingPage() public void OnAssemblyChanged() { + if (!MauiReactorFeatures.HotReloadIsEnabled) + { + throw new InvalidOperationException(); + } + try { - var newComponent = TypeLoader.Instance.LoadObject(typeof(T)); + var newComponent = HotReloadTypeLoader.Instance.LoadObject(typeof(T)); if (newComponent != null) { _component = newComponent; @@ -299,7 +312,11 @@ public void OnAssemblyChanged() public void Stop() { - TypeLoader.Instance.AssemblyChangedEvent?.RemoveListener(this); + if (!MauiReactorFeatures.HotReloadIsEnabled) + { + HotReloadTypeLoader.Instance.AssemblyChangedEvent?.RemoveListener(this); + } + _component = null; _sleeping = true; } diff --git a/src/MauiReactor/ReactorApplication.cs b/src/MauiReactor/ReactorApplication.cs index 09d13828..3e17f7f6 100644 --- a/src/MauiReactor/ReactorApplication.cs +++ b/src/MauiReactor/ReactorApplication.cs @@ -9,13 +9,16 @@ internal abstract class ReactorApplicationHost : VisualNode, IHostElement, IVisu { protected readonly ReactorApplication _application; - internal static bool _showFrameRate = false; + //internal static bool _showFrameRate = false; protected ReactorApplicationHost(ReactorApplication application) { _application = application ?? throw new ArgumentNullException(nameof(application)); - TypeLoader.Instance.AssemblyChangedEvent?.AddListener(this); + if (MauiReactorFeatures.HotReloadIsEnabled) + { + HotReloadTypeLoader.Instance.AssemblyChangedEvent?.AddListener(this); + } } public static Action? UnhandledException { get; set; } @@ -119,9 +122,12 @@ public override IHostElement Run() _application.Theme?.Apply(); _rootComponent ??= new T(); OnLayout(); - TypeLoader.Instance.Run(); + if (MauiReactorFeatures.HotReloadIsEnabled) + { + HotReloadTypeLoader.Instance.Run(); + } - if (_showFrameRate) + if (MauiReactorFeatures.FrameRateIsEnabled) { FrameRateIndicator.Start(); } @@ -136,11 +142,16 @@ public override void OnAssemblyChanged() { if (_application.Theme != null) { - _application.Theme = TypeLoader.Instance.LoadObject(_application.Theme.GetType()); + _application.Theme = MauiReactorFeatures.HotReloadIsEnabled ? + HotReloadTypeLoader.Instance.LoadObject(_application.Theme.GetType()) : + (Theme?)Activator.CreateInstance(_application.Theme.GetType()); _application.Theme?.Apply(); } - var newComponent = TypeLoader.Instance.LoadObject(typeof(T)); + var newComponent = MauiReactorFeatures.HotReloadIsEnabled ? + HotReloadTypeLoader.Instance.LoadObject(typeof(T)) : + new T(); + if (newComponent != null && _rootComponent != newComponent) { @@ -168,11 +179,14 @@ public override void Stop() if (_started) { _started = false; - TypeLoader.Instance.Stop(); + if (MauiReactorFeatures.HotReloadIsEnabled) + { + HotReloadTypeLoader.Instance.Stop(); + } - if (_showFrameRate) + if (MauiReactorFeatures.FrameRateIsEnabled) { - FrameRateIndicator.Start(); + FrameRateIndicator.Stop(); } } } @@ -358,33 +372,43 @@ protected override void CleanUp() public static class MauiAppBuilderExtensions { - public static MauiAppBuilder UseMauiReactorApp(this MauiAppBuilder appBuilder, Action? configureApplication = null) where TComponent : Component, new() + public static MauiAppBuilder UseMauiReactorApp(this MauiAppBuilder appBuilder, + Action? configureApplication = null, + Action? onHotReloadCompleted = null, + Action? unhandledExceptionAction = null) where TComponent : Component, new() => appBuilder.UseMauiApp(sp => { + if (MauiReactorFeatures.HotReloadIsEnabled) + { + HotReloadTypeLoader.Instance.OnHotReloadCompleted = onHotReloadCompleted; + } + + ReactorApplicationHost.UnhandledException = unhandledExceptionAction; + var app = new ReactorApplication(sp); configureApplication?.Invoke(app); return app; }); - public static MauiAppBuilder EnableMauiReactorHotReload(this MauiAppBuilder appBuilder, Action? onHotReloadCompleted = null) - { - TypeLoader.UseRemoteLoader = true; - TypeLoader.OnHotReloadCompleted = onHotReloadCompleted; - ServiceCollectionProvider.EnableHotReload = true; - return appBuilder; - } - - public static MauiAppBuilder EnableFrameRateIndicator(this MauiAppBuilder appBuilder) - { - ReactorApplicationHost._showFrameRate = true; - return appBuilder; - } - - public static MauiAppBuilder OnMauiReactorUnhandledException(this MauiAppBuilder appBuilder, Action unhandledExceptionAction) - { - ReactorApplicationHost.UnhandledException = unhandledExceptionAction; - return appBuilder; - } + //public static MauiAppBuilder EnableMauiReactorHotReload(this MauiAppBuilder appBuilder, Action? onHotReloadCompleted = null) + //{ + // TypeLoader.UseRemoteLoader = true; + // TypeLoader.OnHotReloadCompleted = onHotReloadCompleted; + // ServiceCollectionProvider.EnableHotReload = true; + // return appBuilder; + //} + + //public static MauiAppBuilder EnableFrameRateIndicator(this MauiAppBuilder appBuilder) + //{ + // ReactorApplicationHost._showFrameRate = true; + // return appBuilder; + //} + + //public static MauiAppBuilder OnMauiReactorUnhandledException(this MauiAppBuilder appBuilder, Action unhandledExceptionAction) + //{ + // ReactorApplicationHost.UnhandledException = unhandledExceptionAction; + // return appBuilder; + //} } diff --git a/templates/MauiReactorTemplate.StartupSampleXaml/Components/Main/ProjectCardView.cs b/templates/MauiReactorTemplate.StartupSampleXaml/Components/Main/ProjectCardView.cs index 5812a0ad..a5ca7ad1 100644 --- a/templates/MauiReactorTemplate.StartupSampleXaml/Components/Main/ProjectCardView.cs +++ b/templates/MauiReactorTemplate.StartupSampleXaml/Components/Main/ProjectCardView.cs @@ -76,7 +76,7 @@ IEnumerable RenderTags() .StrokeCornerRadius(16) .HeightRequest(32) .StrokeThickness(0) - .Background(tag.DisplayColor); + .Background(Color.FromArgb(tag.Color)); } } diff --git a/templates/MauiReactorTemplate.StartupSampleXaml/Components/Main/TaskView.cs b/templates/MauiReactorTemplate.StartupSampleXaml/Components/Main/TaskView.cs index 42fee291..d5993011 100644 --- a/templates/MauiReactorTemplate.StartupSampleXaml/Components/Main/TaskView.cs +++ b/templates/MauiReactorTemplate.StartupSampleXaml/Components/Main/TaskView.cs @@ -16,6 +16,9 @@ partial class TaskView : Component [Prop] ProjectTask _task = default!; + [Prop] + Action _onTaskCompletion; + public override VisualNode Render() { return Border( @@ -66,6 +69,8 @@ async Task OnTaskCheckCompletedChanged(CheckedChangedEventArgs e) { _task.IsCompleted = e.Value; await _taskRepository.SaveItemAsync(_task); + + _onTaskCompletion?.Invoke(e.Value); } } diff --git a/templates/MauiReactorTemplate.StartupSampleXaml/Components/Projects/ProjectDetailPage.cs b/templates/MauiReactorTemplate.StartupSampleXaml/Components/Projects/ProjectDetailPage.cs new file mode 100644 index 00000000..20eab609 --- /dev/null +++ b/templates/MauiReactorTemplate.StartupSampleXaml/Components/Projects/ProjectDetailPage.cs @@ -0,0 +1,288 @@ +using Fonts; +using MauiReactorTemplate.StartupSampleXaml.Components.Main; +using MauiReactorTemplate.StartupSampleXaml.Data; +using MauiReactorTemplate.StartupSampleXaml.Framework; +using MauiReactorTemplate.StartupSampleXaml.Models; +using MauiReactorTemplate.StartupSampleXaml.Resources.Styles; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MauiReactorTemplate.StartupSampleXaml.Components.Projects; +class ProjectDetailPageState +{ + public string Name { get; set; } = null!; + public string Description { get; set; } = null!; + public List Categories { get; set; } = []; + public List Tags { get; set; } = []; + public HashSet SelectedTags { get; set; } = []; + public HashSet ProjectTags { get; set; } = []; + public List ProjectTasks { get; set; } = []; + public int CategoryID { get; set; } + public string? Icon { get; set; } + +} + +class ProjectDetailPageProps +{ + public int ProjectID { get; set; } +} + +partial class ProjectDetailPage : Component +{ + [Inject] + ProjectRepository _projectRepository; + + [Inject] + CategoryRepository _categoryRepository; + + [Inject] + TagRepository _tagRepository; + + [Inject] + TaskRepository _taskRepository; + + public override VisualNode Render() + { + return ContentPage("Project", + Grid( + VScrollView( + VStack( + new SfTextInputLayout + { + Entry() + .Text(State.Name) + .OnTextChanged(newText => State.Name = newText) + } + .Hint("Name"), + new SfTextInputLayout + { + Entry() + .Text(State.Description) + .OnTextChanged(newText => State.Description = newText) + } + .Hint("Description"), + new SfTextInputLayout + { + Picker() + .ItemsSource(State.Categories.Select(_=>_.Title).ToList()) + .SelectedIndex(State.Categories.FindIndex(_=>_.ID == State.CategoryID)) + .OnSelectedIndexChanged(newIndex => State.CategoryID = newIndex) + } + .Hint("Category"), + + Label("Icon") + .Style(ResourceHelper.GetResource