diff --git a/src/Calculator/App.xaml.cs b/src/Calculator/App.xaml.cs index 8ca14222..78849c76 100644 --- a/src/Calculator/App.xaml.cs +++ b/src/Calculator/App.xaml.cs @@ -75,13 +75,26 @@ namespace CalculatorApp { if (args.Kind == ActivationKind.Protocol) { - // We currently don't pass the uri as an argument, - // and handle any protocol launch as a normal app launch. - OnAppLaunch(args, null, false); + if (args.IsSnapshotProtocol()) + { + var protoArgs = (IProtocolActivatedEventArgs)args; + OnAppLaunch(args, + new SnapshotLaunchArguments + { + ActivityId = protoArgs.Uri.GetActivityId(), + LaunchUri = protoArgs.Uri + }, + false); + } + else + { + // handle any unknown protocol launch as a normal app launch. + OnAppLaunch(args, null, false); + } } } - private void OnAppLaunch(IActivatedEventArgs args, string argument, bool isPreLaunch) + private void OnAppLaunch(IActivatedEventArgs args, object arguments, bool isPreLaunch) { // Uncomment the following lines to display frame-rate and per-frame CPU usage info. //#if DEBUG @@ -132,7 +145,7 @@ namespace CalculatorApp // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter - if (rootFrame.Content == null && !rootFrame.Navigate(typeof(MainPage), argument)) + if (rootFrame.Content == null && !rootFrame.Navigate(typeof(MainPage), arguments)) { // We couldn't navigate to the main page, kill the app so we have a good // stack to debug diff --git a/src/Calculator/Calculator.csproj b/src/Calculator/Calculator.csproj index b92f52fd..236428aa 100644 --- a/src/Calculator/Calculator.csproj +++ b/src/Calculator/Calculator.csproj @@ -144,6 +144,7 @@ + diff --git a/src/Calculator/Common/LaunchArguments.cs b/src/Calculator/Common/LaunchArguments.cs new file mode 100644 index 00000000..80d4cc26 --- /dev/null +++ b/src/Calculator/Common/LaunchArguments.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; + +using Windows.ApplicationModel.Activation; +using Windows.ApplicationModel.UserActivities; + +namespace CalculatorApp +{ + internal class SnapshotLaunchArguments + { + public string ActivityId { get; set; } + public Uri LaunchUri { get; set; } + } + + internal static class LaunchExtensions + { + public static bool IsSnapshotProtocol(this IActivatedEventArgs args) => + args is IProtocolActivatedEventArgs protoArgs && + protoArgs.Uri != null && + protoArgs.Uri.AbsolutePath == "/snapshot" && + !string.IsNullOrEmpty(protoArgs.Uri.Query); + + /// + /// GetActivityId() requires the parameter `launchUri` to be a well-formed + /// snapshot URI. + /// + /// + /// + public static string GetActivityId(this Uri launchUri) + { + const string ActivityIdKey = "activityId="; + var segment = launchUri.Query.Split('?', '&').FirstOrDefault(x => x.StartsWith(ActivityIdKey)); + if (segment != null) + { + segment = segment.Trim(); + return segment.Length > ActivityIdKey.Length ? + segment.Substring(ActivityIdKey.Length) : + string.Empty; + } + return string.Empty; + } + + public static bool VerifyIncomingActivity(this SnapshotLaunchArguments launchArgs, UserActivity activity) + { + if (string.IsNullOrEmpty(activity.ActivityId) || + activity.ActivationUri == null || + activity.ActivationUri.AbsolutePath != "/snapshot" || + string.IsNullOrEmpty(activity.ActivationUri.Query) || + activity.ContentInfo == null) + { + return false; + } + return activity.ActivityId == GetActivityId(launchArgs.LaunchUri); + } + } +} diff --git a/src/Calculator/Views/MainPage.xaml.cs b/src/Calculator/Views/MainPage.xaml.cs index bd4a56c4..c774e7e4 100644 --- a/src/Calculator/Views/MainPage.xaml.cs +++ b/src/Calculator/Views/MainPage.xaml.cs @@ -1,13 +1,9 @@ -using CalculatorApp.Common; -using CalculatorApp.Converters; -using CalculatorApp.ViewModel; -using CalculatorApp.ViewModel.Common; -using CalculatorApp.ViewModel.Common.Automation; - using System; using System.Collections.Generic; using System.ComponentModel; +using Windows.ApplicationModel.UserActivities; +using Windows.Data.Json; using Windows.Foundation; using Windows.Graphics.Display; using Windows.Storage; @@ -15,19 +11,19 @@ using Windows.UI.Core; using Windows.UI.ViewManagement; using Windows.UI.Xaml; using Windows.UI.Xaml.Automation; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Navigation; +using Microsoft.UI.Xaml.Controls; -using MUXC = Microsoft.UI.Xaml.Controls; +using CalculatorApp.Common; +using CalculatorApp.Converters; +using CalculatorApp.ViewModel; +using CalculatorApp.ViewModel.Common; +using CalculatorApp.ViewModel.Common.Automation; namespace CalculatorApp { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// - public sealed partial class MainPage : Page + public sealed partial class MainPage : Windows.UI.Xaml.Controls.Page { public static readonly DependencyProperty NavViewCategoriesSourceProperty = DependencyProperty.Register(nameof(NavViewCategoriesSource), typeof(List), typeof(MainPage), new PropertyMetadata(default)); @@ -59,6 +55,30 @@ namespace CalculatorApp DisplayInformation.AutoRotationPreferences = DisplayOrientations.Portrait | DisplayOrientations.PortraitFlipped; } } + + UserActivityRequestManager.GetForCurrentView().UserActivityRequested += async (_, args) => + { + var deferral = args.GetDeferral(); + if (deferral == null) + { + // Windows Bug in ni_moment won't return the deferral propoerly, see https://microsoft.visualstudio.com/DefaultCollection/OS/_workitems/edit/47775705/ + return; + } + var channel = UserActivityChannel.GetDefault(); + var activity = await channel.GetOrCreateUserActivityAsync($"{Guid.NewGuid()}"); + activity.ActivationUri = new Uri($"ms-calculator:///snapshot?activityId={activity.ActivityId}"); + + var snapshot = "{}"; // TODO: serialize the current snapshot into a JSON representation string. + activity.ContentInfo = UserActivityContentInfo.FromJson(snapshot); + + var resProvider = AppResourceProvider.GetInstance(); + activity.VisualElements.DisplayText = + $"{resProvider.GetResourceString("AppName")} - {resProvider.GetResourceString(NavCategoryStates.GetNameResourceKey(Model.Mode))}"; + + await activity.SaveAsync(); + args.Request.SetUserActivity(activity); + deferral.Complete(); + }; } public void UnregisterEventHandlers() @@ -121,23 +141,59 @@ namespace CalculatorApp protected override void OnNavigatedTo(NavigationEventArgs e) { - ViewMode initialMode = ViewMode.Standard; - - string stringParameter = (e.Parameter as string); - if (!string.IsNullOrEmpty(stringParameter)) + var initialMode = ViewMode.Standard; + var localSettings = ApplicationData.Current.LocalSettings; + if (localSettings.Values.ContainsKey(ApplicationViewModel.ModePropertyName)) { - initialMode = (ViewMode)Convert.ToInt32(stringParameter); + initialMode = NavCategoryStates.Deserialize(localSettings.Values[ApplicationViewModel.ModePropertyName]); + } + + if (e.Parameter == null) + { + Model.Initialize(initialMode); + return; + } + + if (e.Parameter is string legacyArgs) + { + if (legacyArgs.Length > 0) + { + initialMode = (ViewMode)Convert.ToInt32(legacyArgs); + } + Model.Initialize(initialMode); + } + else if (e.Parameter is SnapshotLaunchArguments snapshotArgs) + { + _ = Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + var channel = UserActivityChannel.GetDefault(); + var activity = await channel.GetOrCreateUserActivityAsync(snapshotArgs.ActivityId); + if (!snapshotArgs.VerifyIncomingActivity(activity)) + { + // something's going wrong with the activity + // TODO: show error dialog + return; + } + else + { + if (JsonObject.TryParse(activity.ContentInfo.ToJson(), out var jsonModel)) + { + // TODO: try restore the model from jsonModel + } + else + { + // data corrupted + // TODO: show error dialog + return; + } + } + }); + Model.Initialize(initialMode); } else { - ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; - if (localSettings.Values.ContainsKey(ApplicationViewModel.ModePropertyName)) - { - initialMode = NavCategoryStates.Deserialize(localSettings.Values[ApplicationViewModel.ModePropertyName]); - } + Environment.FailFast("cd75d5af-0f47-4cc2-910c-ed792ed16fe6"); } - - Model.Initialize(initialMode); } private void InitializeNavViewCategoriesSource() @@ -302,13 +358,13 @@ namespace CalculatorApp NavView.SetValue(KeyboardShortcutManager.VirtualKeyControlChordProperty, MyVirtualKey.E); } - private void OnNavPaneOpened(MUXC.NavigationView sender, object args) + private void OnNavPaneOpened(NavigationView sender, object args) { KeyboardShortcutManager.HonorShortcuts(false); TraceLogger.GetInstance().LogNavBarOpened(); } - private void OnNavPaneClosed(MUXC.NavigationView sender, object args) + private void OnNavPaneClosed(NavigationView sender, object args) { if (Popup.IsOpen) { @@ -360,7 +416,7 @@ namespace CalculatorApp KeyboardShortcutManager.HonorShortcuts(!NavView.IsPaneOpen); } - private void OnNavSelectionChanged(object sender, MUXC.NavigationViewSelectionChangedEventArgs e) + private void OnNavSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e) { if (e.IsSettingsSelected) { @@ -368,13 +424,13 @@ namespace CalculatorApp return; } - if (e.SelectedItemContainer is MUXC.NavigationViewItem item) + if (e.SelectedItemContainer is NavigationViewItem item) { Model.Mode = (ViewMode)item.Tag; } } - private void OnNavItemInvoked(MUXC.NavigationView sender, MUXC.NavigationViewItemInvokedEventArgs e) + private void OnNavItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs e) { NavView.IsPaneOpen = false; }