Xamarin.Forms客户端第一版

1. 功能简介

1.1. 读取手机基本信息

主要使用Xamarin.Essentials库获取设备基本信息,Xam.Plugin.DeviceInfo插件获取App Id,其实该插件也能获取设备基本信息。

1.2. 读取手机联系人信息

Android和iOS工程具体实现联系人读取服务,使用到DependencyService获取服务功能。

1.3. 应用本地化

使用资源文件实现本地化,目前只做了中、英文。

2. 详细功能说明

2.1. 读取手机基本信息

Xamarin.Essentials库用于获取手机基本信息,比如手机厂商、型号、名称、类型、版本等;Xam.Plugin.DeviceInfo插件获取App Id,用于唯一标识不同手机,获取信息见下图:

代码结构如下图:

ClientInfoViewModel.cs

  1. using Plugin.DeviceInfo;

  2. using System;

  3. using Xamarin.Essentials;

  4. namespace TerminalMACS.Clients.App.ViewModels

  5. {

  6.   /// <summary>

  7.   /// Client base information page ViewModel

  8.   /// </summary>

  9.    public class ClientInfoViewModel : BaseViewModel

  10.    {

  11.       /// <summary>

  12.       /// Gets or sets the id of the application.

  13.       /// </summary>

  14.        public string AppId { get; set; } = CrossDeviceInfo.Current.GenerateAppId();

  15.       /// <summary>

  16.       /// Gets or sets the model of the device.

  17.       /// </summary>

  18.        public string Model { get; private set; } = DeviceInfo.Model;

  19.       /// <summary>

  20.       /// Gets or sets the manufacturer of the device.

  21.       /// </summary>

  22.        public string Manufacturer { get; private set; } = DeviceInfo.Manufacturer;

  23.       /// <summary>

  24.       /// Gets or sets the name of the device.

  25.       /// </summary>

  26.        public string Name { get; private set; } = DeviceInfo.Name;

  27.       /// <summary>

  28.       /// Gets or sets the version of the operating system.

  29.       /// </summary>

  30.        public string VersionString { get; private set; } = DeviceInfo.VersionString;

  31.       /// <summary>

  32.       /// Gets or sets the version of the operating system.

  33.       /// </summary>

  34.        public Version Version { get; private set; } = DeviceInfo.Version;

  35.       /// <summary>

  36.       /// Gets or sets the platform or operating system of the device.

  37.       /// </summary>

  38.        public DevicePlatform Platform { get; private set; } = DeviceInfo.Platform;

  39.       /// <summary>

  40.       /// Gets or sets the idiom of the device.

  41.       /// </summary>

  42.        public DeviceIdiom Idiom { get; private set; } = DeviceInfo.Idiom;

  43.       /// <summary>

  44.       /// Gets or sets the type of device the application is running on.

  45.       /// </summary>

  46.        public DeviceType DeviceType { get; private set; } = DeviceInfo.DeviceType;

  47.    }

  48. }

ClientInfoPage.xaml

  1. <?xml version="1.0" encoding="utf-8" ?>

  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

  3.             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

  4.             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

  5.             xmlns:d="http://xamarin.com/schemas/2014/forms/design"

  6.             xmlns:resources="clr-namespace:TerminalMACS.Clients.App.Resx"

  7.             xmlns:vm="clr-namespace:TerminalMACS.Clients.App.ViewModels"

  8.             mc:Ignorable="d"

  9.             x:Class="TerminalMACS.Clients.App.Views.ClientInfoPage"

  10.             Title="{x:Static resources:AppResource.Title_ClientInfoPage}">

  11.    <ContentPage.BindingContext>

  12.        <vm:ClientInfoViewModel/>

  13.    </ContentPage.BindingContext>

  14.    <ContentPage.Content>

  15.        <StackLayout>

  16.            <Label Text="{x:Static resources:AppResource.AppId}"/>

  17.            <Label Text="{Binding AppId}" FontAttributes="Bold" Margin="10,0,0,10"/>

  18.            

  19.            <Label Text="{x:Static resources:AppResource.DeviceModel}"/>

  20.            <Label Text="{Binding Model}" FontAttributes="Bold" Margin="10,0,0,10"/>

  21.            <Label Text="{x:Static resources:AppResource.DeviceManufacturer}"/>

  22.            <Label Text="{Binding Manufacturer}" FontAttributes="Bold" Margin="10,0,0,10"/>

  23.            

  24.            <Label Text="{x:Static resources:AppResource.DeviceName}"/>

  25.            <Label Text="{Binding Name}" FontAttributes="Bold" Margin="10,0,0,10"/>

  26.            

  27.            <Label Text="{x:Static resources:AppResource.DeviceVersionString}"/>

  28.            <Label Text="{Binding VersionString}" FontAttributes="Bold" Margin="10,0,0,10"/>

  29.            <Label Text="{x:Static resources:AppResource.DevicePlatform}"/>

  30.            <Label Text="{Binding Platform}" FontAttributes="Bold" Margin="10,0,0,10"/>

  31.            

  32.            <Label Text="{x:Static resources:AppResource.DeviceIdiom}"/>

  33.            <Label Text="{Binding Idiom}" FontAttributes="Bold" Margin="10,0,0,10"/>

  34.            

  35.            <Label Text="{x:Static resources:AppResource.DeviceType}"/>

  36.            <Label Text="{Binding DeviceType}" FontAttributes="Bold" Margin="10,0,0,10"/>

  37.        </StackLayout>

  38.    </ContentPage.Content>

  39. </ContentPage>

2.2. 读取手机联系人信息

Android和iOS工程具体实现联系人读取服务,使用到DependencyService获取服务功能,功能截图如下:

2.2.1. TerminalMACS.Clients.App

代码结构如下图:

2.2.1.1. 联系人实体类:Contacts.cs

目前只获取联系人名称、图片、电子邮件(可能多个)、电话号码(可能多个),更多可以扩展。

  1. namespace TerminalMACS.Clients.App.Models

  2. {

  3.   /// <summary>

  4.   /// Contact information entity.

  5.   /// </summary>

  6.    public class Contact

  7.    {

  8.       /// <summary>

  9.       /// Gets or sets the name

  10.       /// </summary>

  11.        public string Name { get; set; }

  12.       /// <summary>

  13.       /// Gets or sets the image

  14.       /// </summary>

  15.        public string Image { get; set; }

  16.       /// <summary>

  17.       /// Gets or sets the emails

  18.       /// </summary>

  19.        public string[] Emails { get; set; }

  20.       /// <summary>

  21.       /// Gets or sets the phone numbers

  22.       /// </summary>

  23.        public string[] PhoneNumbers { get; set; }

  24.    }

  25. }

2.2.1.2. 联系人服务接口:IContactsService.cs

包括:

  • 一个联系人获取请求接口:RetrieveContactsAsync

  • 一个读取一条联系人结果通知事件:OnContactLoaded

该接口由具体平台(Android和iOS)实现。

  1. using System;

  2. using System.Collections.Generic;

  3. using System.Threading;

  4. using System.Threading.Tasks;

  5. using TerminalMACS.Clients.App.Models;

  6. namespace TerminalMACS.Clients.App.Services

  7. {

  8.   /// <summary>

  9.   /// Read a contact record notification event parameter.

  10.   /// </summary>

  11.    public class ContactEventArgs:EventArgs

  12.    {

  13.        public Contact Contact { get; }

  14.        public ContactEventArgs(Contact contact)

  15.        {

  16.            Contact = contact;

  17.        }

  18.    }

  19.   /// <summary>

  20.   /// Contact service interface, which is required for Android and iOS terminal specific

  21.   ///  contact acquisition service needs to implement this interface.

  22.   /// </summary>

  23.    public interface IContactsService

  24.    {

  25.       /// <summary>

  26.       /// Read a contact record and notify the shared library through this event.

  27.       /// </summary>

  28.        event EventHandler<ContactEventArgs> OnContactLoaded;

  29.       /// <summary>

  30.       /// Loading or not

  31.       /// </summary>

  32.        bool IsLoading { get; }

  33.       /// <summary>

  34.       /// Try to get all contact information

  35.       /// </summary>

  36.       /// <param name="token"></param>

  37.       /// <returns></returns>

  38.        Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? token = null);

  39.    }

  40. }

2.2.1.3. 联系人VM:ContactViewModel.cs

VM提供下面两个功能:

  1. 全部联系人加载。

  2. 联系人关键字查询。

  1. using System;

  2. using System.Collections;

  3. using System.Collections.Generic;

  4. using System.Collections.ObjectModel;

  5. using System.Linq;

  6. using System.Threading.Tasks;

  7. using System.Windows.Input;

  8. using TerminalMACS.Clients.App.Models;

  9. using TerminalMACS.Clients.App.Resx;

  10. using TerminalMACS.Clients.App.Services;

  11. using Xamarin.Forms;

  12. namespace TerminalMACS.Clients.App.ViewModels

  13. {

  14.   /// <summary>

  15.   /// Contact page ViewModel

  16.   /// </summary>

  17.    public class ContactViewModel : BaseViewModel

  18.    {

  19.       /// <summary>

  20.       /// Contact service interface

  21.       /// </summary>

  22.        IContactsService _contactService;

  23.        private string _SearchText;

  24.       /// <summary>

  25.       /// Gets or sets the search text of the contact list.

  26.       /// </summary>

  27.        public string SearchText

  28.        {

  29.            get { return _SearchText; }

  30.            set

  31.            {

  32.                SetProperty(ref _SearchText, value);

  33.            }

  34.        }

  35.       /// <summary>

  36.       /// The search contact command.

  37.       /// </summary>

  38.        public ICommand RaiseSearchCommand { get; }

  39.       /// <summary>

  40.       /// The contact list.

  41.       /// </summary>

  42.        public ObservableCollection<Contact> Contacts { get; set; }

  43.        private List<Contact> _FilteredContacts;

  44.       /// <summary>

  45.       /// Contact filter list.

  46.       /// </summary>

  47.        public List<Contact> FilteredContacts

  48.        {

  49.            get { return _FilteredContacts; }

  50.            set

  51.            {

  52.                SetProperty(ref _FilteredContacts, value);

  53.            }

  54.        }

  55.        public ContactViewModel()

  56.        {

  57.            _contactService = DependencyService.Get<IContactsService>();

  58.            Contacts = new ObservableCollection<Contact>();

  59.            Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);

  60.            _contactService.OnContactLoaded += OnContactLoaded;

  61.            LoadContacts();

  62.            RaiseSearchCommand = new Command(RaiseSearchHandle);

  63.        }

  64.       /// <summary>

  65.       /// Filter contact list

  66.       /// </summary>

  67.        void RaiseSearchHandle()

  68.        {

  69.            if (string.IsNullOrEmpty(SearchText))

  70.            {

  71.                FilteredContacts = Contacts.ToList();

  72.                return;

  73.            }

  74.            Func<Contact, bool> checkContact = (s) =>

  75.            {

  76.                if (!string.IsNullOrWhiteSpace(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower()))

  77.                {

  78.                    return true;

  79.                }

  80.                else if (s.PhoneNumbers.Length > 0 && s.PhoneNumbers.ToList().Exists(cu => cu.ToString().Contains(SearchText)))

  81.                {

  82.                    return true;

  83.                }

  84.                return false;

  85.            };

  86.            FilteredContacts = Contacts.ToList().Where(checkContact).ToList();

  87.        }

  88.       /// <summary>

  89.       /// BindingBase.EnableCollectionSynchronization

  90.       ///     Enable cross thread updates for collections

  91.       /// </summary>

  92.       /// <param name="collection"></param>

  93.       /// <param name="context"></param>

  94.       /// <param name="accessMethod"></param>

  95.       /// <param name="writeAccess"></param>

  96.        void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)

  97.        {

  98.           // `lock` ensures that only one thread access the collection at a time

  99.            lock (collection)

  100.            {

  101.                accessMethod?.Invoke();

  102.            }

  103.        }

  104.       /// <summary>

  105.       /// Received a event notification that a contact information was successfully read.

  106.       /// </summary>

  107.       /// <param name="sender"></param>

  108.       /// <param name="e"></param>

  109.        private void OnContactLoaded(object sender, ContactEventArgs e)

  110.        {

  111.            Contacts.Add(e.Contact);

  112.            RaiseSearchHandle();

  113.        }

  114.       /// <summary>

  115.       /// Read contact information asynchronously

  116.       /// </summary>

  117.       /// <returns></returns>

  118.        async Task LoadContacts()

  119.        {

  120.            try

  121.            {

  122.                await _contactService.RetrieveContactsAsync();

  123.            }

  124.            catch (TaskCanceledException)

  125.            {

  126.                Console.WriteLine(AppResource.TaskCancelled);

  127.            }

  128.        }

  129.    }

  130. }

2.2.1.4. 联系人展示页面:ContactPage.xaml

简单的布局,一个StackLayout布局容器竖直排列,一个SearchBar提供关键字搜索功能。

  1. <?xml version="1.0" encoding="utf-8" ?>

  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

  3.             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

  4.             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

  5.             xmlns:d="http://xamarin.com/schemas/2014/forms/design"

  6.             xmlns:resources="clr-namespace:TerminalMACS.Clients.App.Resx"

  7.             xmlns:vm="clr-namespace:TerminalMACS.Clients.App.ViewModels"

  8.             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"

  9.             mc:Ignorable="d"

  10.             Title="{x:Static resources:AppResource.Title_ContactPage}"

  11.             x:Class="TerminalMACS.Clients.App.Views.ContactPage"

  12.             ios:Page.UseSafeArea="true">

  13.    <ContentPage.BindingContext>

  14.        <vm:ContactViewModel/>

  15.    </ContentPage.BindingContext>

  16.    <ContentPage.Content>

  17.        <StackLayout>

  18.            <SearchBar x:Name="filterText"

  19.                        HeightRequest="40"

  20.                        Text="{Binding SearchText}"

  21.                       SearchCommand="{Binding RaiseSearchCommand}"/>

  22.            <ListView   ItemsSource="{Binding FilteredContacts}"

  23.                        HasUnevenRows="True">

  24.                <ListView.ItemTemplate>

  25.                    <DataTemplate>

  26.                        <ViewCell>

  27.                            <StackLayout Padding="10"

  28.                                         Orientation="Horizontal">

  29.                                <Image  Source="{Binding Image}"

  30.                                        VerticalOptions="Center"

  31.                                        x:Name="image"

  32.                                        Aspect="AspectFit"

  33.                                        HeightRequest="60"/>

  34.                                <StackLayout VerticalOptions="Center">

  35.                                    <Label Text="{Binding Name}"

  36.                                       FontAttributes="Bold"/>

  37.                                    <Label Text="{Binding PhoneNumbers[0]}"/>

  38.                                    <Label Text="{Binding Emails[0]}"/>

  39.                                </StackLayout>

  40.                            </StackLayout>

  41.                        </ViewCell>

  42.                    </DataTemplate>

  43.                </ListView.ItemTemplate>

  44.            </ListView>

  45.        </StackLayout>

  46.    </ContentPage.Content>

  47. </ContentPage>

2.2.2. Android

代码结构如下图:

  • AndroidManifest.xml:写入读、写联系人权限请求。

  • ContactsService.cs:具体的联系人权限请求、数据读取操作。

  • MainActivity.cs:接收权限请求结果

  • MainApplicaion.cs:此类未添加任务关键代码,但必不可少,否则无法正确弹出权限请求窗口。

  • PermissionUtil.cs:权限请求结果判断

2.2.2.1. AndroidManifest.xml添加权限

只添加下面这一行即可:

  1. <uses-permission android:name="android.permission.READ_CONTACTS" />

2.2.2.2. ContactsService.cs

Android联系人获取实现服务,实现IContactsService。注意命名空间上的特性代码,必须添加上这个特性后,在前面的联系人VM中才能使用DependencyService.Get()获取此服务实例,默认服务是单例的:

  1. [assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]

  2. using Contacts;

  3. using Foundation;

  4. using System;

  5. using System.Collections.Generic;

  6. using System.IO;

  7. using System.Linq;

  8. using System.Threading;

  9. using System.Threading.Tasks;

  10. using TerminalMACS.Clients.App.Models;

  11. using TerminalMACS.Clients.App.Services;

  12. [assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]

  13. namespace TerminalMACS.Clients.App.iOS.Services

  14. {

  15.   /// <summary>

  16.   /// Contact service.

  17.   /// </summary>

  18.    public class ContactsService : NSObject, IContactsService

  19.    {

  20.        const string ThumbnailPrefix = "thumb";

  21.        bool requestStop = false;

  22.        public event EventHandler<ContactEventArgs> OnContactLoaded;

  23.        bool _isLoading = false;

  24.        public bool IsLoading => _isLoading;

  25.       /// <summary>

  26.       /// Asynchronous request permission

  27.       /// </summary>

  28.       /// <returns></returns>

  29.        public async Task<bool> RequestPermissionAsync()

  30.        {

  31.            var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);

  32.            Tuple<bool, NSError> authotization = new Tuple<bool, NSError>(status == CNAuthorizationStatus.Authorized, null);

  33.            if (status == CNAuthorizationStatus.NotDetermined)

  34.            {

  35.                using (var store = new CNContactStore())

  36.                {

  37.                    authotization = await store.RequestAccessAsync(CNEntityType.Contacts);

  38.                }

  39.            }

  40.            return authotization.Item1;

  41.        }

  42.       /// <summary>

  43.       /// Request contact asynchronously. This method is called by the interface.

  44.       /// </summary>

  45.       /// <param name="cancelToken"></param>

  46.       /// <returns></returns>

  47.        public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)

  48.        {

  49.            requestStop = false;

  50.            if (!cancelToken.HasValue)

  51.                cancelToken = CancellationToken.None;

  52.           // We create a TaskCompletionSource of decimal

  53.            var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();

  54.           // Registering a lambda into the cancellationToken

  55.            cancelToken.Value.Register(() =>

  56.            {

  57.               // We received a cancellation message, cancel the TaskCompletionSource.Task

  58.                requestStop = true;

  59.                taskCompletionSource.TrySetCanceled();

  60.            });

  61.            _isLoading = true;

  62.            var task = LoadContactsAsync();

  63.           // Wait for the first task to finish among the two

  64.            var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);

  65.            _isLoading = false;

  66.            return await completedTask;

  67.        }

  68.       /// <summary>

  69.       /// Load contacts asynchronously, fact reading method of address book.

  70.       /// </summary>

  71.       /// <returns></returns>

  72.        async Task<IList<Contact>> LoadContactsAsync()

  73.        {

  74.            IList<Contact> contacts = new List<Contact>();

  75.            var hasPermission = await RequestPermissionAsync();

  76.            if (hasPermission)

  77.            {

  78.                NSError error = null;

  79.                var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };

  80.                var request = new CNContactFetchRequest(keysToFetch: keysToFetch);

  81.                request.SortOrder = CNContactSortOrder.GivenName;

  82.                using (var store = new CNContactStore())

  83.                {

  84.                    var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>

  85.                    {

  86.                        string path = null;

  87.                        if (c.ImageDataAvailable)

  88.                        {

  89.                            path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");

  90.                            if (!File.Exists(path))

  91.                            {

  92.                                var imageData = c.ThumbnailImageData;

  93.                                imageData?.Save(path, true);

  94.                            }

  95.                        }

  96.                        var contact = new Contact()

  97.                        {

  98.                            Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",

  99.                            Image = path,

  100.                            PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),

  101.                            Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),

  102.                        };

  103.                        if (!string.IsNullOrWhiteSpace(contact.Name))

  104.                        {

  105.                            OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));

  106.                            contacts.Add(contact);

  107.                        }

  108.                        stop = requestStop;

  109.                    }));

  110.                }

  111.            }

  112.            return contacts;

  113.        }

  114.    }

  115. }

2.2.2.3. MainActivity.cs

代码简单,只在OnRequestPermissionsResult方法中接收权限请求结果:

  1. // The contact service processes the result of the permission request.

  2. ContactsService.OnRequestPermissionsResult(requestCode, permissions, grantResults);

2.2.3. iOS

代码结构如下图:

  • ContactsService.cs:具体的联系人权限请求、数据读取操作。

  • Info.plist:权限请求时描述文件

2.2.3.1. ContactsService.cs

iOS具体的联系人读取服务,实现IContactsService接口,原理同Android联系人服务类似,本人无调试环境,iOS此功能未测试。

  1. using Contacts;

  2. using Foundation;

  3. using System;

  4. using System.Collections.Generic;

  5. using System.IO;

  6. using System.Linq;

  7. using System.Threading;

  8. using System.Threading.Tasks;

  9. using TerminalMACS.Clients.App.Models;

  10. using TerminalMACS.Clients.App.Services;

  11. [assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]

  12. namespace TerminalMACS.Clients.App.iOS.Services

  13. {

  14.   /// <summary>

  15.   /// Contact service.

  16.   /// </summary>

  17.    public class ContactsService : NSObject, IContactsService

  18.    {

  19.        const string ThumbnailPrefix = "thumb";

  20.        bool requestStop = false;

  21.        public event EventHandler<ContactEventArgs> OnContactLoaded;

  22.        bool _isLoading = false;

  23.        public bool IsLoading => _isLoading;

  24.       /// <summary>

  25.       /// Asynchronous request permission

  26.       /// </summary>

  27.       /// <returns></returns>

  28.        public async Task<bool> RequestPermissionAsync()

  29.        {

  30.            var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);

  31.            Tuple<bool, NSError> authotization = new Tuple<bool, NSError>(status == CNAuthorizationStatus.Authorized, null);

  32.            if (status == CNAuthorizationStatus.NotDetermined)

  33.            {

  34.                using (var store = new CNContactStore())

  35.                {

  36.                    authotization = await store.RequestAccessAsync(CNEntityType.Contacts);

  37.                }

  38.            }

  39.            return authotization.Item1;

  40.        }

  41.       /// <summary>

  42.       /// Request contact asynchronously. This method is called by the interface.

  43.       /// </summary>

  44.       /// <param name="cancelToken"></param>

  45.       /// <returns></returns>

  46.        public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)

  47.        {

  48.            requestStop = false;

  49.            if (!cancelToken.HasValue)

  50.                cancelToken = CancellationToken.None;

  51.           // We create a TaskCompletionSource of decimal

  52.            var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();

  53.           // Registering a lambda into the cancellationToken

  54.            cancelToken.Value.Register(() =>

  55.            {

  56.               // We received a cancellation message, cancel the TaskCompletionSource.Task

  57.                requestStop = true;

  58.                taskCompletionSource.TrySetCanceled();

  59.            });

  60.            _isLoading = true;

  61.            var task = LoadContactsAsync();

  62.           // Wait for the first task to finish among the two

  63.            var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);

  64.            _isLoading = false;

  65.            return await completedTask;

  66.        }

  67.       /// <summary>

  68.       /// Load contacts asynchronously, fact reading method of address book.

  69.       /// </summary>

  70.       /// <returns></returns>

  71.        async Task<IList<Contact>> LoadContactsAsync()

  72.        {

  73.            IList<Contact> contacts = new List<Contact>();

  74.            var hasPermission = await RequestPermissionAsync();

  75.            if (hasPermission)

  76.            {

  77.                NSError error = null;

  78.                var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };

  79.                var request = new CNContactFetchRequest(keysToFetch: keysToFetch);

  80.                request.SortOrder = CNContactSortOrder.GivenName;

  81.                using (var store = new CNContactStore())

  82.                {

  83.                    var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>

  84.                    {

  85.                        string path = null;

  86.                        if (c.ImageDataAvailable)

  87.                        {

  88.                            path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");

  89.                            if (!File.Exists(path))

  90.                            {

  91.                                var imageData = c.ThumbnailImageData;

  92.                                imageData?.Save(path, true);

  93.                            }

  94.                        }

  95.                        var contact = new Contact()

  96.                        {

  97.                            Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",

  98.                            Image = path,

  99.                            PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),

  100.                            Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),

  101.                        };

  102.                        if (!string.IsNullOrWhiteSpace(contact.Name))

  103.                        {

  104.                            OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));

  105.                            contacts.Add(contact);

  106.                        }

  107.                        stop = requestStop;

  108.                    }));

  109.                }

  110.            }

  111.            return contacts;

  112.        }

  113.    }

  114. }

2.2.3.2. Info.plist

联系人权限请求说明 

2.3. 应用本地化

使用资源文件实现本地化,目前只做了中、英文。

资源文件如下:

指定默认区域性

要使资源文件可正常使用,应用程序必须指定 NeutralResourcesLanguage。在共享项目中,应自定义 AssemblyInfo.cs 文件以指定默认区域性 。以下代码演示如何在 AssemblyInfo.cs 文件中将 NeutralResourcesLanguage 设置为 zh-CN (摘自官方文档:https://docs.microsoft.com/zh-cn/samples/xamarin/xamarin-forms-samples/usingresxlocalization/,后经测试,注释下面这段代码也能正常本地化):

  1. [assembly: NeutralResourcesLanguage("zh-Hans")]

XAML中使用

引入资源文件命名空间

  1. xmlns:resources="clr-namespace:TerminalMACS.Clients.App.Resx"

具体使用如

  1. <Label Text="{x:Static resources:AppResource.ClientName_AboutPage}" FontAttributes="Bold"/>

3. 关于TerminalMACS及本客户端

3.1. TermainMACS

多终端资源管理与检测系统,包含多个子进程模块,目前只开发了Xamarin.Forms客户端,下一步开发服务端,使用 .NET 5 Web API开发,基于Abp vNext搭建。

3.2. Xamarin.Forms客户端

作为TerminalMACS系统的一个子进程模块,目前只开发了手机基本信息获取、联系人信息获取、本地化功能,后续开发服务端时,会配合添加通信功能,比如连接服务端验证、主动推送已获取资源等。

3.3. 关于项目开源

  1. 开源项目地址:https://github.com/dotnet9/TerminalMACS

除非注明,文章均由 TerminalMACS 整理发布,欢迎转载。

转载请注明本文地址:https://terminalmacs.com/890.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310751.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

给 EF Core 查询增加 With NoLock

给 EF Core 查询增加 With NoLockIntroEF Core 在 3.x 版本中增加了 Interceptor&#xff0c;使得我们可以在发生低级别数据库操作时作为 EF Core 正常运行的一部分自动调用它们。例如&#xff0c;打开连接、提交事务或执行命令时。所以我们可以自定义一个 Interceptor 来记录执…

LeetCode 138 复制带随机指针的链表-中等

给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节点的 n…

ASP.NET Core分布式项目实战(业务介绍,架构设计,oAuth2,IdentityServer4)--学习笔记...

任务4&#xff1a;第一章计划与目录敏捷产品开发流程原型预览与业务介绍整体架构设计API 接口设计 / swaggerIdentity Server 4 搭建登录账号 API 实现配置中心任务5&#xff1a;业务介绍项目背景&#xff1a;基于人脉关系的金融行业项目用户&#xff1a;1、账号&#xff1a;基…

LeetCode 82 删除排序链表中的重复元素||-中等

存在一个按升序排列的链表&#xff0c;给你这个链表的头节点 head &#xff0c;请你删除链表中所有存在数字重复情况的节点&#xff0c;只保留原始链表中 没有重复出现 的数字。 返回同样按升序排列的结果链表。 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,…

你复工了吗?啥感受?

这里是Z哥的个人公众号每周五11&#xff1a;45 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「136」篇原创敬上感觉还没做什么事情&#xff0c;2020年的第一季度就结束了。相信大多数人也都已经复工了。之前进行远程公办的&#xff0c;大多也都回到了原先在公…

LeetCode 1669合并两个链表-中等

给你两个链表 list1 和 list2 &#xff0c;它们包含的元素分别为 n 个和 m 个。 请你将 list1 中第 a 个节点到第 b 个节点删除&#xff0c;并将list2 接在被删除节点的位置。 下图中蓝色边和节点展示了操作后的结果&#xff1a; 请你返回结果链表的头指针。 输入&#xff1a…

dotNET Core 3.X 使用 Web API

现在的 Web 开发大多都是前后端分离的方式&#xff0c;后端接口的正确使用显得尤为重要&#xff0c;本文讲下在 dotNET Core 3.X 下使用 Web API 。环境操作系统&#xff1a;MacIDE&#xff1a;RiderdotNET Core&#xff1a;3.1创建项目如果是 Windows 操作系统当然是首选 VS20…

你需要了解的 HTTP Status Code

你需要了解的 HTTP Status CodeIntro现在前后端分离的开发模式越来越流行&#xff0c;后端负责开发对应的 API&#xff0c;前端只需要 关注前端页面的数据展示和前端逻辑即可。对于前后端分离这种开发模式&#xff0c;我个人还是比较喜欢的&#xff0c;因为这样可以让更专业的人…

LeetCode 24两两交换链表中的节点-中等

给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。 你不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 输入&#xff1a;head [1,2,3,4] 输出&#xff1a;[2,1,4,3] 示例 2&#xff1a; 输入&#xff1a;head []…

2021中考高考成绩查询,2021中考

2021年浙江东阳中考查分入口暂未公布&#xff01;如有最新信息&#xff0c;中考网会第一时间发布&#xff0c;请中考生和家长及时关注中考网中考考试时间频道&#xff01; 编辑推荐&#xff1a; 2021年浙江省中考查分时间及入口汇总 2021年全国各省市中考查分时间及入2021-06-1…

今天网站都变成灰色了,这其中是怎么实现的?

“ 阅读本文大概需要 7 分钟。 ”今天是 2020 年 4 月 4 日&#xff0c;星期六&#xff0c;清明节。我们的国家经历了非常惨痛的时刻&#xff0c;很多英雄在救助他人的路上倒下&#xff0c;更有很多烈士英雄保卫人民的安危遇难&#xff0c;今天全国下降半旗&#xff0c;北京时间…

深圳市公务员考试计算机专业素养,深圳市考职位分析_公务员考试专业对照表...

2020深圳市公务员招录1069人公告已发布&#xff0c;报名时间&#xff1a;11月13日-19日16:00&#xff0c;报名入口&#xff1a;深圳市考试院专栏(http://hrss.sz.gov.cn/szksy/)或深圳市人事考试考生服务系统(以下简称考生服务系统&#xff0c;https://hrsstext.sz.gov.cn/ess/…

LeetCode 61旋转链表-中等

给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1,2,3] 输入&#xff1a;head [0,1,2], k 4 输出&#xff1a;[2,0,1] 提示&#xff1a; 链表中节点的数目在…

科个普:进程、线程、并发、并行

一、进程刘大胖打开电脑&#xff0c;想写点东西&#xff0c;于是打开WPS&#xff0c;突然又想和女朋友(反正我不信)聊聊天&#xff0c;就又打开了微信PC端&#xff0c;这时操作系统就会为这两个程序生成两个进程&#xff0c;如图&#xff1a;二、线程每个进程至少包含一个线程&…

LeetCode 19删除链表的倒数第N个节点-中等

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 进阶&#xff1a;你能尝试使用一趟扫描实现吗&#xff1f; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n …

EFCore查询语句生成流程、让EFCore支持批量Update/Delete/MergeInto

引子之前发现了一款叫 EFCore.BulkExtensions 的 nuget 包。里面提供了大量的 BulkInsertOrUpdateOrDelete 和 BatchUpdate 的拓展&#xff0c;可以很方便的解决批量更新和删除的问题&#xff0c;不用让 EFCore 一条一条的删除和更新。其中几个比较有用的函数签名是Task<int…

html程序国庆节祝福,2018国庆节祝福祖国的话

2018国庆节即将来袭~那么2018国庆节祝福祖国的话有哪些呢&#xff1f;今天语录大全网小编就为大家整理了一篇10.1国庆节祝福祖国的话语&#xff0c;分享给大家&#xff0c;在这里小编祝大家国庆节快乐1、【祖国是东方的明珠&#xff0c;是亚洲腾飞的巨龙&#xff0c;是地平线上…

【翻译】.NET 5 Preview2发布

在4月2日&#xff0c;发布了.NET 5.0 Preview2&#xff0c;这次发布对一些功能和性能做了相关的改进&#xff0c;同时后面也会实施5.0版本更多的功能&#xff0c;其中一些功能目前也dotnet/designs在.NET 5 Preview1中可以看到.NET 5里程碑中已经完成的建设任务&#xff0c;当然…

LeetCode 142环形链表||-中等

给定一个链表&#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 为了表示给定链表中的环&#xff0c;我们使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索引从 0 开始&#xff09;。 如果 pos 是 -1&#xff0c;则在该链表中没有…

.NET 5.0 Preview 2发布解析

2020年4月2日微软.NET 团队的项目经理 Richard 在博客上 发布了.NET 5 Preview 2&#xff1a;https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-2/ &#xff0c;3月16号&#xff0c;Scott Hunter 在博客中发布了.NET 5 Preview 1 第一个预览版发布。https://…