Skip to content
This repository has been archived by the owner on May 15, 2024. It is now read-only.

GH-26: Add SMS API #91

Merged
merged 8 commits into from
Mar 21, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Caboodle.Tests/Sms_Tests.cs
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());
}
}
}
7 changes: 7 additions & 0 deletions Caboodle/Platform/Platform.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ internal static bool HasPermissionInManifest(string permission)
return requestedPermissions?.Any(r => r.Equals(permission, StringComparison.InvariantCultureIgnoreCase)) ?? false;
}

internal static bool IsIntentSupported(Intent intent)
{
var manager = CurrentContext.PackageManager;
var activities = manager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly);
return activities.Any();
}

public static void BeginInvokeOnMainThread(Action action)
{
if (handler?.Looper != Looper.MainLooper)
Expand Down
33 changes: 33 additions & 0 deletions Caboodle/Platform/Platform.ios.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Linq;
using Foundation;
using UIKit;

namespace Microsoft.Caboodle
{
Expand All @@ -15,5 +17,36 @@ public static void BeginInvokeOnMainThread(Action action)

NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke);
}

internal static UIViewController GetCurrentViewController(bool throwIfNull = true)
{
UIViewController viewController = null;

var window = UIApplication.SharedApplication.KeyWindow;

if (window.WindowLevel == UIWindowLevel.Normal)
viewController = window.RootViewController;

if (viewController == null)
{
window = UIApplication.SharedApplication
.Windows
.OrderByDescending(w => w.WindowLevel)
.FirstOrDefault(w => w.RootViewController != null && w.WindowLevel == UIWindowLevel.Normal);

if (window == null)
throw new InvalidOperationException("Could not find current view controller.");
else
viewController = window.RootViewController;
}

while (viewController.PresentedViewController != null)
viewController = viewController.PresentedViewController;

if (throwIfNull && viewController == null)
throw new InvalidOperationException("Could not find current view controller.");

return viewController;
}
}
}
17 changes: 17 additions & 0 deletions Caboodle/Shared/Exceptions.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,21 @@ public PermissionException(string permission)
{
}
}

public class FeatureNotSupportedException : NotSupportedException
{
public FeatureNotSupportedException()
{
}

public FeatureNotSupportedException(string message)
: base(message)
{
}

public FeatureNotSupportedException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
66 changes: 66 additions & 0 deletions Caboodle/Sms/Sms.android.cs
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;
}
}
}
}
38 changes: 38 additions & 0 deletions Caboodle/Sms/Sms.ios.cs
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) =>
Copy link
Collaborator

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?

Copy link
Contributor Author

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)

{
messageController.DismissViewController(true, null);
tcs.SetResult(e.Result == MessageComposeResult.Sent);
};
controller.PresentViewController(messageController, true, null);

return tcs.Task;
}
}
}
10 changes: 10 additions & 0 deletions Caboodle/Sms/Sms.netstandard.cs
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();
}
}
27 changes: 27 additions & 0 deletions Caboodle/Sms/Sms.shared.cs
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
The recipient... each platform just takes a string, so we could leave it as is and hope for the best :)

{
Body = body;
Recipient = recipient;
}

public string Body { get; set; }

public string Recipient { get; set; }
}
}
27 changes: 27 additions & 0 deletions Caboodle/Sms/Sms.uwp.cs
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
Expand Up @@ -15,6 +15,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Geocoding_Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Preferences_Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FileSystem_Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Sms_Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Traits.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils.cs" />
</ItemGroup>
Expand Down
34 changes: 34 additions & 0 deletions DeviceTests/Caboodle.DeviceTests.Shared/Sms_Tests.cs
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);
});
}
}
}
4 changes: 4 additions & 0 deletions DeviceTests/Caboodle.DeviceTests.Shared/Utils.cs
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a method that we pass in the device platform?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Collaborator

Choose a reason for hiding this comment

The 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)
{
Expand Down
7 changes: 4 additions & 3 deletions Samples/Caboodle.Samples/App.xaml
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>
25 changes: 25 additions & 0 deletions Samples/Caboodle.Samples/Converters/NegativeConverter.cs
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;
}
}
}
Loading