Skip to content

Commit

Permalink
Merge pull request #2 from felipebaltazar/feature/base-server-driven-…
Browse files Browse the repository at this point in the history
…page

ServerDrivenVisualElements
  • Loading branch information
felipebaltazar authored Feb 4, 2024
2 parents 73047d4 + d17d7ea commit 12b0a10
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ updates:
interval: daily
time: "09:00"
open-pull-requests-limit: 10

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
4 changes: 2 additions & 2 deletions .github/workflows/PrCheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
fetch-depth: 0

- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}

Expand All @@ -72,4 +72,4 @@ jobs:
run: dotnet build Maui.ServerDrivenUI.sln --configuration Release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2
22 changes: 22 additions & 0 deletions Maui.ServerDrivenUI/Abstractions/IServerDrivenVisualElement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Maui.ServerDrivenUI;

public interface IServerDrivenVisualElement
{
public string? ServerKey
{
get; set;
}

public UIElementState State
{
get; set;
}
public Action OnLoaded
{
get;
set;
}

void OnStateChanged(UIElementState newState);
void OnError(Exception ex);
}
8 changes: 0 additions & 8 deletions Maui.ServerDrivenUI/Models/ServerUIElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ public class ServerUIElement
/// </summary>
public string Class { get; set; }

/// <summary>
/// Define if this element is a root visual element
/// </summary>
public bool IsRootElement { get; set; }

/// <summary>
/// Visual element properties
/// </summary>
Expand Down Expand Up @@ -57,22 +52,19 @@ public class ServerUIElement
/// <param name="type">Maui element type</param>
/// <param name="class">Class with namespace eg.: "MyNamespace.Folder.ClassName"</param>
/// <param name="content">Inner visual element content</param>
/// <param name="isRootElement">Define if this element is a root visual element</param>
/// <param name="customNamespaces">Custom namespaces to be used as root element</param>
public ServerUIElement(
string elementKey,
string type,
string @class,
IList<ServerUIElement> content,
bool isRootElement = false,
IList<CustomNamespace>? customNamespaces = null)
{
CustomNamespaces = customNamespaces ?? new List<CustomNamespace>(0);
Content = content;
Class = @class;
Type = type;
Key = elementKey;
IsRootElement = isRootElement;
}

[JsonConstructor]
Expand Down
9 changes: 9 additions & 0 deletions Maui.ServerDrivenUI/Models/UIElementState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Maui.ServerDrivenUI;

public enum UIElementState
{
None = 0,
Loading = 1,
Loaded = 2,
Error = 3,
}
11 changes: 11 additions & 0 deletions Maui.ServerDrivenUI/Services/ServiceProviderHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Maui.ServerDrivenUI.Services;

internal static class ServiceProviderHelper
{
public static IServiceProvider? ServiceProvider =>
#if WINDOWS
MauiWinUIApplication.Current?.Services;
#else
IPlatformApplication.Current?.Services;
#endif
}
13 changes: 6 additions & 7 deletions Maui.ServerDrivenUI/Services/XamlConverterService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ public static string ConvertToXml(ServerUIElement element)
strBuilder.Append($"<{element.Type}");
strBuilder.AppendLine();

if (element.IsRootElement)
{
if (!string.IsNullOrWhiteSpace(element.Class))
strBuilder.AppendLine($"x:Class=\"{element.Class}\"");
strBuilder.AppendLine($"xmlns=\"http://schemas.microsoft.com/dotnet/2021/maui\"");
strBuilder.AppendLine($"xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\"");

foreach (var cn in element.CustomNamespaces)
strBuilder.AppendLine($"xmlns:{cn.Alias}=\"clr-namespace:{cn.Namespace}{ParseAssembly(cn)}\"");
}
strBuilder.AppendLine($"xmlns=\"http://schemas.microsoft.com/dotnet/2021/maui\"");
strBuilder.AppendLine($"xmlns:x=\"http://schemas.microsoft.com/winfx/2009/xaml\"");

foreach (var cn in element.CustomNamespaces)
strBuilder.AppendLine($"xmlns:{cn.Alias}=\"clr-namespace:{cn.Namespace}{ParseAssembly(cn)}\"");

foreach (var prop in element.Properties)
strBuilder.AppendLine($"{prop.Key}=\"{prop.Value}\"");
Expand Down
67 changes: 67 additions & 0 deletions Maui.ServerDrivenUI/Views/ServerDrivenContentPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Maui.ServerDrivenUI.Views;

namespace Maui.ServerDrivenUI;

public abstract class ServerDrivenContentPage : ContentPage, IServerDrivenVisualElement
{
#region BindableProperties

public static readonly BindableProperty ServerKeyProperty = BindableProperty.Create(
nameof(ServerKey),
typeof(string),
typeof(ServerDrivenContentPage),
null);

public static readonly BindableProperty StateProperty = BindableProperty.Create(
nameof(State),
typeof(UIElementState),
typeof(ServerDrivenContentPage),
UIElementState.None,
propertyChanged: ServerDrivenVisualElement.OnStatePropertyChanged);

#endregion

#region Properties

public string? ServerKey
{
get => (string?)GetValue(ServerKeyProperty);
set => SetValue(ServerKeyProperty, value);
}

public UIElementState State
{
get => (UIElementState)GetValue(StateProperty);
set => SetValue(StateProperty, value);
}

public Action OnLoaded
{
get;
set;
}

#endregion

#region Constructors

public ServerDrivenContentPage() =>
_ = Task.Run(InitializeComponentAsync);

#endregion

#region Protected Methods

protected virtual Task InitializeComponentAsync() =>
ServerDrivenVisualElement.InitializeComponentAsync(this);

public virtual void OnStateChanged(UIElementState newState)
{
}

public virtual void OnError(Exception ex)
{
}

#endregion
}
67 changes: 67 additions & 0 deletions Maui.ServerDrivenUI/Views/ServerDrivenView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Maui.ServerDrivenUI.Views;

namespace Maui.ServerDrivenUI;

public class ServerDrivenView : ContentView, IServerDrivenVisualElement
{
#region BindableProperties

public static readonly BindableProperty ServerKeyProperty = BindableProperty.Create(
nameof(ServerKey),
typeof(string),
typeof(ServerDrivenView),
null);

public static readonly BindableProperty StateProperty = BindableProperty.Create(
nameof(State),
typeof(UIElementState),
typeof(ServerDrivenView),
UIElementState.None,
propertyChanged: ServerDrivenVisualElement.OnStatePropertyChanged);

#endregion

#region Properties

public string? ServerKey
{
get => (string?)GetValue(ServerKeyProperty);
set => SetValue(ServerKeyProperty, value);
}

public UIElementState State
{
get => (UIElementState)GetValue(StateProperty);
set => SetValue(StateProperty, value);
}

public Action OnLoaded
{
get;
set;
}

#endregion

#region Constructors

public ServerDrivenView() =>

Check warning on line 48 in Maui.ServerDrivenUI/Views/ServerDrivenView.cs

View workflow job for this annotation

GitHub Actions / Build and publish package

Non-nullable property 'OnLoaded' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 48 in Maui.ServerDrivenUI/Views/ServerDrivenView.cs

View workflow job for this annotation

GitHub Actions / Build and publish package

Non-nullable property 'OnLoaded' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
_ = Task.Run(InitializeComponentAsync);

#endregion

#region IServerDrivenVisualElement

protected virtual Task InitializeComponentAsync() =>
ServerDrivenVisualElement.InitializeComponentAsync(this);

public virtual void OnStateChanged(UIElementState newState)
{
}

public virtual void OnError(Exception ex)
{
}

#endregion
}
69 changes: 69 additions & 0 deletions Maui.ServerDrivenUI/Views/ServerDrivenVisualElement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Maui.ServerDrivenUI.Services;

namespace Maui.ServerDrivenUI.Views;

internal class ServerDrivenVisualElement
{
internal static async Task InitializeComponentAsync(IServerDrivenVisualElement element)
{
try
{
MainThread.BeginInvokeOnMainThread(() => element.State = UIElementState.Loading);

var serverDrivenUiService = ServiceProviderHelper.ServiceProvider?.GetService<IServerDrivenUIService>();
if (serverDrivenUiService != null)
{
var xaml = await serverDrivenUiService.GetXamlAsync(element.ServerKey ?? element.GetType().Name).ConfigureAwait(false);
MainThread.BeginInvokeOnMainThread(() => {
var onLoaded = element.OnLoaded;
(element as VisualElement)?.LoadFromXaml(xaml);

if (element is ServerDrivenContentPage page)
{
if (page.Content is null)
{
_ = Task.Run(() => InitializeComponentAsync(element));
return;
}
}
else if (element is ServerDrivenView view)
{
if (view.Content is null)
{
_ = Task.Run(() => InitializeComponentAsync(element));
return;
}
}

element.OnLoaded = onLoaded;
element.State = UIElementState.Loaded;
});
}
else
{
MainThread.BeginInvokeOnMainThread(() => {
element.State = UIElementState.Error;
element.OnError(new DependencyRegistrationException("IServerDrivenUIService not found, make sure you are calling 'ConfigureServerDrivenUI(s=> s.RegisterElementGetter((k)=> yourApiCall(k)))'"));
});
}
}
catch (Exception ex)
{
MainThread.BeginInvokeOnMainThread(() => {
element.State = UIElementState.Error;
element.OnError(ex);
});
}
}

internal static void OnStatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is not IServerDrivenVisualElement view
|| newValue is not UIElementState newState)
return;

view.OnStateChanged(newState);
if(newState is UIElementState.Loaded)
view.OnLoaded?.Invoke();
}
}
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,19 @@ public static class MauiProgram
}
```

- You can now receive the xaml and load it
- You can now use the ServerDrivenUI Elements

```csharp
```xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="Maui.ServerDrivenUI.Sample.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

<ServerDrivenView x:Name="sduiView" ServerKey="MyView" />

</ContentPage>

var xaml = await _serverDrivenUIService.GetXamlAsync("MyView").ConfigureAwait(false);
MainThread.BeginInvokeOnMainThread(() =>
{
sduiView.LoadFromXaml(xaml);
});
```

## Repo Activity
Expand Down
4 changes: 3 additions & 1 deletion samples/Maui.ServerDrivenUI.Sample/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

<ContentView x:Name="sduiView" />
<ServerDrivenView
x:Name="sduiView"
ServerKey="MyView" />

</ContentPage>
Loading

0 comments on commit 12b0a10

Please sign in to comment.