Skip to content

Commit

Permalink
feat: DataTransferManager support on iOS
Browse files Browse the repository at this point in the history
- Adds support for DataTransferManager on iOS
- Includes ShareUIOptions for target rect and dark/light theme
  • Loading branch information
MartinZikmund committed Jan 29, 2021
1 parent 97ccc3f commit bfe6ec9
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
<RowDefinition Height="*" />
<RowDefinition Height="300" />
</Grid.RowDefinitions>
<StackPanel>
<StackPanel Spacing="8">
<TextBox Header="Title" Text="{x:Bind ViewModel.Title, Mode=TwoWay}" />
<TextBox Header="Description" Text="{x:Bind ViewModel.Description, Mode=TwoWay}" />
<CheckBox x:Name="DarkThemeCheckBox" IsChecked="{x:Bind ViewModel.SetDarkTheme, Mode=TwoWay}" IsThreeState="True" Content="Dark theme" />

<TextBlock Text="Data" Style="{ThemeResource HeaderTextBlockStyle}" />
<TextBox Header="Text" Text="{x:Bind ViewModel.Text, Mode=TwoWay}" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class DataTransferManagerTestsViewModel : ViewModelBase
private string _uriText = null;
private string _applicationLink = null;
private string _webLink = null;
private bool? _setDarkTheme = null;

public DataTransferManagerTestsViewModel(CoreDispatcher dispatcher) : base(dispatcher)
{
Expand Down Expand Up @@ -76,6 +77,16 @@ public string Description
}
}

public bool? SetDarkTheme
{
get => _setDarkTheme;
set
{
_setDarkTheme = value;
RaisePropertyChanged();
}
}

public string Text
{
get => _text;
Expand Down Expand Up @@ -116,7 +127,26 @@ public string WebLink
}
}

private void ShowUI() => DataTransferManager.ShowShareUI();
private void ShowUI() => DataTransferManager.ShowShareUI(new ShareUIOptions()
{
Theme = GetTheme()
});

private ShareUITheme GetTheme()
{
if (SetDarkTheme == null)
{
return ShareUITheme.Default;
}
else if (SetDarkTheme == true)
{
return ShareUITheme.Dark;
}
else
{
return ShareUITheme.Light;
}
}

private void ClearEventLog() => EventLog.Clear();

Expand Down
2 changes: 2 additions & 0 deletions src/Uno.UI/UI/Xaml/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ private ApplicationTheme? InternalRequestedTheme
set
{
_requestedTheme = value;
// Sync with core application's theme
CoreApplication.RequestedTheme = value == ApplicationTheme.Dark ? SystemTheme.Dark : SystemTheme.Light;
UpdateRequestedThemesForResources();
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Uno.UWP/ApplicationModel/Core/CoreApplication.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma warning disable 108 // new keyword hiding
#pragma warning disable 114 // new keyword hiding
using System.Collections.Generic;
using Uno.Helpers.Theming;

namespace Windows.ApplicationModel.Core
{
Expand Down Expand Up @@ -48,5 +49,11 @@ public static IReadOnlyList<CoreApplicationView> Views
return _views;
}
}

/// <summary>
/// This property is kept in sync with the Application.RequestedTheme to enable
/// native UI elements in non Uno.UWP to resolve the currently set Application theme.
/// </summary>
internal static SystemTheme RequestedTheme { get; set; }
}
}
57 changes: 55 additions & 2 deletions src/Uno.UWP/ApplicationModel/DataTransfer/DataTransferManager.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,68 @@
#if __WASM__
#nullable enable

#if __WASM__ || __IOS__
using System;
using Windows.Foundation;
using Uno.Logging;
using Microsoft.Extensions.Logging;
using Uno.Extensions;
using System.Threading.Tasks;

namespace Windows.ApplicationModel.DataTransfer
{
public partial class DataTransferManager
{
private static Lazy<DataTransferManager> _instance = new Lazy<DataTransferManager>(() => new DataTransferManager());

private DataTransferManager()
{
}

public event TypedEventHandler<DataTransferManager, DataRequestedEventArgs> DataRequested;
public event TypedEventHandler<DataTransferManager, DataRequestedEventArgs>? DataRequested;

public static DataTransferManager GetForCurrentView() => _instance.Value;

public static void ShowShareUI() => ShowShareUI(new ShareUIOptions());

public static async void ShowShareUI(ShareUIOptions options)
{
var dataTransferManager = _instance.Value;
var args = new DataRequestedEventArgs();
dataTransferManager.DataRequested?.Invoke(dataTransferManager, args);
var dataPackage = args.Request.Data;
try
{
// Because showing the Share UI is a fire-and-forget operation
// and retrieving data from DataPackage requires async-await,
// this method must be async void.
await ShowShareUIAsync(options, dataPackage);
}
catch (Exception ex)
{
if (dataTransferManager.Log().IsEnabled(LogLevel.Error))
{
dataTransferManager.Log().LogError($"Exception occurred trying to show share UI: {ex}");
}
}
}

private static async Task<Uri?> GetSharedUriAsync(DataPackageView view)
{
if (view.Contains(StandardDataFormats.Uri))
{
return await view.GetUriAsync();
}
else if (view.Contains(StandardDataFormats.WebLink))
{
return await view.GetWebLinkAsync();
}
else if (view.Contains(StandardDataFormats.ApplicationLink))
{
return await view.GetApplicationLinkAsync();
}

return null;
}
}
}
#endif
114 changes: 114 additions & 0 deletions src/Uno.UWP/ApplicationModel/DataTransfer/DataTransferManager.iOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CoreGraphics;
using Foundation;
using Microsoft.Extensions.Logging;
using UIKit;
using Uno.Extensions;
using Uno.Helpers.Theming;
using Windows.ApplicationModel.Core;
using Windows.Foundation;

namespace Windows.ApplicationModel.DataTransfer
{
public partial class DataTransferManager
{
public static bool IsSupported() => true;

private static async Task ShowShareUIAsync(ShareUIOptions options, DataPackage dataPackage)
{
var rootViewController = UIApplication.SharedApplication?.KeyWindow?.RootViewController;
if (rootViewController == null)
{
if (_instance.Value.Log().IsEnabled(LogLevel.Error))
{
_instance.Value.Log().LogError("The Share API was called too early in the application lifecycle");
}
return;
}

var dataPackageView = dataPackage.GetView();

var sharedData = new List<NSObject>();

var title = dataPackage.Properties.Title ?? string.Empty;

if (dataPackageView.Contains(StandardDataFormats.Text))
{
var text = await dataPackageView.GetTextAsync();
sharedData.Add(new DataActivityItemSource(new NSString(text), title));
}

var uri = await GetSharedUriAsync(dataPackageView);
if (uri != null)
{
sharedData.Add(new DataActivityItemSource(NSUrl.FromString(uri.ToString()), title));
}

var activityViewController = new UIActivityViewController(sharedData.ToArray(), null);

if (activityViewController.PopoverPresentationController != null && rootViewController.View != null)
{
activityViewController.PopoverPresentationController.SourceView = rootViewController.View;

if (options.SelectionRect != null)
{
activityViewController.PopoverPresentationController.SourceRect = options.SelectionRect.Value.ToCGRect();
}
else
{
activityViewController.PopoverPresentationController.SourceRect = new CGRect(rootViewController.View.Bounds.Width / 2, rootViewController.View.Bounds.Height / 2, 0, 0);
activityViewController.PopoverPresentationController.PermittedArrowDirections = 0;
}
}

if (options.Theme != ShareUITheme.Default)
{
activityViewController.OverrideUserInterfaceStyle = options.Theme == ShareUITheme.Light ? UIUserInterfaceStyle.Light : UIUserInterfaceStyle.Dark;
}
else
{
// Theme should match the application theme
activityViewController.OverrideUserInterfaceStyle = CoreApplication.RequestedTheme == SystemTheme.Light ? UIUserInterfaceStyle.Light : UIUserInterfaceStyle.Dark;
}

var completionSource = new TaskCompletionSource<bool>();

activityViewController.CompletionWithItemsHandler = (NSString activityType, bool completed, NSExtensionItem[] returnedItems, NSError error) =>
{
completionSource.SetResult(completed);
};

await rootViewController.PresentViewControllerAsync(activityViewController, true);

var result = await completionSource.Task;

if (result)
{
dataPackage.OnShareCompleted();
}
else
{
dataPackage.OnShareCanceled();
}
}

internal class DataActivityItemSource : UIActivityItemSource
{
private NSObject _data;
private string _title;

internal DataActivityItemSource(NSObject data, string title) =>
(_data, _title) = (data, title);

public override NSObject GetItemForActivity(UIActivityViewController activityViewController, NSString? activityType) => _data;

public override string GetSubjectForActivity(UIActivityViewController activityViewController, NSString? activityType) => _title;

public override NSObject GetPlaceholderData(UIActivityViewController activityViewController) => _data;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#if !__WASM__
#if !__WASM__ && !__IOS__

namespace Windows.ApplicationModel.DataTransfer
{
public partial class DataTransferManager
{
private DataTransferManager()
{
}

public static bool IsSupported() => false;

public static DataTransferManager GetForCurrentView() => throw new NotSupportedException("DataTransferManager is not yet supported on this platform.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
#nullable enable

using System;
using System.Threading.Tasks;
using Uno.Foundation;

namespace Windows.ApplicationModel.DataTransfer
{
public partial class DataTransferManager
{
private const string JsType = "Windows.ApplicationModel.DataTransfer.DataTransferManager";

private static Lazy<DataTransferManager> _instance = new Lazy<DataTransferManager>(() => new DataTransferManager());


public static bool IsSupported() => bool.TryParse(WebAssemblyRuntime.InvokeJS($"{JsType}.isSupported()"), out var result) && result;

public static DataTransferManager GetForCurrentView() => _instance.Value;

public static async void ShowShareUI()
private static async Task ShowShareUIAsync(ShareUIOptions options, DataPackage dataPackage)
{
var dataTransferManager = _instance.Value;
var args = new DataRequestedEventArgs();
dataTransferManager.DataRequested?.Invoke(dataTransferManager, args);
var dataPackage = args.Request.Data;
var dataPackageView = args.Request.Data.GetView();
var dataPackageView = dataPackage.GetView();

var title = dataPackage.Properties.Title != null ? $"\"{WebAssemblyRuntime.EscapeJs(dataPackage.Properties.Title)}\"" : null;

Expand All @@ -36,19 +29,7 @@ public static async void ShowShareUI()
}
text = text != null ? $"\"{WebAssemblyRuntime.EscapeJs(text)}\"" : null;

Uri? uri = null;
if (dataPackageView.Contains(StandardDataFormats.Uri))
{
uri = await dataPackageView.GetUriAsync();
}
else if (dataPackageView.Contains(StandardDataFormats.WebLink))
{
uri = await dataPackageView.GetWebLinkAsync();
}
else if (dataPackageView.Contains(StandardDataFormats.ApplicationLink))
{
uri = await dataPackageView.GetApplicationLinkAsync();
}
var uri = await GetSharedUriAsync(dataPackageView);

var uriText = uri != null ? $"\"{WebAssemblyRuntime.EscapeJs(uri.ToString())}\"" : null;

Expand Down
18 changes: 18 additions & 0 deletions src/Uno.UWP/ApplicationModel/DataTransfer/ShareUIOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Windows.Foundation;

namespace Windows.ApplicationModel.DataTransfer
{
public partial class ShareUIOptions
{
public ShareUIOptions()
{
}

#if __IOS__

public ShareUITheme Theme { get; set; }

public Rect? SelectionRect { get; set; }
#endif
}
}
9 changes: 9 additions & 0 deletions src/Uno.UWP/ApplicationModel/DataTransfer/ShareUITheme.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Windows.ApplicationModel.DataTransfer
{
public enum ShareUITheme
{
Default,
Light,
Dark,
}
}
Loading

0 comments on commit bfe6ec9

Please sign in to comment.