- No more URI app launching required
- Sample doesn't use AppLinks
- AppAction model is simplified
This commit is contained in:
Jonathan Dick 2020-08-20 15:11:32 -04:00
Родитель b5c749fed4
Коммит 435d149e82
17 изменённых файлов: 256 добавлений и 126 удалений

Просмотреть файл

@ -10,12 +10,8 @@ namespace Samples.Droid
{
[Activity(Label = "@string/app_name", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[IntentFilter(
new[] { Intent.ActionView },
Categories = new[] { Intent.CategoryDefault },
DataScheme = "xam")] // Shortcuts with a xam:// Uri (Automatically passed to Xamarin.Forms OnAppLinkRequestReceived()
[IntentFilter(
new[] { "battery" },
Categories = new[] { Intent.CategoryDefault })] // Shortcuts without a Uri
new[] { Xamarin.Essentials.Platform.Intent.ActionAppAction },
Categories = new[] { Intent.CategoryDefault })]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
@ -32,6 +28,8 @@ namespace Samples.Droid
Xamarin.Essentials.Platform.ActivityStateChanged += Platform_ActivityStateChanged;
LoadApplication(new App());
Xamarin.Essentials.Platform.OnCreate(this);
}
protected override void OnResume()
@ -39,19 +37,19 @@ namespace Samples.Droid
base.OnResume();
Xamarin.Essentials.Platform.OnResume();
}
// Handle shortcuts without a Uri here
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
if (Intent?.Action == "battery")
{
Xamarin.Forms.Application.Current.MainPage.Navigation.PopToRootAsync();
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new BatteryPage());
}
Xamarin.Essentials.Platform.OnNewIntent(intent);
}
protected override void OnDestroy()
{
base.OnDestroy();
Xamarin.Essentials.Platform.ActivityStateChanged -= Platform_ActivityStateChanged;
}

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Text;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
@ -14,13 +15,6 @@ namespace Samples.UWP
{
InitializeComponent();
Suspending += OnSuspending;
MainThread.BeginInvokeOnMainThread(() =>
{
Console.WriteLine("Success!");
});
var test = DeviceInfo.DeviceType;
}
protected override void OnLaunched(LaunchActivatedEventArgs e)
@ -57,6 +51,8 @@ namespace Samples.UWP
// Ensure the current window is active
Window.Current.Activate();
Xamarin.Essentials.Platform.OnLaunched(e);
}
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)

Двоичные данные
Samples/Samples.UWP/Assets/app_info_action_icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.7 KiB

Двоичные данные
Samples/Samples.UWP/Assets/battery_action_icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.9 KiB

Просмотреть файл

@ -13,6 +13,7 @@ namespace Samples.iOS
{
Xamarin.Forms.Forms.Init();
Xamarin.Forms.FormsMaterial.Init();
Distribute.DontCheckForUpdatesInDebug();
LoadApplication(new App());
@ -28,23 +29,6 @@ namespace Samples.iOS
}
public override void PerformActionForShortcutItem(UIApplication application, UIApplicationShortcutItem shortcutItem, UIOperationHandler completionHandler)
{
var uri = shortcutItem.UserInfo?["uri"] as NSString;
if (!string.IsNullOrWhiteSpace(uri))
{
Xamarin.Forms.Application.Current.SendOnAppLinkRequestReceived(new Uri(uri));
}
else
{
// Handle shortcuts without a Uri here
if (shortcutItem.Type == "battery")
{
Xamarin.Forms.Application.Current.MainPage.Navigation.PopToRootAsync();
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new BatteryPage());
}
}
}
=> Xamarin.Essentials.Platform.PerformActionForShortcutItem(application, shortcutItem, completionHandler);
}
}

Двоичные данные
Samples/Samples.iOS/Resources/app_info_action_icon@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.7 KiB

Двоичные данные
Samples/Samples.iOS/Resources/battery_action_icon@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.9 KiB

Просмотреть файл

@ -118,14 +118,30 @@
<InterfaceDefinition Include="Resources\LaunchScreen.storyboard" />
</ItemGroup>
<ItemGroup>
<ImageAsset Include="Assets.xcassets\battery_action_icon.imageset\Contents.json" />
<ImageAsset Include="Assets.xcassets\battery_action_icon.imageset\battery_action_icon.png" />
<ImageAsset Include="Assets.xcassets\battery_action_icon.imageset\battery_action_icon%402x.png" />
<ImageAsset Include="Assets.xcassets\battery_action_icon.imageset\battery_action_icon%403x.png" />
<ImageAsset Include="Assets.xcassets\app_info_action_icon.imageset\Contents.json" />
<ImageAsset Include="Assets.xcassets\app_info_action_icon.imageset\app_info_action_icon.png" />
<ImageAsset Include="Assets.xcassets\app_info_action_icon.imageset\app_info_action_icon%402x.png" />
<ImageAsset Include="Assets.xcassets\app_info_action_icon.imageset\app_info_action_icon%403x.png" />
<ImageAsset Include="Assets.xcassets\battery_action_icon.imageset\Contents.json">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\battery_action_icon.imageset\battery_action_icon.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\battery_action_icon.imageset\battery_action_icon%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\battery_action_icon.imageset\battery_action_icon%403x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\app_info_action_icon.imageset\Contents.json">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\app_info_action_icon.imageset\app_info_action_icon.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\app_info_action_icon.imageset\app_info_action_icon%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\app_info_action_icon.imageset\app_info_action_icon%403x.png">
<Visible>false</Visible>
</ImageAsset>
</ItemGroup>
<ItemGroup>
<Folder Include="Assets.xcassets\battery_action_icon.imageset\" />

Просмотреть файл

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
@ -22,16 +23,14 @@ namespace Samples
{
InitializeComponent();
AddAppActions();
// Enable currently experimental features
VersionTracking.Track();
MainPage = new NavigationPage(new HomePage());
AppActions.OnAppAction += AppActions_OnAppAction;
}
protected override void OnStart()
protected override async void OnStart()
{
if ((Device.RuntimePlatform == Device.Android && CommonConstants.AppCenterAndroid != "AC_ANDROID") ||
(Device.RuntimePlatform == Device.iOS && CommonConstants.AppCenteriOS != "AC_IOS") ||
@ -45,6 +44,29 @@ namespace Samples
typeof(Crashes),
typeof(Distribute));
}
await AppActions.SetAsync(
new AppAction("App Info", id: "app_info", icon: "app_info_action_icon"),
new AppAction("Battery Info", id: "battery_info"));
}
void AppActions_OnAppAction(object sender, AppActionEventArgs e)
{
Device.BeginInvokeOnMainThread(async () =>
{
var page = e.AppAction.Id switch
{
"battery_info" => new BatteryPage(),
"app_info" => new AppInfoPage(),
_ => default(Page)
};
if (page != null)
{
await Xamarin.Forms.Application.Current.MainPage.Navigation.PopToRootAsync();
await Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(page);
}
});
}
protected override void OnSleep()
@ -56,40 +78,5 @@ namespace Samples
{
// Handle when your app resumes
}
protected override void OnAppLinkRequestReceived(Uri uri)
{
if (uri?.Scheme != "xam")
{
base.OnAppLinkRequestReceived(uri);
return;
}
MainPage.Navigation.PopToRootAsync();
var page = uri.Segments.Last();
switch (page)
{
case "appInfo":
MainPage.Navigation.PushAsync(new AppInfoPage());
break;
case "deviceInfo":
MainPage.Navigation.PushAsync(new DeviceInfoPage());
break;
default:
break;
}
}
void AddAppActions()
{
AppActions.Actions = new List<AppAction>
{
new AppAction("app_info", "App Info", uri: new Uri("xam://samples/appInfo"), icon: "app_info_action_icon"),
new AppAction("device_info", "Device Info", uri: new Uri("xam://samples/deviceInfo")), // Sample without an icon
new AppAction("battery", "Battery", icon: "battery_action_icon") // Sample without a Uri - will require manually handling in AppDelegate and MainActivity
};
}
}
}

Просмотреть файл

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
@ -14,19 +15,19 @@ namespace Xamarin.Essentials
internal static bool PlatformIsSupported
=> Platform.HasApiLevelNMr1;
static IEnumerable<AppAction> PlatformGetActions()
static Task<IEnumerable<AppAction>> PlatformGetAsync()
{
if (!IsSupported)
throw new FeatureNotSupportedException();
#if __ANDROID_25__
return Platform.ShortcutManager.DynamicShortcuts.Select(s => s.ToAppAction());
return Task.FromResult(Platform.ShortcutManager.DynamicShortcuts.Select(s => s.ToAppAction()));
#else
return null;
return Task.FromResult<IEnumerable<AppAction>>>(null);
#endif
}
static void PlatformSetActions(IEnumerable<AppAction> actions)
static Task PlatformSetAsync(IEnumerable<AppAction> actions)
{
if (!IsSupported)
throw new FeatureNotSupportedException();
@ -34,14 +35,27 @@ namespace Xamarin.Essentials
#if __ANDROID_25__
Platform.ShortcutManager.SetDynamicShortcuts(actions.Select(a => a.ToShortcutInfo()).ToList());
#endif
return Task.CompletedTask;
}
static AppAction ToAppAction(this ShortcutInfo shortcutInfo) =>
new AppAction(shortcutInfo.Id, shortcutInfo.ShortLabel, shortcutInfo.LongLabel);
new AppAction(shortcutInfo.ShortLabel, shortcutInfo.LongLabel, shortcutInfo.Id);
const string extraAppActionId = "EXTRA_XE_APP_ACTION_ID";
const string extraAppActionTitle = "EXTRA_XE_APP_ACTION_TITLE";
const string extraAppActionSubtitle = "EXTRA_XE_APP_ACTION_SUBTITLE";
const string extraAppActionIcon = "EXTRA_XE_APP_ACTION_ICON";
internal static AppAction ToAppAction(this Intent intent)
=> new AppAction(
intent.GetStringExtra(extraAppActionTitle),
intent.GetStringExtra(extraAppActionId),
intent.GetStringExtra(extraAppActionSubtitle),
intent.GetStringExtra(extraAppActionIcon));
static ShortcutInfo ToShortcutInfo(this AppAction action)
{
var shortcut = new ShortcutInfo.Builder(Platform.AppContext, action.ActionType)
var shortcut = new ShortcutInfo.Builder(Platform.AppContext, action.Id)
.SetShortLabel(action.Title);
if (!string.IsNullOrWhiteSpace(action.Subtitle))
@ -56,14 +70,15 @@ namespace Xamarin.Essentials
shortcut.SetIcon(Icon.CreateWithResource(Platform.AppContext, iconResId));
}
if (action.Uri != null)
{
shortcut.SetIntent(new Intent(Intent.ActionView, AndroidUri.Parse(action.Uri.ToString())));
}
else
{
shortcut.SetIntent(new Intent(action.ActionType));
}
var intent = new Intent(Platform.Intent.ActionAppAction);
intent.SetPackage(Platform.AppContext.PackageName);
intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
intent.PutExtra(extraAppActionId, action.Id);
intent.PutExtra(extraAppActionTitle, action.Title);
intent.PutExtra(extraAppActionSubtitle, action.Subtitle);
intent.PutExtra(extraAppActionIcon, action.Icon);
shortcut.SetIntent(intent);
return shortcut.Build();
}

Просмотреть файл

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Foundation;
using UIKit;
@ -8,34 +9,38 @@ namespace Xamarin.Essentials
{
public static partial class AppActions
{
internal const string Type = "XE_APP_ACTION_TYPE";
internal static bool PlatformIsSupported
=> Platform.HasOSVersion(9, 0);
static IEnumerable<AppAction> PlatformGetActions()
static Task<IEnumerable<AppAction>> PlatformGetAsync()
{
if (!IsSupported)
throw new FeatureNotSupportedException();
return UIApplication.SharedApplication.ShortcutItems.Select(s => s.ToAppAction());
return Task.FromResult(UIApplication.SharedApplication.ShortcutItems.Select(s => s.ToAppAction()));
}
static void PlatformSetActions(IEnumerable<AppAction> actions)
static Task PlatformSetAsync(IEnumerable<AppAction> actions)
{
if (!IsSupported)
throw new FeatureNotSupportedException();
UIApplication.SharedApplication.ShortcutItems = actions.Select(a => a.ToShortcutItem()).ToArray();
return Task.CompletedTask;
}
static AppAction ToAppAction(this UIApplicationShortcutItem shortcutItem) =>
internal static AppAction ToAppAction(this UIApplicationShortcutItem shortcutItem) =>
new AppAction(shortcutItem.Type, shortcutItem.LocalizedTitle, shortcutItem.LocalizedSubtitle);
static UIApplicationShortcutItem ToShortcutItem(this AppAction action) =>
new UIApplicationShortcutItem(
action.ActionType,
AppActions.Type,
action.Title,
action.Subtitle,
action.Icon != null ? UIApplicationShortcutIcon.FromTemplateImageName(action.Icon) : null,
action.Uri != null ? new NSDictionary<NSString, NSObject>((NSString)"uri", (NSString)action.Uri.ToString()) : null);
new NSDictionary<NSString, NSObject>((NSString)"id", (NSString)action.Id.ToString()));
}
}

Просмотреть файл

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Xamarin.Essentials
{
@ -8,10 +9,10 @@ namespace Xamarin.Essentials
internal static bool PlatformIsSupported
=> throw ExceptionUtils.NotSupportedOrImplementedException;
static IEnumerable<AppAction> PlatformGetActions() =>
static Task<IEnumerable<AppAction>> PlatformGetAsync() =>
throw ExceptionUtils.NotSupportedOrImplementedException;
static void PlatformSetActions(IEnumerable<AppAction> actions) =>
static Task PlatformSetAsync(IEnumerable<AppAction> actions) =>
throw ExceptionUtils.NotSupportedOrImplementedException;
}
}

Просмотреть файл

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Xamarin.Essentials
{
@ -8,31 +9,44 @@ namespace Xamarin.Essentials
internal static bool IsSupported
=> PlatformIsSupported;
public static IEnumerable<AppAction> Actions
{
get => PlatformGetActions();
set => PlatformSetActions(value);
}
public static Task<IEnumerable<AppAction>> GetAsync()
=> PlatformGetAsync();
public static Task SetAsync(params AppAction[] actions)
=> PlatformSetAsync(actions);
public static Task SetAsync(IEnumerable<AppAction> actions)
=> PlatformSetAsync(actions);
public static event EventHandler<AppActionEventArgs> OnAppAction;
internal static void InvokeOnAppAction(object sender, AppAction appAction)
=> OnAppAction?.Invoke(sender, new AppActionEventArgs(appAction));
}
public class AppActionEventArgs : EventArgs
{
public AppActionEventArgs(AppAction appAction)
: base() => AppAction = appAction;
public AppAction AppAction { get; }
}
public class AppAction
{
public AppAction(string actionType, string title, string subtitle = null, Uri uri = null, string icon = null)
public AppAction(string title, string id, string subtitle = null, string icon = null)
{
ActionType = actionType;
Title = title;
Subtitle = subtitle;
Icon = icon;
Uri = uri;
Id = id;
}
public string ActionType { get; set; }
public string Title { get; set; }
public string Subtitle { get; set; }
public Uri Uri { get; set; }
public string Id { get; set; }
internal string Icon { get; set; }
}

Просмотреть файл

@ -1,17 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.Activation;
using Windows.UI.StartScreen;
namespace Xamarin.Essentials
{
public static partial class AppActions
{
const string appActionPrefix = "XE_APP_ACTIONS-";
internal static bool PlatformIsSupported
=> false;
=> true;
static IEnumerable<AppAction> PlatformGetActions() =>
throw ExceptionUtils.NotSupportedOrImplementedException;
internal static async Task OnLaunched(LaunchActivatedEventArgs e)
{
if (e?.Arguments?.StartsWith(appActionPrefix) ?? false)
{
var id = ArgumentsToId(e.Arguments);
static void PlatformSetActions(IEnumerable<AppAction> actions) =>
throw ExceptionUtils.NotSupportedOrImplementedException;
if (!string.IsNullOrEmpty(id))
{
var actions = await PlatformGetAsync();
var appAction = actions.FirstOrDefault(a => a.Id == id);
if (appAction != null)
AppActions.InvokeOnAppAction(null, appAction);
}
}
}
static async Task<IEnumerable<AppAction>> PlatformGetAsync()
{
// Load existing items
var jumpList = await JumpList.LoadCurrentAsync();
var actions = new List<AppAction>();
foreach (var item in jumpList.Items)
actions.Add(item.ToAction());
return actions;
}
static async Task PlatformSetAsync(IEnumerable<AppAction> actions)
{
// Load existing items
var jumpList = await JumpList.LoadCurrentAsync();
// Set as custom, not system or frequent
jumpList.SystemGroupKind = JumpListSystemGroupKind.None;
// Clear the existing items
jumpList.Items.Clear();
// Add each action
foreach (var a in actions)
jumpList.Items.Add(a.ToJumpListItem());
// Save the changes
await jumpList.SaveAsync();
}
static AppAction ToAction(this JumpListItem item)
=> new AppAction(item.DisplayName, ArgumentsToId(item.Arguments), item.Description);
static string ArgumentsToId(string arguments)
{
if (arguments?.StartsWith(appActionPrefix) ?? false)
return System.Text.Encoding.Default.GetString(Convert.FromBase64String(arguments.Substring(appActionPrefix.Length)));
return default;
}
static JumpListItem ToJumpListItem(this AppAction action)
{
var a = appActionPrefix + Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(action.Id.ToString()));
var item = JumpListItem.CreateWithArguments(a, action.Title);
if (!string.IsNullOrEmpty(action.Subtitle))
item.Description = action.Subtitle;
// if (!string.IsNullOrEmpty(action.Icon))
// item.Logo = new Uri("ms-appx://Assets/" + action.Icon + ".png");
return item;
}
}
}

Просмотреть файл

@ -11,6 +11,8 @@ using Android.Locations;
using Android.Net;
using Android.Net.Wifi;
using Android.OS;
using Android.Util;
using AndroidIntent = Android.Content.Intent;
using AndroidUri = Android.Net.Uri;
namespace Xamarin.Essentials
@ -78,8 +80,25 @@ namespace Xamarin.Essentials
public static void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) =>
Permissions.OnRequestPermissionsResult(requestCode, permissions, grantResults);
public static void OnResume() =>
WebAuthenticator.OnResume(null);
public static void OnNewIntent(AndroidIntent intent)
=> CheckAppActions(intent);
public static void OnResume()
=> WebAuthenticator.OnResume(null);
public static void OnCreate(Activity activity)
=> CheckAppActions(activity?.Intent);
static void CheckAppActions(AndroidIntent intent)
{
if (intent?.Action == Intent.ActionAppAction)
{
var appAction = intent.ToAppAction();
if (!string.IsNullOrEmpty(appAction?.Id))
AppActions.InvokeOnAppAction(Platform.CurrentActivity, appAction);
}
}
internal static bool HasSystemFeature(string systemFeature)
{
@ -92,7 +111,7 @@ namespace Xamarin.Essentials
return false;
}
internal static bool IsIntentSupported(Intent intent)
internal static bool IsIntentSupported(AndroidIntent intent)
{
var manager = AppContext.PackageManager;
var activities = manager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly);
@ -252,6 +271,11 @@ namespace Xamarin.Essentials
resources.UpdateConfiguration(config, resources.DisplayMetrics);
#pragma warning restore CS0618 // Type or member is obsolete
}
public static class Intent
{
public const string ActionAppAction = "ACTION_XE_APP_ACTION";
}
}
public enum ActivityState

Просмотреть файл

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Foundation;
using ObjCRuntime;
using UIKit;
@ -21,6 +22,18 @@ namespace Xamarin.Essentials
=> WebAuthenticator.OpenUrl(new Uri(url.AbsoluteString));
#endif
#if __IOS__
public static void PerformActionForShortcutItem(UIApplication application, UIApplicationShortcutItem shortcutItem, UIOperationHandler completionHandler)
{
if (shortcutItem.Type == AppActions.Type)
{
var appAction = shortcutItem.ToAppAction();
AppActions.InvokeOnAppAction(application, shortcutItem.ToAppAction());
}
}
#endif
#if __IOS__
[DllImport(Constants.SystemLibrary, EntryPoint = "sysctlbyname")]
#else

Просмотреть файл

@ -9,5 +9,8 @@ namespace Xamarin.Essentials
internal const string AppManifestUapXmlns = "http://schemas.microsoft.com/appx/manifest/uap/windows10";
public static string MapServiceToken { get; set; }
public static async void OnLaunched(LaunchActivatedEventArgs e)
=> await AppActions.OnLaunched(e);
}
}