From 3cb12f61d9b6f33aa4b8da6b9b24749f734e9da5 Mon Sep 17 00:00:00 2001 From: Joe Sauve Date: Tue, 12 Jan 2016 16:56:25 -0600 Subject: [PATCH] An enhancement to deal with the iOS simulator not supporting calls, messaging, or email composition. --- Customers/App.xaml.cs | 2 +- Customers/Customers.csproj | 4 + Customers/Data/Customer.cs | 26 +- Customers/Pages/CustomerDetailPage.xaml | 46 ++-- Customers/Pages/CustomerDetailPage.xaml.cs | 2 +- Customers/Pages/CustomerListPage.xaml | 33 +-- Customers/Services/CapabilityService.cs | 47 ++++ Customers/Services/ICapabilityService.cs | 12 + Customers/Services/IEnvironmentService.cs | 10 + .../ViewModels/CustomerDetailViewModel.cs | 63 +++-- Customers/ViewModels/CustomerListViewModel.cs | 98 ++++++- Droid/Customers.Droid.csproj | 2 + Droid/Customers.Droid.csproj.bak | 246 ++++++++++++++++++ Droid/Services/EnvironmentService.cs | 23 ++ iOS/Customers.iOS.csproj | 2 + iOS/Services/EnvironmentService.cs | 18 ++ 16 files changed, 556 insertions(+), 78 deletions(-) create mode 100644 Customers/Services/CapabilityService.cs create mode 100644 Customers/Services/ICapabilityService.cs create mode 100644 Customers/Services/IEnvironmentService.cs create mode 100755 Droid/Customers.Droid.csproj.bak create mode 100644 Droid/Services/EnvironmentService.cs create mode 100644 iOS/Services/EnvironmentService.cs diff --git a/Customers/App.xaml.cs b/Customers/App.xaml.cs index e468147..5dab7a9 100644 --- a/Customers/App.xaml.cs +++ b/Customers/App.xaml.cs @@ -10,7 +10,7 @@ namespace Customers navPage.BarTextColor = Color.White; - customerListPage.BindingContext = new CustomerListViewModel() { Navigation = navPage.Navigation }; // set the context for the customer list page to a new instance of CustomerListViewModel, giving it the same Navigation instance as the navigation page. + customerListPage.BindingContext = new CustomerListViewModel() { Navigation = navPage.Navigation, Page = customerListPage }; // set the context for the customer list page to a new instance of CustomerListViewModel, giving it the same Navigation instance as the navigation page. } } } diff --git a/Customers/Customers.csproj b/Customers/Customers.csproj index b5ca6c2..20540b5 100644 --- a/Customers/Customers.csproj +++ b/Customers/Customers.csproj @@ -53,6 +53,9 @@ CustomerDetailPage.xaml + + + @@ -136,5 +139,6 @@ + diff --git a/Customers/Data/Customer.cs b/Customers/Data/Customer.cs index 335afe1..bac93a7 100644 --- a/Customers/Data/Customer.cs +++ b/Customers/Data/Customer.cs @@ -27,18 +27,30 @@ namespace Customers public string PhotoUrl { get; set; } public string SmallPhotoUrl { get { return PhotoUrl; }} + string _AddressString; [JsonIgnore] public string AddressString { get { - return string.Format( - "{0}{1} {2} {3} {4}", - Street, - !string.IsNullOrWhiteSpace(Unit) ? " " + Unit + "," : string.Empty + ",", - !string.IsNullOrWhiteSpace(City) ? City + "," : string.Empty, - State, - PostalCode); + if (String.IsNullOrWhiteSpace(_AddressString)) + { + var s = string.Format( + "{0}{1} {2} {3} {4}", + Street, + !string.IsNullOrWhiteSpace(Unit) ? " " + Unit + "," : string.Empty + ",", + !string.IsNullOrWhiteSpace(City) ? City + "," : string.Empty, + State, + PostalCode); + + _AddressString = s; + } + + return _AddressString; + } + set + { + _AddressString = value; } } diff --git a/Customers/Pages/CustomerDetailPage.xaml b/Customers/Pages/CustomerDetailPage.xaml index 0cc8c2e..81e23e8 100644 --- a/Customers/Pages/CustomerDetailPage.xaml +++ b/Customers/Pages/CustomerDetailPage.xaml @@ -25,32 +25,35 @@ - - + + - - - - - - - - - - - - + + + + + + + + + + + + + + - + + - + - - - - - - - + + @@ -57,12 +54,18 @@ + + + + + - + diff --git a/Customers/Services/CapabilityService.cs b/Customers/Services/CapabilityService.cs new file mode 100644 index 0000000..72fec44 --- /dev/null +++ b/Customers/Services/CapabilityService.cs @@ -0,0 +1,47 @@ +using System; +using Xamarin.Forms; +using Customers; + +[assembly: Xamarin.Forms.Dependency (typeof (CapabilityService))] + +namespace Customers +{ + public class CapabilityService : ICapabilityService + { + readonly IEnvironmentService _EnvironmentService; + + public CapabilityService() + { + _EnvironmentService = DependencyService.Get(); + } + + #region ICapabilityService implementation + + public bool CanMakeCalls + { + get + { + return _EnvironmentService.IsRealDevice || (Device.OS != TargetPlatform.iOS); + } + } + + public bool CanSendMessages + { + get + { + return _EnvironmentService.IsRealDevice || (Device.OS != TargetPlatform.iOS); + } + } + + public bool CanSendEmail + { + get + { + return _EnvironmentService.IsRealDevice || (Device.OS != TargetPlatform.iOS); + } + } + + #endregion + } +} + diff --git a/Customers/Services/ICapabilityService.cs b/Customers/Services/ICapabilityService.cs new file mode 100644 index 0000000..4ec4502 --- /dev/null +++ b/Customers/Services/ICapabilityService.cs @@ -0,0 +1,12 @@ +using System; + +namespace Customers +{ + public interface ICapabilityService + { + bool CanMakeCalls { get; } + bool CanSendMessages { get; } + bool CanSendEmail { get; } + } +} + diff --git a/Customers/Services/IEnvironmentService.cs b/Customers/Services/IEnvironmentService.cs new file mode 100644 index 0000000..138a151 --- /dev/null +++ b/Customers/Services/IEnvironmentService.cs @@ -0,0 +1,10 @@ +using System; + +namespace Customers +{ + public interface IEnvironmentService + { + bool IsRealDevice { get; } + } +} + diff --git a/Customers/ViewModels/CustomerDetailViewModel.cs b/Customers/ViewModels/CustomerDetailViewModel.cs index 7b69161..b09e57d 100644 --- a/Customers/ViewModels/CustomerDetailViewModel.cs +++ b/Customers/ViewModels/CustomerDetailViewModel.cs @@ -13,10 +13,15 @@ namespace Customers { bool _IsNewCustomer; + // this is just a utility service that we're using in this demo app to mitigate some limitations of the iOS simulator + ICapabilityService _CapabilityService; + readonly Geocoder _Geocoder; public CustomerDetailViewModel(Customer account = null) { + _CapabilityService = DependencyService.Get(); + _Geocoder = new Geocoder(); if (account == null) @@ -200,18 +205,21 @@ namespace Customers async Task ExecuteDialNumberCommand() { if (String.IsNullOrWhiteSpace(Account.Phone)) - return; + return; - if (await Page.DisplayAlert( - title: $"Would you like to call {Account.DisplayName}?", - message: "", - accept: "Call", - cancel: "Cancel")) + if (_CapabilityService.CanMakeCalls) { var phoneCallTask = MessagingPlugin.PhoneDialer; if (phoneCallTask.CanMakePhoneCall) phoneCallTask.MakePhoneCall(Account.Phone.SanitizePhoneNumber()); } + else + { + await Page.DisplayAlert( + title: "Simulator Not Supported", + message: "Phone calls are not supported in the iOS simulator.", + cancel: "OK"); + } } Command _MessageNumberCommand; @@ -232,18 +240,21 @@ namespace Customers async Task ExecuteMessageNumberCommand() { if (String.IsNullOrWhiteSpace(Account.Phone)) - return; + return; - if (await Page.DisplayAlert( - title: $"Would you like to message {Account.DisplayName}?", - message: "", - accept: "Message", - cancel: "Cancel")) + if (_CapabilityService.CanSendMessages) { var messageTask = MessagingPlugin.SmsMessenger; if (messageTask.CanSendSms) messageTask.SendSms(Account.Phone.SanitizePhoneNumber()); } + else + { + await Page.DisplayAlert( + title: "Simulator Not Supported", + message: "Messaging is not supported in the iOS simulator.", + cancel: "OK"); + } } Command _EmailCommand; @@ -266,16 +277,19 @@ namespace Customers if (String.IsNullOrWhiteSpace(Account.Email)) return; - if (await Page.DisplayAlert( - title: $"Would you like to email {Account.DisplayName}?", - message: "", - accept: "Email", - cancel: "Cancel")) + if (_CapabilityService.CanSendEmail) { var emailTask = MessagingPlugin.EmailMessenger; if (emailTask.CanSendEmail) emailTask.SendEmail(Account.Email); } + else + { + await Page.DisplayAlert( + title: "Simulator Not Supported", + message: "Email composition is not supported in the iOS simulator.", + cancel: "OK"); + } } Command _GetDirectionsCommand; @@ -292,18 +306,11 @@ namespace Customers async Task ExecuteGetDirectionsCommand() { - if (await Page.DisplayAlert( - "Get Directions?", - "Getting directions will take you out of this app. Continue to get directions?", - "Yes", - "Cancel")) - { - var position = await GetPosition(); + var position = await GetPosition(); - var pin = new Pin() { Position = position }; + var pin = new Pin() { Position = position }; - CrossExternalMaps.Current.NavigateTo(pin.Label, pin.Position.Latitude, pin.Position.Longitude, NavigationType.Driving); - } + CrossExternalMaps.Current.NavigateTo(pin.Label, pin.Position.Latitude, pin.Position.Longitude, NavigationType.Driving); } public async Task GetPosition() @@ -324,7 +331,7 @@ namespace Customers void SubscribeToSaveCustomerMessages() { // This subscribes to the "SaveCustomer" message - MessagingCenter.Subscribe(this, "SaveCustomer", async (customer) => + MessagingCenter.Subscribe(this, "SaveCustomer", (customer) => { Account = customer; diff --git a/Customers/ViewModels/CustomerListViewModel.cs b/Customers/ViewModels/CustomerListViewModel.cs index 220f419..5833c1c 100644 --- a/Customers/ViewModels/CustomerListViewModel.cs +++ b/Customers/ViewModels/CustomerListViewModel.cs @@ -11,6 +11,8 @@ namespace Customers { public CustomerListViewModel() { + _CapabilityService = DependencyService.Get(); + DataSource = new CustomerDataSource(); SubscribeToSaveCustomerMessages(); @@ -18,6 +20,9 @@ namespace Customers SubscribeToDeleteCustomerMessages(); } + // this is just a utility service that we're using in this demo app to mitigate some limitations of the iOS simulator + ICapabilityService _CapabilityService; + readonly IDataSource DataSource; ObservableCollection _Accounts; @@ -145,16 +150,99 @@ namespace Customers if (customer == null) return; - if (await Page.DisplayAlert( - title: $"Would you like to call {customer.DisplayName}?", - message: "", - accept: "Call", - cancel: "Cancel")) + if (_CapabilityService.CanMakeCalls) { var phoneCallTask = MessagingPlugin.PhoneDialer; if (phoneCallTask.CanMakePhoneCall) phoneCallTask.MakePhoneCall(customer.Phone.SanitizePhoneNumber()); } + else + { + await Page.DisplayAlert( + title: "Simulator Not Supported", + message: "Phone calls are not supported in the iOS simulator.", + cancel: "OK"); + } + } + + Command _MessageNumberCommand; + + /// + /// Command to message customer phone number + /// + public Command MessageNumberCommand + { + get + { + return _MessageNumberCommand ?? + (_MessageNumberCommand = new Command(async (parameter) => + await ExecuteMessageNumberCommand((string)parameter))); + } + } + + async Task ExecuteMessageNumberCommand(string customerId) + { + if (String.IsNullOrWhiteSpace(customerId)) + return; + + var customer = _Accounts.SingleOrDefault(c => c.Id == customerId); + + if (customer == null) + return; + + if (_CapabilityService.CanSendMessages) + { + var messageTask = MessagingPlugin.SmsMessenger; + if (messageTask.CanSendSms) + messageTask.SendSms(customer.Phone.SanitizePhoneNumber()); + } + else + { + await Page.DisplayAlert( + title: "Simulator Not Supported", + message: "Messaging is not supported in the iOS simulator.", + cancel: "OK"); + } + } + + Command _EmailCommand; + + /// + /// Command to email customer + /// + public Command EmailCommand + { + get + { + return _EmailCommand ?? + (_EmailCommand = new Command(async (parameter) => + await ExecuteEmailCommandCommand((string)parameter))); + } + } + + async Task ExecuteEmailCommandCommand(string customerId) + { + if (String.IsNullOrWhiteSpace(customerId)) + return; + + var customer = _Accounts.SingleOrDefault(c => c.Id == customerId); + + if (customer == null) + return; + + if (_CapabilityService.CanSendEmail) + { + var emailTask = MessagingPlugin.EmailMessenger; + if (emailTask.CanSendEmail) + emailTask.SendEmail(customer.Email); + } + else + { + await Page.DisplayAlert( + title: "Simulator Not Supported", + message: "Email composition is not supported in the iOS simulator.", + cancel: "OK"); + } } void SubscribeToSaveCustomerMessages() diff --git a/Droid/Customers.Droid.csproj b/Droid/Customers.Droid.csproj index 2e9598d..249449b 100644 --- a/Droid/Customers.Droid.csproj +++ b/Droid/Customers.Droid.csproj @@ -158,6 +158,7 @@ + @@ -170,6 +171,7 @@ + diff --git a/Droid/Customers.Droid.csproj.bak b/Droid/Customers.Droid.csproj.bak new file mode 100755 index 0000000..2e9598d --- /dev/null +++ b/Droid/Customers.Droid.csproj.bak @@ -0,0 +1,246 @@ + + + + Debug + AnyCPU + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {186E2C95-63F1-44B9-AE99-0F436E3DADFE} + Library + Customers.Droid + Assets + Resources + Resource + Resources\Resource.designer.cs + True + True + CustomersDroid + Properties\AndroidManifest.xml + v6.0 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + None + false + 2G + + + full + true + bin\Release + prompt + 4 + false + false + + + + + + + + ..\packages\Xamarin.Android.Support.v4.23.0.1.3\lib\MonoAndroid403\Xamarin.Android.Support.v4.dll + + + ..\packages\Xamarin.Android.Support.v7.AppCompat.23.0.1.3\lib\MonoAndroid403\Xamarin.Android.Support.v7.AppCompat.dll + + + ..\packages\Xamarin.Android.Support.Design.23.0.1.3\lib\MonoAndroid403\Xamarin.Android.Support.Design.dll + + + ..\packages\Xamarin.Android.Support.v7.CardView.23.0.1.3\lib\MonoAndroid403\Xamarin.Android.Support.v7.CardView.dll + + + ..\packages\Xamarin.Android.Support.v7.MediaRouter.23.0.1.3\lib\MonoAndroid403\Xamarin.Android.Support.v7.MediaRouter.dll + + + ..\packages\Xamarin.Forms.2.0.0.6490\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll + + + ..\packages\Xamarin.Forms.2.0.0.6490\lib\MonoAndroid10\FormsViewGroup.dll + + + ..\packages\Xamarin.Forms.2.0.0.6490\lib\MonoAndroid10\Xamarin.Forms.Core.dll + + + ..\packages\Xamarin.Forms.2.0.0.6490\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll + + + ..\packages\Xamarin.Forms.2.0.0.6490\lib\MonoAndroid10\Xamarin.Forms.Platform.dll + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\monoandroid\System.Net.Http.Primitives.dll + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\monoandroid\System.Net.Http.Extensions.dll + + + + ..\packages\SQLitePCL.3.8.7.2\lib\MonoAndroid\SQLitePCL.dll + + + ..\packages\SQLitePCL.3.8.7.2\lib\MonoAndroid\SQLitePCL.Ext.dll + + + ..\packages\Microsoft.Azure.Mobile.Client.2.0.1\lib\monoandroid\Microsoft.WindowsAzure.Mobile.dll + + + ..\packages\Microsoft.Azure.Mobile.Client.2.0.1\lib\monoandroid\Microsoft.WindowsAzure.Mobile.Ext.dll + + + ..\packages\Microsoft.Azure.Mobile.Client.SQLiteStore.2.0.1\lib\portable-win+net45+wp8+wpa81+monotouch+monoandroid\Microsoft.WindowsAzure.Mobile.SQLiteStore.dll + + + ..\packages\PCLStorage.1.0.2\lib\monoandroid\PCLStorage.dll + + + ..\packages\PCLStorage.1.0.2\lib\monoandroid\PCLStorage.Abstractions.dll + + + ..\packages\Xam.Plugins.Forms.ImageCircle.1.3.0\lib\MonoAndroid10\ImageCircle.Forms.Plugin.Android.dll + + + ..\packages\Xam.Plugins.Forms.ImageCircle.1.3.0\lib\MonoAndroid10\ImageCircle.Forms.Plugin.Abstractions.dll + + + ..\Faker.Portable.dll + + + ..\packages\Xamarin.GooglePlayServices.Base.26.0.0.0\lib\MonoAndroid41\Xamarin.GooglePlayServices.Base.dll + + + ..\packages\Xamarin.GooglePlayServices.Maps.26.0.0.0\lib\MonoAndroid41\Xamarin.GooglePlayServices.Maps.dll + + + ..\packages\Xamarin.Forms.Maps.2.0.0.6490\lib\MonoAndroid10\Xamarin.Forms.Maps.Android.dll + + + ..\packages\Xamarin.Forms.Maps.2.0.0.6490\lib\MonoAndroid10\Xamarin.Forms.Maps.dll + + + ..\packages\Xam.Plugins.Messaging.3.0.0\lib\MonoAndroid10\Lotz.Xam.Messaging.Abstractions.dll + + + ..\packages\Xam.Plugins.Messaging.3.0.0\lib\MonoAndroid10\Lotz.Xam.Messaging.dll + + + ..\packages\Xamarin.Android.Support.v7.RecyclerView.23.0.1.3\lib\MonoAndroid403\Xamarin.Android.Support.v7.RecyclerView.dll + False + + + ..\packages\Refractored.FloatingActionButton.1.4.0\lib\MonoAndroid10\Refractored.FloatingActionButton.dll + False + + + ..\packages\Newtonsoft.Json.8.0.2\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll + + + ..\packages\Xam.Plugin.ExternalMaps.2.0.0\lib\MonoAndroid10\Plugin.ExternalMaps.dll + + + ..\packages\Xam.Plugin.ExternalMaps.2.0.0\lib\MonoAndroid10\Plugin.ExternalMaps.Abstractions.dll + + + + + {55CEDCD6-E2D6-48EB-B9C6-A0E74016DCE0} + Customers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Droid/Services/EnvironmentService.cs b/Droid/Services/EnvironmentService.cs new file mode 100644 index 0000000..d3dd1d0 --- /dev/null +++ b/Droid/Services/EnvironmentService.cs @@ -0,0 +1,23 @@ +using System; +using Android.OS; +using Customers.Droid; + +[assembly: Xamarin.Forms.Dependency (typeof (EnvironmentService))] + +namespace Customers.Droid +{ + public class EnvironmentService : IEnvironmentService + { + #region IEnvironmentService implementation + public bool IsRealDevice + { + get + { + string f = Build.Fingerprint; + return !(f.Contains("vbox") || f.Contains("generic") || f.Contains("vsemu")); + } + } + #endregion + } +} + diff --git a/iOS/Customers.iOS.csproj b/iOS/Customers.iOS.csproj index e0233ee..bd0fb06 100644 --- a/iOS/Customers.iOS.csproj +++ b/iOS/Customers.iOS.csproj @@ -178,12 +178,14 @@ + + diff --git a/iOS/Services/EnvironmentService.cs b/iOS/Services/EnvironmentService.cs new file mode 100644 index 0000000..bbde354 --- /dev/null +++ b/iOS/Services/EnvironmentService.cs @@ -0,0 +1,18 @@ +using ObjCRuntime; +using Customers.iOS; + +[assembly: Xamarin.Forms.Dependency (typeof (EnvironmentService))] + +namespace Customers.iOS +{ + public class EnvironmentService : IEnvironmentService + { + #region IEnvironmentService implementation + public bool IsRealDevice + { + get { return Runtime.Arch == Arch.DEVICE; } + } + #endregion + } +} +