Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[expo-sms] Adds Attachment Support To Add Images To SMS Messages #7967

Merged
merged 3 commits into from Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/test-suite/tests/SMS.ios.js
Expand Up @@ -3,6 +3,11 @@ import * as SMS from 'expo-sms';

import { expectMethodToThrowAsync } from '../TestUtils';
import { isInteractive } from '../utils/Environment';
import {
testSMSComposeWithSingleImageAttachment,
testSMSComposeWithTwoImageAttachments,
testSMSComposeWithAudioAttachment,
} from './SMSCommon';

export const name = 'SMS';

Expand All @@ -14,6 +19,18 @@ export function test({ describe, it, expect }) {
// TODO: Bacon: Build an API to close the UI Controller
await SMS.sendSMSAsync(['0123456789', '9876543210'], 'test');
});

it(`opens an SMS composer with single image attachment`, async () => {
await testSMSComposeWithSingleImageAttachment(expect);
});

it(`opens an SMS composer with two image attachments`, async () => {
await testSMSComposeWithTwoImageAttachments(expect);
});

it(`opens an SMS composer with audio attachment`, async () => {
await testSMSComposeWithAudioAttachment(expect);
});
}
} else {
it(`is unavailable`, async () => {
Expand Down
17 changes: 17 additions & 0 deletions apps/test-suite/tests/SMS.js
Expand Up @@ -2,6 +2,11 @@ import * as SMS from 'expo-sms';
import { Platform } from 'react-native';

import { isInteractive } from '../utils/Environment';
import {
testSMSComposeWithSingleImageAttachment,
testSMSComposeWithAudioAttachment,
testSMSComposeWithTwoImageAttachments,
} from './SMSCommon';

export const name = 'SMS';

Expand All @@ -11,6 +16,18 @@ export function test({ describe, it, expect }) {
it(`opens an SMS composer`, async () => {
await SMS.sendSMSAsync(['0123456789', '9876543210'], 'test');
});

it(`opens an SMS composer with single image attachment`, async () => {
await testSMSComposeWithSingleImageAttachment(expect);
});

it(`opens an SMS composer with two image attachments. Only first one is used.`, async () => {
await testSMSComposeWithTwoImageAttachments(expect);
});

it(`opens an SMS composer with audio attachment`, async () => {
await testSMSComposeWithAudioAttachment(expect);
});
});
}

Expand Down
98 changes: 98 additions & 0 deletions apps/test-suite/tests/SMSCommon.js
@@ -0,0 +1,98 @@
import * as FS from 'expo-file-system';
import * as SMS from 'expo-sms';

async function assertExists(testFile, expectedToExist, expect) {
const { exists } = await FS.getInfoAsync(testFile.localUri);
if (expectedToExist) {
expect(exists).toBeTruthy();
} else {
expect(exists).not.toBeTruthy();
}
}

async function loadAndSaveFle(fileInfo, expect) {
await FS.deleteAsync(fileInfo.localUri, { idempotent: true });
await assertExists(fileInfo, false, expect);
const { md5, headers } = await FS.downloadAsync(fileInfo.remoteUri, fileInfo.localUri, {
md5: true,
});
expect(md5).toBe(fileInfo.md5);
await assertExists(fileInfo, true, expect);
expect(headers['Content-Type'] || headers['content-type']).toBe(fileInfo.mimeType);
}

async function cleanupFile(fileInfo, expect) {
await FS.deleteAsync(fileInfo.localUri);
await assertExists(fileInfo, false, expect);
}

const pngFile = {
localUri: FS.documentDirectory + 'image.png',
remoteUri: 'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png',
md5: '1e02045c10b8f1145edc7c8375998f87',
mimeType: 'image/png',
};

const gifFile = {
localUri: FS.documentDirectory + 'image.gif',
remoteUri: 'https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif',
md5: '090592ebd01ac1e425b2766989040f80',
mimeType: 'image/gif',
};

const audioFile = {
localUri: FS.documentDirectory + 'sound.mp3',
remoteUri: 'https://dl.espressif.com/dl/audio/gs-16b-1c-44100hz.mp3',
Comment on lines +38 to +45
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@EvanBacon @bbarthec - would one of y'all be able to upload these to the test-suite-data S3 bucket?

Copy link
Contributor

Choose a reason for hiding this comment

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

I can change after merged

md5: 'e21d733c3506280974842f11c6d30005',
mimeType: 'audio/mpeg',
};

const numbers = ['0123456789', '9876543210'];

export async function testSMSComposeWithSingleImageAttachment(expect) {
await loadAndSaveFle(pngFile, expect);
const contentUri = await FS.getContentUriAsync(pngFile.localUri);
await SMS.sendSMSAsync(numbers, 'test with image', {
attachments: {
uri: contentUri,
mimeType: 'image/png',
filename: 'image.png',
},
});
await cleanupFile(pngFile, expect);
}

export async function testSMSComposeWithTwoImageAttachments(expect) {
await loadAndSaveFle(gifFile, expect);
await loadAndSaveFle(pngFile, expect);
await SMS.sendSMSAsync(numbers, 'test with two images', {
attachments: [
{
uri: await FS.getContentUriAsync(gifFile.localUri),
mimeType: 'image/gif',
filename: 'image.gif',
},
{
uri: await FS.getContentUriAsync(pngFile.localUri),
mimeType: 'image/png',
filename: 'image.png',
},
],
});
await cleanupFile(gifFile, expect);
await cleanupFile(pngFile, expect);
}

export async function testSMSComposeWithAudioAttachment(expect) {
await loadAndSaveFle(audioFile, expect);
await SMS.sendSMSAsync(numbers, 'test with audio', {
attachments: [
{
uri: await FS.getContentUriAsync(audioFile.localUri),
mimeType: 'audio/mpeg',
filename: 'sound.mp3',
},
],
});
await cleanupFile(audioFile, expect);
}
23 changes: 21 additions & 2 deletions docs/pages/versions/unversioned/sdk/sms.md
Expand Up @@ -47,7 +47,11 @@ Opens the default UI/app for sending SMS messages with prefilled addresses and m

- **addresses (_Array\<string\>|string_)** -- An array of addresses (_phone numbers_) or single address passed as strings. Those would appear as recipients of the prepared message.

- **message (_string_)** -- Message to be sent
- **message (_string_)** -- Message to be sent.

- **options (_optional_) (_object_)** -- A map defining additional sms configuration options

- **attachments (_optional_) (_Array<\object_\>|_object_)** -- An array of [SMSAttachment](#smsattachment) objects or single object. Android supports only one attachment.

#### Returns

Expand All @@ -66,6 +70,21 @@ Android does not provide information about the status of the SMS message, so on
```javascript
const { result } = await SMS.sendSMSAsync(
['0123456789', '9876543210'],
'My sample HelloWorld message'
'My sample HelloWorld message',
attachments: {
uri: 'path/myfile.png',
mimeType: 'image/png',
filename: 'myfile.png',
}
);
```

## Related types

### SMSAttachment

An object that is used to describe an attachment that is included with a SMS message.

- **uri (_string_)** -- the content URI of the attachment. The URI needs be a content URI so that it can be accessed by other applications outside of Expo. (See [FileSystem.getContentUriAsync](../filesystem/#filesystemgetcontenturiasyncfileuri))
- **mimeType (_string_)** -- the mime type of the attachment such as `image/png`
- **filename (_string_)** -- the filename of the attachment
2 changes: 2 additions & 0 deletions packages/expo-sms/CHANGELOG.md
Expand Up @@ -6,4 +6,6 @@

### 🎉 New features

- Add `attachments` as an optional parameter to `sendSMSAsync`. It can be used to provide an attachment along with the recipients and message arguments. ([#7967](https://github.com/expo/expo/pull/7967) by [@thorbenprimke](https://github.com/thorbenprimke))

### 🐛 Bug fixes
1 change: 1 addition & 0 deletions packages/expo-sms/android/build.gradle
Expand Up @@ -59,4 +59,5 @@ if (new File(rootProject.projectDir.parentFile, 'package.json').exists()) {
dependencies {
unimodule "unimodules-core"
unimodule "unimodules-permissions-interface"
implementation 'androidx.annotation:annotation:1.1.0'
}
Expand Up @@ -5,9 +5,11 @@
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Telephony;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.unimodules.core.ExportedModule;
import org.unimodules.core.ModuleRegistry;
Expand All @@ -17,10 +19,14 @@
import org.unimodules.core.interfaces.LifecycleEventListener;
import org.unimodules.core.interfaces.services.UIManager;

import androidx.annotation.Nullable;

public class SMSModule extends ExportedModule implements LifecycleEventListener {
private static final String TAG = "ExpoSMS";
private static final String ERROR_TAG = "E_SMS";

private static final String OPTIONS_ATTACHMENTS_KEY = "attachments";

private ModuleRegistry mModuleRegistry;
private Promise mPendingPromise;
private boolean mSMSComposerOpened = false;
Expand Down Expand Up @@ -53,23 +59,42 @@ public void onDestroy() {
}

@ExpoMethod
public void sendSMSAsync(final ArrayList<String> addresses, final String message, final Promise promise) {
public void sendSMSAsync(
final ArrayList<String> addresses,
final String message,
final @Nullable Map<String, Object> options,
final Promise promise) {
if (mPendingPromise != null) {
promise.reject(ERROR_TAG + "_SENDING_IN_PROGRESS", "Different SMS sending in progress. Await the old request and then try again.");
return;
}

final Intent SMSIntent = new Intent(Intent.ACTION_SENDTO);
Intent SMSIntent = new Intent(Intent.ACTION_SEND);
String defaultSMSPackage = Telephony.Sms.getDefaultSmsPackage(getContext());
if (defaultSMSPackage != null){
SMSIntent.setPackage(defaultSMSPackage);
} else {
promise.reject(ERROR_TAG + "_NO_SMS_APP", "No messaging application available");
return;
}
SMSIntent.setType("text/plain");
final String smsTo = constructRecipients(addresses);
SMSIntent.setData(Uri.parse("smsto:" + smsTo));
SMSIntent.putExtra("address", smsTo);

SMSIntent.putExtra("exit_on_sent", true);
SMSIntent.putExtra("compose_mode", true);
SMSIntent.putExtra(Intent.EXTRA_TEXT, message);
SMSIntent.putExtra("sms_body", message);

if (SMSIntent.resolveActivity(getContext().getPackageManager()) == null) {
promise.reject(ERROR_TAG + "_NO_SMS_APP", "No messaging application available");
return;
if (options != null) {
if (options.containsKey(OPTIONS_ATTACHMENTS_KEY)) {
final List<Map<String, String>> attachments = (List<Map<String, String>>) options.get(OPTIONS_ATTACHMENTS_KEY);
if (attachments != null && !attachments.isEmpty()) {
Map<String, String> attachment = attachments.get(0);
SMSIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(attachment.get("uri")));
SMSIntent.setType(attachment.get("mimeType"));
}
}
}

mPendingPromise = promise;
Expand Down
4 changes: 2 additions & 2 deletions packages/expo-sms/build/SMS.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 24 additions & 4 deletions packages/expo-sms/build/SMS.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-sms/build/SMS.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/expo-sms/build/SMS.types.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-sms/build/SMS.types.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.