-
Notifications
You must be signed in to change notification settings - Fork 506
GH-26: Add SMS API #91
Changes from 6 commits
704e622
f710d77
f119567
c89a4c9
5176d75
725c3e2
6249fd0
cf1f9fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
|
||
namespace Microsoft.Caboodle.Tests | ||
{ | ||
public class Sms_Tests | ||
{ | ||
[Fact] | ||
public Task Sms_Fail_On_NetStandard() | ||
{ | ||
return Assert.ThrowsAsync<NotImplementedInReferenceAssemblyException>(() => Sms.ComposeAsync()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
using System.Threading.Tasks; | ||
using Android.Content; | ||
using Android.OS; | ||
using Android.Provider; | ||
|
||
using AndroidUri = Android.Net.Uri; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
static bool IsComposeSupported | ||
=> Platform.IsIntentSupported(CreateIntent("0000000000")); | ||
|
||
public static Task ComposeAsync(SmsMessage message) | ||
{ | ||
if (!IsComposeSupported) | ||
throw new FeatureNotSupportedException(); | ||
|
||
var intent = CreateIntent(message); | ||
Platform.CurrentContext.StartActivity(intent); | ||
|
||
return Task.FromResult(true); | ||
} | ||
|
||
static Intent CreateIntent(SmsMessage message) | ||
=> CreateIntent(message?.Recipient, message?.Body); | ||
|
||
static Intent CreateIntent(string recipient, string body = null) | ||
{ | ||
Intent intent; | ||
if (!string.IsNullOrWhiteSpace(recipient)) | ||
{ | ||
var uri = AndroidUri.Parse("smsto:" + recipient); | ||
intent = new Intent(Intent.ActionSendto, uri); | ||
|
||
if (!string.IsNullOrWhiteSpace(body)) | ||
intent.PutExtra("sms_body", body); | ||
} | ||
else | ||
{ | ||
var pm = Platform.CurrentContext.PackageManager; | ||
var packageName = Telephony.Sms.GetDefaultSmsPackage(Platform.CurrentContext); | ||
intent = pm.GetLaunchIntentForPackage(packageName); | ||
} | ||
|
||
return intent; | ||
} | ||
|
||
public static string GetDefaultSmsPackage(Context context) | ||
{ | ||
if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat) | ||
{ | ||
return Telephony.Sms.GetDefaultSmsPackage(context); | ||
} | ||
else | ||
{ | ||
var defApp = Settings.Secure.GetString(context.ContentResolver, "sms_default_application"); | ||
var pm = context.ApplicationContext.PackageManager; | ||
var intent = pm.GetLaunchIntentForPackage(defApp); | ||
var mInfo = pm.ResolveActivity(intent, 0); | ||
return mInfo.ActivityInfo.PackageName; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System.Threading.Tasks; | ||
using MessageUI; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
static bool IsComposeSupported | ||
=> MFMessageComposeViewController.CanSendText; | ||
|
||
public static Task ComposeAsync(SmsMessage message) | ||
{ | ||
if (!IsComposeSupported) | ||
throw new FeatureNotSupportedException(); | ||
|
||
// do this first so we can throw as early as possible | ||
var controller = Platform.GetCurrentViewController(); | ||
|
||
// create the controller | ||
var messageController = new MFMessageComposeViewController(); | ||
if (!string.IsNullOrWhiteSpace(message?.Body)) | ||
messageController.Body = message.Body; | ||
if (!string.IsNullOrWhiteSpace(message?.Recipient)) | ||
messageController.Recipients = new[] { message.Recipient }; | ||
|
||
// show the controller | ||
var tcs = new TaskCompletionSource<bool>(); | ||
messageController.Finished += (sender, e) => | ||
{ | ||
messageController.DismissViewController(true, null); | ||
tcs.SetResult(e.Result == MessageComposeResult.Sent); | ||
}; | ||
controller.PresentViewController(messageController, true, null); | ||
|
||
return tcs.Task; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
public static Task ComposeAsync(SmsMessage message) | ||
=> throw new NotImplementedInReferenceAssemblyException(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
public static Task ComposeAsync() | ||
=> ComposeAsync(null); | ||
} | ||
|
||
public class SmsMessage | ||
{ | ||
public SmsMessage() | ||
{ | ||
} | ||
|
||
public SmsMessage(string body, string recipient) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there be one with just the body? also what is a recipient? phone number? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could have overloads for each combo, or we could just let the devs use the properties. |
||
{ | ||
Body = body; | ||
Recipient = recipient; | ||
} | ||
|
||
public string Body { get; set; } | ||
|
||
public string Recipient { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Windows.ApplicationModel.Chat; | ||
using Windows.Foundation.Metadata; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
static bool IsComposeSupported | ||
=> ApiInformation.IsTypePresent("Windows.ApplicationModel.Chat.ChatMessageManager"); | ||
|
||
public static Task ComposeAsync(SmsMessage message) | ||
{ | ||
if (!IsComposeSupported) | ||
throw new FeatureNotSupportedException(); | ||
|
||
var chat = new ChatMessage(); | ||
if (!string.IsNullOrWhiteSpace(message?.Body)) | ||
chat.Body = message.Body; | ||
if (!string.IsNullOrWhiteSpace(message?.Recipient)) | ||
chat.Recipients.Add(message.Recipient); | ||
|
||
return ChatMessageManager.ShowComposeSmsMessageAsync(chat).AsTask(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System.Threading.Tasks; | ||
using Microsoft.Caboodle; | ||
using Xunit; | ||
|
||
namespace Caboodle.DeviceTests | ||
{ | ||
public class Sms_Tests | ||
{ | ||
[Fact] | ||
public Task Sms_ComposeAsync_Does_Not_Throw() | ||
{ | ||
return Utils.OnMainThread(() => | ||
{ | ||
if (Utils.IsiOSSimulator) | ||
return Assert.ThrowsAsync<FeatureNotSupportedException>(() => Sms.ComposeAsync()); | ||
else | ||
return Sms.ComposeAsync(); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public Task Sms_ComposeAsync_Does_Not_Throw_When_Empty() | ||
{ | ||
var message = new SmsMessage(); | ||
return Utils.OnMainThread(() => | ||
{ | ||
if (Utils.IsiOSSimulator) | ||
return Assert.ThrowsAsync<FeatureNotSupportedException>(() => Sms.ComposeAsync(message)); | ||
else | ||
return Sms.ComposeAsync(message); | ||
}); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,14 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.Caboodle; | ||
|
||
namespace Caboodle.DeviceTests | ||
{ | ||
public class Utils | ||
{ | ||
public static bool IsiOSSimulator | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be a method that we pass in the device platform? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was more just for the tests, as the iOS sim was quite different There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ahhh gotcha <3 I see it is devicetests cool. |
||
=> DeviceInfo.DeviceType == DeviceType.Virtual && DeviceInfo.Platform == DeviceInfo.Platforms.iOS; | ||
|
||
#if WINDOWS_UWP | ||
public static async Task OnMainThread(Windows.UI.Core.DispatchedHandler action) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<Application xmlns="http://xamarin.com/schemas/2014/forms" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
xmlns:converters="clr-namespace:Caboodle.Samples.Converters" | ||
x:Class="Caboodle.Samples.App"> | ||
<Application.Resources> | ||
|
||
<!-- Application resource dictionary --> | ||
|
||
<ResourceDictionary> | ||
<converters:NegativeConverter x:Key="NegativeConverter" /> | ||
</ResourceDictionary> | ||
</Application.Resources> | ||
</Application> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
using System.Globalization; | ||
using Xamarin.Forms; | ||
|
||
namespace Caboodle.Samples.Converters | ||
{ | ||
public class NegativeConverter : IValueConverter | ||
{ | ||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | ||
{ | ||
if (value is bool v) | ||
return !v; | ||
else | ||
return false; | ||
} | ||
|
||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | ||
{ | ||
if (value is bool v) | ||
return !v; | ||
else | ||
return true; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this get triggered no matter what? even if it is canceled? Is it better ot fire and forget?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to be the case. The docs do say we have to explicitly hide the controller in this event and we are provided a result (success, canceled, failed)