using Plugin.DeviceInfo;
using System;
using Xamarin.Essentials;
namespace TerminalMACS.Clients.App.ViewModels
{
///
/// Client base information page ViewModel
///
public class ClientInfoViewModel : BaseViewModel
{
///
/// Gets or sets the id of the application.
///
public string AppId { get; set; } = CrossDeviceInfo.Current.GenerateAppId();
///
/// Gets or sets the model of the device.
///
public string Model { get; private set; } = DeviceInfo.Model;
///
/// Gets or sets the manufacturer of the device.
///
public string Manufacturer { get; private set; } = DeviceInfo.Manufacturer;
///
/// Gets or sets the name of the device.
///
public string Name { get; private set; } = DeviceInfo.Name;
///
/// Gets or sets the version of the operating system.
///
public string VersionString { get; private set; } = DeviceInfo.VersionString;
///
/// Gets or sets the version of the operating system.
///
public Version Version { get; private set; } = DeviceInfo.Version;
///
/// Gets or sets the platform or operating system of the device.
///
public DevicePlatform Platform { get; private set; } = DeviceInfo.Platform;
///
/// Gets or sets the idiom of the device.
///
public DeviceIdiom Idiom { get; private set; } = DeviceInfo.Idiom;
///
/// Gets or sets the type of device the application is running on.
///
public DeviceType DeviceType { get; private set; } = DeviceInfo.DeviceType;
}
}
namespace TerminalMACS.Clients.App.Models
{
///
/// Contact information entity.
///
public class Contact
{
///
/// Gets or sets the name
///
public string Name { get; set; }
///
/// Gets or sets the image
///
public string Image { get; set; }
///
/// Gets or sets the emails
///
public string[] Emails { get; set; }
///
/// Gets or sets the phone numbers
///
public string[] PhoneNumbers { get; set; }
}
}
2.2.1.2. 联系人服务接口:IContactsService.cs
包括:
一个联系人获取请求接口:RetrieveContactsAsync
一个读取一条联系人结果通知事件:OnContactLoaded
该接口由具体平台(Android和iOS)实现。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;
namespace TerminalMACS.Clients.App.Services
{
///
/// Read a contact record notification event parameter.
///
public class ContactEventArgs:EventArgs
{
public Contact Contact { get; }
public ContactEventArgs(Contact contact)
{
Contact = contact;
}
}
///
/// Contact service interface, which is required for Android and iOS terminal specific
/// contact acquisition service needs to implement this interface.
///
public interface IContactsService
{
///
/// Read a contact record and notify the shared library through this event.
///
event EventHandler OnContactLoaded;
///
/// Loading or not
///
bool IsLoading { get; }
///
/// Try to get all contact information
///
///
///
Task> RetrieveContactsAsync(CancellationToken? token = null);
}
}
2.2.1.3. 联系人VM:ContactViewModel.cs
VM提供下面两个功能:
全部联系人加载。
联系人关键字查询。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Resx;
using TerminalMACS.Clients.App.Services;
using Xamarin.Forms;
namespace TerminalMACS.Clients.App.ViewModels
{
///
/// Contact page ViewModel
///
public class ContactViewModel : BaseViewModel
{
///
/// Contact service interface
///
IContactsService _contactService;
private string _SearchText;
///
/// Gets or sets the search text of the contact list.
///
public string SearchText
{
get { return _SearchText; }
set
{
SetProperty(ref _SearchText, value);
}
}
///
/// The search contact command.
///
public ICommand RaiseSearchCommand { get; }
///
/// The contact list.
///
public ObservableCollection Contacts { get; set; }
private List _FilteredContacts;
///
/// Contact filter list.
///
public List FilteredContacts
{
get { return _FilteredContacts; }
set
{
SetProperty(ref _FilteredContacts, value);
}
}
public ContactViewModel()
{
_contactService = DependencyService.Get();
Contacts = new ObservableCollection();
Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
_contactService.OnContactLoaded += OnContactLoaded;
LoadContacts();
RaiseSearchCommand = new Command(RaiseSearchHandle);
}
///
/// Filter contact list
///
void RaiseSearchHandle()
{
if (string.IsNullOrEmpty(SearchText))
{
FilteredContacts = Contacts.ToList();
return;
}
Func checkContact = (s) =>
{
if (!string.IsNullOrWhiteSpace(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower()))
{
return true;
}
else if (s.PhoneNumbers.Length > 0 && s.PhoneNumbers.ToList().Exists(cu => cu.ToString().Contains(SearchText)))
{
return true;
}
return false;
};
FilteredContacts = Contacts.ToList().Where(checkContact).ToList();
}
///
/// BindingBase.EnableCollectionSynchronization
/// Enable cross thread updates for collections
///
///
///
///
///
void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
{
// `lock` ensures that only one thread access the collection at a time
lock (collection)
{
accessMethod?.Invoke();
}
}
///
/// Received a event notification that a contact information was successfully read.
///
///
///
private void OnContactLoaded(object sender, ContactEventArgs e)
{
Contacts.Add(e.Contact);
RaiseSearchHandle();
}
///
/// Read contact information asynchronously
///
///
async Task LoadContacts()
{
try
{
await _contactService.RetrieveContactsAsync();
}
catch (TaskCanceledException)
{
Console.WriteLine(AppResource.TaskCancelled);
}
}
}
}
using Contacts;
using Foundation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Services;
[assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]
namespace TerminalMACS.Clients.App.iOS.Services
{
///
/// Contact service.
///
public class ContactsService : NSObject, IContactsService
{
const string ThumbnailPrefix = "thumb";
bool requestStop = false;
public event EventHandler OnContactLoaded;
bool _isLoading = false;
public bool IsLoading => _isLoading;
///
/// Asynchronous request permission
///
///
public async Task RequestPermissionAsync()
{
var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);
Tuple authotization = new Tuple(status == CNAuthorizationStatus.Authorized, null);
if (status == CNAuthorizationStatus.NotDetermined)
{
using (var store = new CNContactStore())
{
authotization = await store.RequestAccessAsync(CNEntityType.Contacts);
}
}
return authotization.Item1;
}
///
/// Request contact asynchronously. This method is called by the interface.
///
///
///
public async Task> RetrieveContactsAsync(CancellationToken? cancelToken = null)
{
requestStop = false;
if (!cancelToken.HasValue)
cancelToken = CancellationToken.None;
// We create a TaskCompletionSource of decimal
var taskCompletionSource = new TaskCompletionSource>();
// Registering a lambda into the cancellationToken
cancelToken.Value.Register(() =>
{
// We received a cancellation message, cancel the TaskCompletionSource.Task
requestStop = true;
taskCompletionSource.TrySetCanceled();
});
_isLoading = true;
var task = LoadContactsAsync();
// Wait for the first task to finish among the two
var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
_isLoading = false;
return await completedTask;
}
///
/// Load contacts asynchronously, fact reading method of address book.
///
///
async Task> LoadContactsAsync()
{
IList contacts = new List();
var hasPermission = await RequestPermissionAsync();
if (hasPermission)
{
NSError error = null;
var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };
var request = new CNContactFetchRequest(keysToFetch: keysToFetch);
request.SortOrder = CNContactSortOrder.GivenName;
using (var store = new CNContactStore())
{
var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>
{
string path = null;
if (c.ImageDataAvailable)
{
path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");
if (!File.Exists(path))
{
var imageData = c.ThumbnailImageData;
imageData?.Save(path, true);
}
}
var contact = new Contact()
{
Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",
Image = path,
PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),
Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),
};
if (!string.IsNullOrWhiteSpace(contact.Name))
{
OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));
contacts.Add(contact);
}
stop = requestStop;
}));
}
}
return contacts;
}
}
}
2.2.2.3. MainActivity.cs
代码简单,只在OnRequestPermissionsResult方法中接收权限请求结果:
// The contact service processes the result of the permission request.
ContactsService.OnRequestPermissionsResult(requestCode, permissions, grantResults);
using Contacts;
using Foundation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models;
using TerminalMACS.Clients.App.Services;
[assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]
namespace TerminalMACS.Clients.App.iOS.Services
{
///
/// Contact service.
///
public class ContactsService : NSObject, IContactsService
{
const string ThumbnailPrefix = "thumb";
bool requestStop = false;
public event EventHandler OnContactLoaded;
bool _isLoading = false;
public bool IsLoading => _isLoading;
///
/// Asynchronous request permission
///
///
public async Task RequestPermissionAsync()
{
var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);
Tuple authotization = new Tuple(status == CNAuthorizationStatus.Authorized, null);
if (status == CNAuthorizationStatus.NotDetermined)
{
using (var store = new CNContactStore())
{
authotization = await store.RequestAccessAsync(CNEntityType.Contacts);
}
}
return authotization.Item1;
}
///
/// Request contact asynchronously. This method is called by the interface.
///
///
///
public async Task> RetrieveContactsAsync(CancellationToken? cancelToken = null)
{
requestStop = false;
if (!cancelToken.HasValue)
cancelToken = CancellationToken.None;
// We create a TaskCompletionSource of decimal
var taskCompletionSource = new TaskCompletionSource>();
// Registering a lambda into the cancellationToken
cancelToken.Value.Register(() =>
{
// We received a cancellation message, cancel the TaskCompletionSource.Task
requestStop = true;
taskCompletionSource.TrySetCanceled();
});
_isLoading = true;
var task = LoadContactsAsync();
// Wait for the first task to finish among the two
var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
_isLoading = false;
return await completedTask;
}
///
/// Load contacts asynchronously, fact reading method of address book.
///
///
async Task> LoadContactsAsync()
{
IList contacts = new List();
var hasPermission = await RequestPermissionAsync();
if (hasPermission)
{
NSError error = null;
var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };
var request = new CNContactFetchRequest(keysToFetch: keysToFetch);
request.SortOrder = CNContactSortOrder.GivenName;
using (var store = new CNContactStore())
{
var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>
{
string path = null;
if (c.ImageDataAvailable)
{
path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");
if (!File.Exists(path))
{
var imageData = c.ThumbnailImageData;
imageData?.Save(path, true);
}
}
var contact = new Contact()
{
Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",
Image = path,
PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),
Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),
};
if (!string.IsNullOrWhiteSpace(contact.Name))
{
OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));
contacts.Add(contact);
}
stop = requestStop;
}));
}
}
return contacts;
}
}
}