From 2e32116eb5026fdd15b2c6ceac4f864b07065d65 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Mon, 19 Oct 2020 19:34:18 +0200 Subject: [PATCH] feat(ContactPicker): Pick single contact on Tizen --- .../Contacts/ContactPickerExtension.cs | 180 ++++++++++++++++++ .../Tizen/Helpers/Privileges.cs | 9 + .../Tizen/Helpers/PrivilegesHelper.cs | 54 ++++++ .../Contacts/ContactPicker.skia.cs | 41 ++++ .../Contacts/ContactPicker.unsupported.cs | 4 +- 5 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 src/Uno.UI.Runtime.Skia.Tizen/Tizen/ApplicationModel/Contacts/ContactPickerExtension.cs create mode 100644 src/Uno.UI.Runtime.Skia.Tizen/Tizen/Helpers/Privileges.cs create mode 100644 src/Uno.UI.Runtime.Skia.Tizen/Tizen/Helpers/PrivilegesHelper.cs create mode 100644 src/Uno.UWP/ApplicationModel/Contacts/ContactPicker.skia.cs diff --git a/src/Uno.UI.Runtime.Skia.Tizen/Tizen/ApplicationModel/Contacts/ContactPickerExtension.cs b/src/Uno.UI.Runtime.Skia.Tizen/Tizen/ApplicationModel/Contacts/ContactPickerExtension.cs new file mode 100644 index 000000000000..3c063bc6cf49 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.Tizen/Tizen/ApplicationModel/Contacts/ContactPickerExtension.cs @@ -0,0 +1,180 @@ +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Tizen.Applications; +using Tizen.Pims.Contacts; +using Windows.ApplicationModel.Contacts; +using Uno.UI.Runtime.Skia.Tizen.Helpers; +using System; +using TizenContact = Tizen.Pims.Contacts.ContactsViews.Contact; +using TizenEmail = Tizen.Pims.Contacts.ContactsViews.Email; +using TizenName = Tizen.Pims.Contacts.ContactsViews.Name; +using TizenNumber = Tizen.Pims.Contacts.ContactsViews.Number; +using TizenAddress = Tizen.Pims.Contacts.ContactsViews.Address; + +namespace Uno.UI.Runtime.Skia.Tizen.ApplicationModel.Contacts +{ + public class ContactPickerExtension : IContactPickerExtension + { + private const string ContactMimeType = "application/vnd.tizen.contact"; + + public Task IsSupportedAsync() => Task.FromResult(true); + + public async Task PickContactAsync() + { + var results = await PickContactsAsync(false); + return results.FirstOrDefault(); + } + + private async Task PickContactsAsync(bool pickMultiple) + { + if (!await PrivilegesHelper.RequestAsync(Privileges.ContactsRead)) + { + return Array.Empty(); + } + + var completionSource = new TaskCompletionSource(); + + var appControl = new AppControl + { + Operation = AppControlOperations.Pick, + LaunchMode = AppControlLaunchMode.Single, + Mime = ContactMimeType + }; + appControl.ExtraData.Add(AppControlData.SectionMode, pickMultiple ? "multiple" : "single"); + + AppControl.SendLaunchRequest(appControl, (request, reply, pickResult) => + { + var results = new List(); + + if (pickResult == AppControlReplyResult.Succeeded) + { + var contactsManager = new ContactsManager(); + + var pickedContacts = reply.ExtraData.Get>(AppControlData.Selected); + + foreach (var pickedContactId in pickedContacts) + { + if (int.TryParse(pickedContactId, out var contactId)) + { + var tizenContact = contactsManager.Database.Get(TizenContact.Uri, contactId); + if (tizenContact != null) + { + var contact = ContactFromContactsRecord(tizenContact); + results.Add(contact); + } + } + } + } + + completionSource.TrySetResult(results.ToArray()); + }); + + return await completionSource.Task; + } + + private static Contact ContactFromContactsRecord(ContactsRecord contactsRecord) + { + var contact = new Contact(); + + var recordName = contactsRecord.GetChildRecord(TizenContact.Name, 0); + if (recordName != null) + { + contact.HonorificNamePrefix = recordName.Get(TizenName.Prefix) ?? string.Empty; + contact.FirstName = recordName.Get(TizenName.First) ?? string.Empty; + contact.MiddleName = recordName.Get(TizenName.Addition) ?? string.Empty; + contact.LastName = recordName.Get(TizenName.Last) ?? string.Empty; + contact.HonorificNameSuffix = recordName.Get(TizenName.Suffix) ?? string.Empty; + + contact.YomiGivenName = recordName.Get(TizenName.PhoneticFirst) ?? string.Empty; + contact.YomiFamilyName = recordName.Get(TizenName.PhoneticLast) ?? string.Empty; + } + + var emailCount = contactsRecord.GetChildRecordCount(TizenContact.Email); + for (var mailId = 0; mailId < emailCount; mailId++) + { + var emailRecord = contactsRecord.GetChildRecord(TizenContact.Email, mailId); + var address = emailRecord.Get(TizenEmail.Address); + var type = (TizenEmail.Types)emailRecord.Get(TizenEmail.Type); + + contact.Emails.Add(new ContactEmail() + { + Address = address, + Kind = GetContactEmailKind(type) + }); + } + + var phoneCount = contactsRecord.GetChildRecordCount(TizenContact.Number); + for (var phoneId = 0; phoneId < phoneCount; phoneId++) + { + var phoneRecord = contactsRecord.GetChildRecord(TizenContact.Number, phoneId); + var number = phoneRecord.Get(TizenNumber.NumberData); + var type = (TizenNumber.Types)phoneRecord.Get(TizenNumber.Type); + + contact.Phones.Add(new ContactPhone() + { + Number = number, + Kind = GetContactPhoneKind(type) + }); + } + + var addressCount = contactsRecord.GetChildRecordCount(TizenContact.Address); + for (var addressId = 0; addressId < addressCount; addressId++) + { + var addressRecord = contactsRecord.GetChildRecord(TizenContact.Address, addressId); + var country = addressRecord.Get(TizenAddress.Country); + var locality = addressRecord.Get(TizenAddress.Locality); + var street = addressRecord.Get(TizenAddress.Street); + var region = addressRecord.Get(TizenAddress.Region); + var postalCode = addressRecord.Get(TizenAddress.PostalCode); + + var type = (TizenAddress.Types)addressRecord.Get(TizenAddress.Type); + + contact.Addresses.Add(new ContactAddress() + { + Country = country ?? string.Empty, + Locality = locality ?? string.Empty, + PostalCode = postalCode ?? string.Empty, + Region = region ?? string.Empty, + StreetAddress = street ?? string.Empty, + Kind = GetContactAddressKind(type) + }); + } + + return contact; + } + + private static ContactEmailKind GetContactEmailKind(TizenEmail.Types emailType) + => emailType switch + { + TizenEmail.Types.Home => ContactEmailKind.Personal, + TizenEmail.Types.Mobile => ContactEmailKind.Personal, + TizenEmail.Types.Work => ContactEmailKind.Work, + _ => ContactEmailKind.Other + }; + + private static ContactPhoneKind GetContactPhoneKind(TizenNumber.Types numberType) + => numberType switch + { + TizenNumber.Types.Cell => ContactPhoneKind.Mobile, + TizenNumber.Types.Main => ContactPhoneKind.Mobile, + TizenNumber.Types.Home => ContactPhoneKind.Home, + TizenNumber.Types.Pager => ContactPhoneKind.Pager, + TizenNumber.Types.Assistant => ContactPhoneKind.Assistant, + TizenNumber.Types.Company => ContactPhoneKind.Company, + TizenNumber.Types.Fax => ContactPhoneKind.BusinessFax, + TizenNumber.Types.Radio => ContactPhoneKind.Radio, + _ => ContactPhoneKind.Other + }; + + private static ContactAddressKind GetContactAddressKind(TizenAddress.Types addressType) => + addressType switch + { + TizenAddress.Types.Home => ContactAddressKind.Home, + TizenAddress.Types.Work => ContactAddressKind.Work, + _ => ContactAddressKind.Other + }; + } +} diff --git a/src/Uno.UI.Runtime.Skia.Tizen/Tizen/Helpers/Privileges.cs b/src/Uno.UI.Runtime.Skia.Tizen/Tizen/Helpers/Privileges.cs new file mode 100644 index 000000000000..11555d490d87 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.Tizen/Tizen/Helpers/Privileges.cs @@ -0,0 +1,9 @@ +#nullable enable + +namespace Uno.UI.Runtime.Skia.Tizen.Helpers +{ + internal static class Privileges + { + internal const string ContactsRead = "http://tizen.org/privilege/contact.read"; + } +} diff --git a/src/Uno.UI.Runtime.Skia.Tizen/Tizen/Helpers/PrivilegesHelper.cs b/src/Uno.UI.Runtime.Skia.Tizen/Tizen/Helpers/PrivilegesHelper.cs new file mode 100644 index 000000000000..00cbafebbe21 --- /dev/null +++ b/src/Uno.UI.Runtime.Skia.Tizen/Tizen/Helpers/PrivilegesHelper.cs @@ -0,0 +1,54 @@ +#nullable enable + +using System; +using System.Linq; +using System.Threading.Tasks; +using Tizen.Applications; +using Tizen.Security; + +namespace Uno.UI.Runtime.Skia.Tizen.Helpers +{ + internal class PrivilegesHelper + { + internal static void EnsureDeclared(string privilege) + { + var packageId = Application.Current.ApplicationInfo.PackageId; + var packageManager = PackageManager.GetPackage(packageId); + + if (!packageManager.Privileges.Contains(privilege)) + { + throw new InvalidOperationException($"Privilege {privilege} must be declared in the Tizen application manifest."); + } + } + + internal static async Task RequestAsync(string privilege) + { + EnsureDeclared(privilege); + + var checkResult = PrivacyPrivilegeManager.CheckPermission(privilege); + if (checkResult == CheckResult.Ask) + { + var completionSource = new TaskCompletionSource(); + if (PrivacyPrivilegeManager.GetResponseContext(privilege).TryGetTarget(out var context)) + { + + void OnResponseFetched(object sender, RequestResponseEventArgs e) + { + completionSource.TrySetResult(e.result == RequestResult.AllowForever); + } + context.ResponseFetched += OnResponseFetched; + PrivacyPrivilegeManager.RequestPermission(privilege); + var result = await completionSource.Task; + context.ResponseFetched -= OnResponseFetched; + return true; + } + return false; + } + else if (checkResult == CheckResult.Deny) + { + return false; + } + return true; + } + } +} diff --git a/src/Uno.UWP/ApplicationModel/Contacts/ContactPicker.skia.cs b/src/Uno.UWP/ApplicationModel/Contacts/ContactPicker.skia.cs new file mode 100644 index 000000000000..1e84901dc8c9 --- /dev/null +++ b/src/Uno.UWP/ApplicationModel/Contacts/ContactPicker.skia.cs @@ -0,0 +1,41 @@ +#nullable enable + +using System.Threading.Tasks; +using Uno.Foundation.Extensibility; + +namespace Windows.ApplicationModel.Contacts +{ + public partial class ContactPicker + { + private static IContactPickerExtension? _contactPickerExtension; + + static ContactPicker() + { + if (_contactPickerExtension == null) + { + ApiExtensibility.CreateInstance(typeof(ContactPicker), out _contactPickerExtension); + } + } + + private static async Task IsSupportedTaskAsync() + { + return _contactPickerExtension != null && await _contactPickerExtension.IsSupportedAsync(); + } + + private async Task PickContactTaskAsync() + { + if (_contactPickerExtension != null) + { + return await _contactPickerExtension.PickContactAsync(); + } + return null; + } + } + + internal interface IContactPickerExtension + { + Task IsSupportedAsync(); + + Task PickContactAsync(); + } +} diff --git a/src/Uno.UWP/ApplicationModel/Contacts/ContactPicker.unsupported.cs b/src/Uno.UWP/ApplicationModel/Contacts/ContactPicker.unsupported.cs index 1dabcfba6be3..ef30712ad496 100644 --- a/src/Uno.UWP/ApplicationModel/Contacts/ContactPicker.unsupported.cs +++ b/src/Uno.UWP/ApplicationModel/Contacts/ContactPicker.unsupported.cs @@ -1,11 +1,11 @@ -#if NET461 || __SKIA__ || __NETSTD_REFERENCE__ +#if NET461 || __NETSTD_REFERENCE__ #nullable enable namespace Windows.ApplicationModel.Contacts { public partial class ContactPicker { - private static Task IsSupportedTaskAsync() => Task.FromResult(false); + private static Task IsSupportedTaskAsync => Task.FromResult(false); private Task PickContactTaskAsync() => Task.FromResult(null); }