-
Notifications
You must be signed in to change notification settings - Fork 506
GH-26: Add SMS API #91
Changes from 1 commit
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,47 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Android.Content; | ||
|
||
using Uri = Android.Net.Uri; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
public static bool IsComposeSupported | ||
=> Platform.IsIntentSupported(CreateIntent("0000000000")); | ||
|
||
public static Task ComposeAsync(SmsMessage message) | ||
{ | ||
var intent = CreateIntent(message); | ||
Platform.CurrentContext.StartActivity(intent); | ||
|
||
return Task.FromResult(true); | ||
} | ||
|
||
private static Intent CreateIntent(SmsMessage message) | ||
{ | ||
if (message == null) | ||
throw new ArgumentNullException(nameof(message)); | ||
message.Validate(); | ||
|
||
return CreateIntent(message.Recipient, message.Body); | ||
} | ||
|
||
private static Intent CreateIntent(string recipient, string body = null) | ||
{ | ||
Uri uri; | ||
if (!string.IsNullOrWhiteSpace(recipient)) | ||
uri = Uri.Parse("smsto:" + recipient); | ||
else | ||
uri = Uri.Parse("smsto:"); | ||
|
||
var intent = new Intent(Intent.ActionSendto, uri); | ||
|
||
if (!string.IsNullOrWhiteSpace(body)) | ||
intent.PutExtra("sms_body", body); | ||
|
||
return intent; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using MessageUI; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
public static bool IsComposeSupported | ||
=> MFMessageComposeViewController.CanSendText; | ||
|
||
public static Task ComposeAsync(SmsMessage message) | ||
{ | ||
if (message == null) | ||
throw new ArgumentNullException(nameof(message)); | ||
message.Validate(); | ||
|
||
// do this first so we can throw as early as possible | ||
var controller = Platform.GetCurrentViewController(); | ||
|
||
// show the controller | ||
var tcs = new TaskCompletionSource<bool>(); | ||
var messageController = new MFMessageComposeViewController | ||
{ | ||
Body = message.Body, | ||
Recipients = new[] { message.Recipient } | ||
}; | ||
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,13 @@ | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
public static bool IsComposeSupported | ||
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. I think we still need to decide if a bool and this naming is what we want to end up with. 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. Yeah. We may want to have some support level thing: Unsupported, Supported, SupportedWithBackgroundSend or something along those lines. This is more a feature that works when there is a SIM card of some sort. 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. I think that probably each class should be small enough that a single Enum should represent what it needs... So we could introduce "SmsBackground" for instance and that is a new API with a new Enum. I think that is almost better to be honest with you. |
||
=> throw new NotImplementedInReferenceAssemblyException(); | ||
|
||
public static Task ComposeAsync(SmsMessage message) | ||
=> throw new NotImplementedInReferenceAssemblyException(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
} | ||
|
||
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; } | ||
|
||
internal void Validate() | ||
{ | ||
if (string.IsNullOrWhiteSpace(Body)) | ||
throw new ArgumentException("SMS body must not be empty.", nameof(Body)); | ||
|
||
if (string.IsNullOrWhiteSpace(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. I think it can be empty. I think that is fine. |
||
throw new ArgumentException("SMS recipient must not be empty.", nameof(Recipient)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Windows.ApplicationModel.Chat; | ||
using Windows.Foundation.Metadata; | ||
|
||
namespace Microsoft.Caboodle | ||
{ | ||
public static partial class Sms | ||
{ | ||
public static bool IsComposeSupported | ||
=> ApiInformation.IsTypePresent("Windows.ApplicationModel.Chat.ChatMessageManager"); | ||
|
||
public static Task ComposeAsync(SmsMessage message) | ||
{ | ||
if (message == null) | ||
throw new ArgumentNullException(nameof(message)); | ||
message.Validate(); | ||
|
||
var chat = new ChatMessage | ||
{ | ||
Body = message.Body, | ||
Recipients = | ||
{ | ||
message.Recipient | ||
} | ||
}; | ||
|
||
return ChatMessageManager.ShowComposeSmsMessageAsync(chat).AsTask(); | ||
} | ||
} | ||
} |
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; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<views:BasePage xmlns="http://xamarin.com/schemas/2014/forms" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
xmlns:views="clr-namespace:Caboodle.Samples.View" | ||
xmlns:viewmodels="clr-namespace:Caboodle.Samples.ViewModel" | ||
x:Class="Caboodle.Samples.View.SMSPage" | ||
Title="SMS"> | ||
<ContentPage.BindingContext> | ||
<viewmodels:SmsViewModel /> | ||
</ContentPage.BindingContext> | ||
|
||
<StackLayout> | ||
<Label Text="Easily send SMS messages." FontAttributes="Bold" Margin="12" /> | ||
|
||
<ScrollView> | ||
<StackLayout Padding="12,0,12,12" Spacing="6"> | ||
<Label Text="Recipent:" /> | ||
<Entry Text="{Binding Recipient}" Keyboard="Telephone" /> | ||
<Label Text="Message Text:" /> | ||
<Entry Text="{Binding MessageText}" /> | ||
|
||
<Button Text="Send SMS" | ||
Command="{Binding SendSmsCommand}" | ||
IsEnabled="{Binding CanSend}" /> | ||
<Label Text="Please make sure you enter a recipient and some text for the message." | ||
TextColor="Red" | ||
FontAttributes="Italic" | ||
IsVisible="{Binding IsValid, Converter={StaticResource NegativeConverter}}" /> | ||
<Label Text="Sending an SMS is not supported on this platform." | ||
FontAttributes="Italic" | ||
IsVisible="{Binding IsSupported, Converter={StaticResource NegativeConverter}}" /> | ||
|
||
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" /> | ||
</StackLayout> | ||
</ScrollView> | ||
</StackLayout> | ||
|
||
</views:BasePage> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace Caboodle.Samples.View | ||
{ | ||
public partial class SMSPage : BasePage | ||
{ | ||
public SMSPage() | ||
{ | ||
InitializeComponent(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
using System.Windows.Input; | ||
using Microsoft.Caboodle; | ||
using Xamarin.Forms; | ||
|
||
namespace Caboodle.Samples.ViewModel | ||
{ | ||
public class SmsViewModel : BaseViewModel | ||
{ | ||
string recipient; | ||
string messageText; | ||
|
||
public SmsViewModel() | ||
{ | ||
SendSmsCommand = new Command(OnSendSms); | ||
} | ||
|
||
public string Recipient | ||
{ | ||
get => recipient; | ||
set => base.SetProperty(ref recipient, value, onChanged: OnChange); | ||
} | ||
|
||
public string MessageText | ||
{ | ||
get => messageText; | ||
set => SetProperty(ref messageText, value, onChanged: OnChange); | ||
} | ||
|
||
public ICommand SendSmsCommand { get; } | ||
|
||
public bool CanSend => IsSupported && IsValid; | ||
|
||
public bool IsSupported => Sms.IsComposeSupported; | ||
|
||
public bool IsValid => !string.IsNullOrWhiteSpace(MessageText) && !string.IsNullOrWhiteSpace(Recipient); | ||
|
||
private void OnChange() | ||
{ | ||
OnPropertyChanged(nameof(CanSend)); | ||
OnPropertyChanged(nameof(IsSupported)); | ||
OnPropertyChanged(nameof(IsValid)); | ||
} | ||
|
||
async void OnSendSms() | ||
{ | ||
if (IsBusy) | ||
return; | ||
IsBusy = true; | ||
|
||
var message = new SmsMessage(MessageText, Recipient); | ||
await Sms.ComposeAsync(message); | ||
|
||
IsBusy = false; | ||
} | ||
} | ||
} |
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)