div>
+ body>
+ html>
+";
+ internalWebView.NavigateToString(html);
+#else
+ LoadJavaScript();
+#endif
+ }
+
+ protected async Task LoadEmbeddedJavaScriptFile(string filename)
+ {
+ var markdownScript = (await GetEmbeddedFileStreamAsync(GetType(), filename)).ReadToEnd();
+
+ await InvokeScriptAsync(markdownScript, false);
+ }
+
+ protected abstract Task LoadJavaScript();
+
+#if !__WASM__
+ private void NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
+ {
+#if __ANDROID__
+ var wv = internalWebView.GetType().GetField("_webView", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(internalWebView) as Android.Webkit.WebView;
+ wv.SetBackgroundColor(Android.Graphics.Color.Transparent);
+#endif
+ LoadJavaScript();
+ }
+#endif
+
+ protected async Task UpdateHtmlFromScript(string contentScript)
+ {
+ if (Foreground is SolidColorBrush colorBrush)
+ {
+ var color = colorBrush.Color;
+ // This is required because default tostring on wasm doesn't come out in the format #RRGGBB or even #AARRGGBB
+ var colorString = $"#{color.R.ToString("X")}{color.G.ToString("X")}{color.B.ToString("X")}";
+ Console.WriteLine($"Color {colorString}");
+ var colorScript = $@"document.getElementById('{HtmlContentId}').style.color = '{colorString}';";
+ await InvokeScriptAsync(colorScript);
+ }
+
+ var script = $@"document.getElementById('{HtmlContentId}').innerHTML = {contentScript};";
+ await InvokeScriptAsync(script);
+ }
+
+ public async Task
InvokeScriptAsync(string scriptToRun, bool resizeAfterScript = true)
+ {
+ scriptToRun = ReplaceLiterals(scriptToRun);
+
+#if !__WASM__
+ var source = new CancellationTokenSource();
+ var result = await internalWebView.InvokeScriptAsync(
+ source.Token,
+ "eval", new[] { scriptToRun }).AsTask();
+ if (resizeAfterScript)
+ {
+ await ResizeToContent();
+ }
+ return result;
+#else
+ var script = $"javascript:eval(\"{scriptToRun}\");";
+ Console.Error.WriteLine(script);
+
+ try
+ {
+ var result = WebAssemblyRuntime.InvokeJS(script);
+ Console.WriteLine($"Result: {result}");
+
+ if (resizeAfterScript)
+ {
+ await ResizeToContent();
+ }
+
+ return result;
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine("FAILED " + e);
+ return null;
+ }
+
+#endif
+ }
+
+ private static Func ReplaceLiterals = txt =>
+ txt.Replace("\\", "\\\\").Replace("\n", "\\n").Replace("\r", "\\r").Replace("\"", "\\\"").Replace("\'", "\\\'").Replace("`", "\\`").Replace("^", "\\^");
+
+
+ public static async Task GetEmbeddedFileStreamAsync(Type assemblyType, string fileName)
+ {
+ await Task.Yield();
+
+ var manifestName = assemblyType.GetTypeInfo().Assembly
+ .GetManifestResourceNames()
+ .FirstOrDefault(n => n.EndsWith(fileName.Replace(" ", "_").Replace("/", ".").Replace("\\", "."), StringComparison.OrdinalIgnoreCase));
+
+ if (manifestName == null)
+ {
+ throw new InvalidOperationException($"Failed to find resource [{fileName}]");
+ }
+
+ return assemblyType.GetTypeInfo().Assembly.GetManifestResourceStream(manifestName);
+ }
+ public async Task ResizeToContent()
+ {
+ var documentRoot =
+#if __WASM__
+ $"document.getElementById('{HtmlContentId}')";
+#else
+ $"document.body";
+#endif
+
+
+ var heightString = await InvokeScriptAsync($"{documentRoot}.scrollHeight.toString()",
+ false);
+ int height;
+ if (int.TryParse(heightString, out height))
+ {
+ this.Height = height;
+ }
+ }
+ }
+}
+
+#endif
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Controls/MarkedControl.cs b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Controls/MarkedControl.cs
new file mode 100644
index 0000000..c0ff202
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Controls/MarkedControl.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Threading.Tasks;
+using Windows.UI.Text;
+using Uno.Extensions;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+using Microsoft.Toolkit.Uwp.Helpers;
+
+namespace MvvmSample.Controls
+{
+ public partial class MarkedControl
+#if WINDOWS_UWP
+ : Microsoft.Toolkit.Uwp.UI.Controls.MarkdownTextBlock
+ {
+ public MarkedControl()
+ {
+ Background = new SolidColorBrush("#60222222".ToColor());
+ Padding = new Thickness(8);
+ Header2FontWeight = FontWeights.Bold;
+ Header2FontSize = 20;
+ Header2Margin = new Thickness(0, 15, 0, 15);
+ Header2Foreground = (Brush)Application.Current.Resources.ThemeDictionaries["DefaultTextForegroundThemeBrush"];
+ }
+ }
+#else
+ : JavaScriptControl
+ {
+ public event EventHandler MarkedReady;
+
+ public bool IsMarkedReady { get; private set; }
+
+
+ public string Text
+ {
+ get { return (string)GetValue(MarkdownTextProperty); }
+ set { SetValue(MarkdownTextProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
+ public static readonly DependencyProperty MarkdownTextProperty =
+ DependencyProperty.Register("Text", typeof(string), typeof(MarkedControl), new PropertyMetadata(null, MarkdownTextChanged));
+
+ private static async void MarkdownTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ await (d as MarkedControl).DisplayMarkdownText();
+ }
+
+ private async Task DisplayMarkdownText()
+ {
+ if (IsMarkedReady && !string.IsNullOrWhiteSpace(Text))
+ {
+ await DisplayMarkdown(Text);
+ }
+ }
+
+ public string MarkedEmbeddedJavaScriptFile { get; set; } = "marked.min.js";
+
+ protected override async Task LoadJavaScript()
+ {
+ await LoadEmbeddedJavaScriptFile(MarkedEmbeddedJavaScriptFile);
+
+ IsMarkedReady = true;
+
+ await DisplayMarkdownText();
+
+ MarkedReady?.Invoke(this, EventArgs.Empty);
+ }
+
+ public async Task DisplayMarkdown(string markdown)
+ {
+ markdown = markdown.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\"", "\\\"").Replace("\'", "\\\'");//.Replace("\t","\\t").Replace("`","");
+ System.Diagnostics.Debug.WriteLine(markdown);
+ await UpdateHtmlFromScript($"marked('{markdown}')");
+ }
+
+ public async Task LoadMarkdownFromFile(string embeddedFileName)
+ {
+ var markdown = (await GetEmbeddedFileStreamAsync(GetType(), embeddedFileName)).ReadToEnd();
+ await DisplayMarkdown(markdown);
+ }
+ }
+#endif
+}
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Helpers/TitleBarHelper.cs b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Helpers/TitleBarHelper.cs
new file mode 100644
index 0000000..d7a9bb3
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Helpers/TitleBarHelper.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.ApplicationModel.Core;
+using Windows.UI;
+using Windows.UI.ViewManagement;
+
+namespace MvvmSample.Helpers
+{
+ ///
+ /// A with helper methods to manage the title bar.
+ ///
+ public static class TitleBarHelper
+ {
+ ///
+ /// Styles the title bar buttons according to the theme in use.
+ ///
+ public static void StyleTitleBar()
+ {
+ ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
+
+ // Transparent colors
+ titleBar.ForegroundColor = Colors.Transparent;
+ titleBar.BackgroundColor = Colors.Transparent;
+ titleBar.ButtonBackgroundColor = Colors.Transparent;
+ titleBar.InactiveBackgroundColor = Colors.Transparent;
+ titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
+
+ // Theme aware colors
+ titleBar.ButtonForegroundColor = titleBar.ButtonHoverForegroundColor = titleBar.ButtonPressedForegroundColor = Colors.White;
+ titleBar.ButtonHoverBackgroundColor = Color.FromArgb(0x20, 0xFF, 0xFF, 0xFF);
+ titleBar.ButtonPressedBackgroundColor = Color.FromArgb(0x40, 0xFF, 0xFF, 0xFF);
+ titleBar.ButtonInactiveForegroundColor = Color.FromArgb(0xC0, 0xFF, 0xFF, 0xFF);
+ titleBar.InactiveForegroundColor = Color.FromArgb(0xA0, 0xA0, 0xA0, 0xA0);
+ }
+
+ ///
+ /// Sets up the app UI to be expanded into the title bar.
+ ///
+ public static void ExpandViewIntoTitleBar()
+ {
+ CoreApplicationViewTitleBar coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
+ coreTitleBar.ExtendViewIntoTitleBar = true;
+ }
+ }
+}
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MainPage.xaml b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MainPage.xaml
new file mode 100644
index 0000000..4625c40
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MainPage.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MainPage.xaml.cs b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MainPage.xaml.cs
new file mode 100644
index 0000000..21a0ca7
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MainPage.xaml.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
+
+namespace MvvmSample
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainPage : Page
+ {
+ public MainPage()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MvvmSample.Shared.projitems b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MvvmSample.Shared.projitems
new file mode 100644
index 0000000..d52f342
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MvvmSample.Shared.projitems
@@ -0,0 +1,177 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ 6279c845-92f8-4333-ab99-3d213163593c
+
+
+ MvvmSample.Shared
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ App.xaml
+
+
+
+
+
+
+ MainPage.xaml
+
+
+
+
+ Shell.xaml
+
+
+ AsyncRelayCommandPage.xaml
+
+
+ BuildingTheUIPage.xaml
+
+
+ IntroductionPage.xaml
+
+
+ IocPage.xaml
+
+
+ MessengerPage.xaml
+
+
+ MessengerRequestPage.xaml
+
+
+ MessengerSendPage.xaml
+
+
+ ObservableObjectPage.xaml
+
+
+ PuttingThingsTogetherPage.xaml
+
+
+ RedditBrowserPage.xaml
+
+
+ RedditServicePage.xaml
+
+
+ RelayCommandPage.xaml
+
+
+ SettingsServicePage.xaml
+
+
+ SettingUpTheViewModelsPage.xaml
+
+
+ PostWidget.xaml
+
+
+ SubredditWidget.xaml
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MvvmSample.Shared.shproj b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MvvmSample.Shared.shproj
new file mode 100644
index 0000000..6790347
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/MvvmSample.Shared.shproj
@@ -0,0 +1,13 @@
+
+
+
+ 6279c845-92f8-4333-ab99-3d213163593c
+ 14.0
+
+
+
+
+
+
+
+
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Services/FileService.cs b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Services/FileService.cs
new file mode 100644
index 0000000..7757535
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Services/FileService.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Windows.ApplicationModel;
+using Windows.Storage;
+
+#nullable enable
+
+namespace MvvmSample.Services
+{
+ ///
+ /// A that implements the using UWP APIs.
+ ///
+ public sealed class FilesService : IFilesService
+ {
+ ///
+ public string InstallationPath => Package.Current.InstalledLocation.Path;
+
+ ///
+ public async Task OpenForReadAsync(string path)
+ {
+ return await GetEmbeddedFileStreamAsync(GetType(), path);
+ }
+
+
+ private static async Task GetEmbeddedFileStreamAsync(Type assemblyType, string fileName)
+ {
+ await Task.Yield();
+
+ var manifestName = assemblyType.GetTypeInfo().Assembly
+ .GetManifestResourceNames()
+ .FirstOrDefault(n => n.EndsWith(fileName.Replace(" ", "_").Replace("\\",".").Replace("/", "."), StringComparison.OrdinalIgnoreCase));
+
+ if (manifestName == null)
+ {
+ throw new InvalidOperationException($"Failed to find resource [{fileName}]");
+ }
+
+ return assemblyType.GetTypeInfo().Assembly.GetManifestResourceStream(manifestName);
+ }
+ }
+}
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Services/SettingsService.cs b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Services/SettingsService.cs
new file mode 100644
index 0000000..ba49a03
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Services/SettingsService.cs
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.Foundation.Collections;
+using Windows.Storage;
+
+namespace MvvmSample.Services
+{
+ ///
+ /// A simple that handles the local app settings.
+ ///
+ public sealed class SettingsService : ISettingsService
+ {
+ ///
+ /// The with the settings targeted by the current instance.
+ ///
+ private readonly IPropertySet SettingsStorage = ApplicationData.Current.LocalSettings.Values;
+
+ ///
+ public void SetValue(string key, T value)
+ {
+ if (!SettingsStorage.ContainsKey(key)) SettingsStorage.Add(key, value);
+ else SettingsStorage[key] = value;
+ }
+
+ ///
+ public T GetValue(string key)
+ {
+ if (SettingsStorage.TryGetValue(key, out object value))
+ {
+ return (T)value;
+ }
+
+ return default;
+ }
+ }
+}
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Shell.xaml b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Shell.xaml
new file mode 100644
index 0000000..1a0be95
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Shell.xaml
@@ -0,0 +1,220 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Shell.xaml.cs b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Shell.xaml.cs
new file mode 100644
index 0000000..f85b74f
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Shell.xaml.cs
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+using MvvmSample.Views;
+
+#nullable enable
+
+namespace MvvmSample
+{
+ public sealed partial class Shell : UserControl
+ {
+ private readonly IReadOnlyCollection NavigationItems;
+
+ public Shell()
+ {
+ this.InitializeComponent();
+
+ NavigationItems = new[]
+ {
+ new SampleEntry(IntroductionItem, typeof(IntroductionPage)),
+ new SampleEntry(ObservableObjectItem, typeof(ObservableObjectPage), "ObservableObject", "observable inotify property changed propertychanging changing"),
+ new SampleEntry(CommandsItem, typeof(RelayCommandPage), "RelayCommand and RelayCommand", "commands icommand relaycommand binding"),
+ new SampleEntry(AsyncCommandsItem, typeof(AsyncRelayCommandPage), "AsyncRelayCommand and AsyncRelayCommand", "asynccommands icommand relaycommand binding asynchronous"),
+ new SampleEntry(MessengerItem, typeof(MessengerPage), "Messenger and IMessenger", "messenger messaging message receiver recipient"),
+ new SampleEntry(SendMessagesItem, typeof(MessengerSendPage), "[IMessenger] Send messages", "messenger messaging message receiver recipient send"),
+ new SampleEntry(RequestMessagesItem, typeof(MessengerRequestPage), "[IMessenger] Request messages", "messenger messaging message receiver recipient request reply"),
+ new SampleEntry(InversionOfControlItem, typeof(IocPage), "Ioc (Inversion of control)", "ioc inversion control dependency injection service locator"),
+ new SampleEntry(RedditBrowserOverviewItem, typeof(PuttingThingsTogetherPage), "Putting things together"),
+ new SampleEntry(ViewModelsSetupItem, typeof(SettingUpTheViewModelsPage), "Setting up the ViewModels"),
+ new SampleEntry(SettingsServiceItem, typeof(SettingsServicePage), "Settings service"),
+ new SampleEntry(RedditServiceItem, typeof(RedditServicePage), "Reddit service"),
+ new SampleEntry(BuildingTheUIItem, typeof(BuildingTheUIPage), "Building the UI"),
+ new SampleEntry(FinalResultItem, typeof(RedditBrowserPage), "Reddit browser")
+ };
+#if !__IOS__ && !__MACOS__
+ // Set the custom title bar to act as a draggable region
+ Window.Current.SetTitleBar(TitleBarBorder);
+#endif
+ }
+
+ // Navigates to a sample page when a button is clicked
+ private void NavigationView_OnItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
+ {
+ if (NavigationItems.FirstOrDefault(item => item.Item == args.InvokedItemContainer)?.PageType is Type pageType)
+ {
+ NavigationFrame.Navigate(pageType);
+ }
+ }
+
+ // Sets whether or not the back button is enabled
+ private void NavigationFrame_OnNavigated(object sender, NavigationEventArgs e)
+ {
+ NavigationView.IsBackEnabled = ((Frame)sender).BackStackDepth > 0;
+ }
+
+ // Navigates back
+ private void NavigationView_OnBackRequested(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewBackRequestedEventArgs args)
+ {
+ if (NavigationFrame.BackStack.LastOrDefault() is PageStackEntry entry)
+ {
+ NavigationView.SelectedItem = NavigationItems.First(item => item.PageType == entry.SourcePageType).Item;
+
+ NavigationFrame.GoBack();
+ }
+ }
+
+ // Select the introduction item when the shell is loaded
+ private void Shell_OnLoaded(object sender, RoutedEventArgs e)
+ {
+ //NavigationView.SelectedItem = IntroductionItem;
+
+ //NavigationFrame.Navigate(typeof(IntroductionPage));
+ }
+
+ // Updates the search results
+ private void SearchBox_OnTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
+ {
+ if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
+ {
+ // Not a simple tokenized search, but good enough for now
+ string query = sender.Text.ToLowerInvariant();
+
+ sender.ItemsSource = NavigationItems.Where(item => item.Tags?.Contains(query) == true);
+ }
+ }
+
+ // Navigates to a selected item
+ private void AutoSuggestBox_OnSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
+ {
+ SampleEntry entry = (SampleEntry)args.SelectedItem;
+
+ NavigationFrame.Navigate(entry.PageType);
+
+ NavigationView.SelectedItem = entry.Item;
+
+ sender.Text = string.Empty;
+ }
+ }
+
+ ///
+ /// A simple model for tracking sample pages associated with buttons.
+ ///
+ public sealed class SampleEntry
+ {
+ public SampleEntry(Microsoft.UI.Xaml.Controls.NavigationViewItem viewItem, Type pageType, string? name = null, string? tags = null)
+ {
+ Item = viewItem;
+ PageType = pageType;
+ Name = name;
+ Tags = tags;
+ }
+
+ ///
+ /// The navigation item for the current entry.
+ ///
+ public Microsoft.UI.Xaml.Controls.NavigationViewItem Item { get; }
+
+ ///
+ /// The associated page type for the current entry.
+ ///
+ public Type PageType { get; }
+
+ ///
+ /// Gets the name of the current entry.
+ ///
+ public string? Name { get; }
+
+ ///
+ /// Gets the tag for the current entry, if any.
+ ///
+ public string? Tags { get; }
+ }
+}
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Strings/en/Resources.resw b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Strings/en/Resources.resw
new file mode 100644
index 0000000..46bb989
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Strings/en/Resources.resw
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ MvvmSample
+
+
\ No newline at end of file
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Views/AsyncRelayCommandPage.xaml b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Views/AsyncRelayCommandPage.xaml
new file mode 100644
index 0000000..4e4007c
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Views/AsyncRelayCommandPage.xaml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<Page.Resources>
+ <converters:TaskResultConverter x:Key="TaskResultConverter"/>
+</Page.Resources>
+<StackPanel Spacing="8">
+ <TextBlock>
+ <Run Text="Task status:"/>
+ <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask.Status, Mode=OneWay}"/>
+ <LineBreak/>
+ <Run Text="Result:"/>
+ <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, Mode=OneWay}"/>
+ </TextBlock>
+ <Button
+ Content="Click me!"
+ Command="{x:Bind ViewModel.DownloadTextCommand}"/>
+ <muxc:ProgressRing
+ HorizontalAlignment="Left"
+ IsActive="{x:Bind ViewModel.DownloadTextCommand.IsRunning, Mode=OneWay}"/>
+</StackPanel>
+
+
+public MyViewModel()
+{
+ DownloadTextCommand = new AsyncRelayCommand(DownloadTextAsync);
+}
+
+public IAsyncRelayCommand DownloadTextCommand { get; }
+
+private async Task<string> DownloadTextAsync()
+{
+ await Task.Delay(3000); // Simulate a web request
+
+ return "Hello world!";
+}
+
+
+
+
+
diff --git a/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Views/AsyncRelayCommandPage.xaml.cs b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Views/AsyncRelayCommandPage.xaml.cs
new file mode 100644
index 0000000..e6b7ff8
--- /dev/null
+++ b/samples/MvvmSampleUno/MvvmSample/MvvmSample.Shared/Views/AsyncRelayCommandPage.xaml.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Data;
+
+namespace MvvmSample.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class AsyncRelayCommandPage : Page
+ {
+ public AsyncRelayCommandPage()
+ {
+ this.InitializeComponent();
+ }
+ }
+
+ // TODO: replace this with the one from the toolkit, when https://github.com/windows-toolkit/WindowsCommunityToolkit/pull/3410 is merged
+ public sealed class TaskResultConverter : IValueConverter
+ {
+ ///
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ //if (value is Task task &&
+ // task.IsCompletedSuccessfully)
+ //{
+ // return task.GetType().GetProperty(nameof(Task