Skip to content

Commit

Permalink
Merge pull request #130 from danielmark0116/dg/add-get-email-clients-…
Browse files Browse the repository at this point in the history
…and-update-open-composer
  • Loading branch information
tschoffelen authored Jan 24, 2024
2 parents 937180a + 7e8ec77 commit aa0c1f6
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 6 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ openComposer();

#### Arguments

- [`app`](#app)
- [`title`](#title)
- [`message`](#message) (iOS only)
- [`cancelLabel`](#cancelLabel) (iOS only)
Expand All @@ -186,6 +187,16 @@ openComposer();
- [`body`](#body)
- [`encodeBody`](#encodeBody)

#### `app`

App to open the composer with

| Type | Required | Example |
| ------ | -------- | --------------- |
| string | No | An app's `id` that can be retrieved with `getEmailClients` |
| | | On Android - `id` holds the package name, e.g. `com.mail.app` |
| | | On iOS - `id` holds the app slug/name, e.g. `gmail` |

#### `title`

Text for the top of the ActionSheet or Intent.
Expand Down Expand Up @@ -286,6 +297,28 @@ openComposer({
});
```

### getEmailClients

```javascript
import { getEmailClients } from "react-native-email-link";

const clients = await getEmailClients();

console.log(clients)

[
{
iOSAppName: 'gmail', // iOS only
prefix: 'gmail://',
title: 'GMail',
androidPackagename: 'com.google.android.gm', // Android only
id: 'gmail' // depending on the platform, holds either the package name or the app slug value
}
]
```

To utilize this feature to display an email client picker within a custom UI and subsequently use the `openComposer` to launch a specific app, you just need to pass the `id` (from response) value into the options (`options.app`) within the `openComposer`.

---

<div align="center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -27,6 +29,23 @@ public String getName() {
return "Email";
}

@ReactMethod
public void getEmailClients(final Promise promise) {
Intent emailIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("mailto:"));
PackageManager pm = getCurrentActivity().getPackageManager();
List<ResolveInfo> resInfo = pm.queryIntentActivities(emailIntent, 0);
if (resInfo.size() > 0) {
WritableArray emailApps = new WritableNativeArray();
for (ResolveInfo ri : resInfo) {
String packageName = ri.activityInfo.packageName;
emailApps.pushString(packageName);
}
promise.resolve(emailApps);
} else {
promise.reject("NoEmailAppsAvailable", "No email apps available");
}
}

@ReactMethod
public void open(final String title, final boolean newTask, final Promise promise) {
Intent emailIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("mailto:"));
Expand Down Expand Up @@ -68,6 +87,34 @@ public void open(final String title, final boolean newTask, final Promise promis
}
}

@ReactMethod
public void composeWith(String packageName, final String title, final String to, final String subject, final String body, final String cc, final String bcc, final Promise promise) {
Intent launchIntent = new Intent(Intent.ACTION_SENDTO);
launchIntent.setPackage(packageName);

String uriText = "mailto:" + Uri.encode(to) +
"?subject=" + Uri.encode(subject) +
"&body=" + Uri.encode(body);
if(cc != null) {
uriText += "&cc=" + Uri.encode(cc);
}
if(bcc != null) {
uriText += "&bcc=" + Uri.encode(bcc);
}

Uri uri = Uri.parse(uriText);

launchIntent.setData(uri);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

if (launchIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
getCurrentActivity().startActivity(launchIntent);
promise.resolve("Success");
} else {
promise.reject("AppNotFound", "Application not found");
}
}

@ReactMethod
public void compose(final String title, final String to, final String subject, final String body, final String cc, final String bcc) {
Intent send = new Intent(Intent.ACTION_SENDTO);
Expand Down
4 changes: 2 additions & 2 deletions index.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This file supports both iOS and Android.
*/

import { openInbox, openComposer } from "./src/android";
import { openInbox, openComposer, getEmailClients } from "./src/android";
import { EmailException } from "./src/email-exception";

export { EmailException, openInbox, openComposer };
export { EmailException, openInbox, openComposer, getEmailClients };
10 changes: 10 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ export interface ComposeOptions extends InboxOptions {
body?: string;
}

export function getEmailClients(): Promise<
{
androidPackageName: string;
title: string;
prefix: string;
iOSAppName: string;
id: string;
}[]
>;

export function openInbox({
app,
title,
Expand Down
4 changes: 2 additions & 2 deletions index.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This file supports both iOS and Android.
*/

import { openInbox, openComposer } from "./src/ios";
import { openInbox, openComposer, getEmailClients } from "./src/ios";
import { EmailException } from "./src/email-exception";

export { EmailException, openInbox, openComposer };
export { EmailException, openInbox, openComposer, getEmailClients };
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Placeholder file so that Expo plugin module resolution works

import { openInbox, openComposer } from "./src/ios";
import { openInbox, openComposer, getEmailClients } from "./src/ios";
import { EmailException } from "./src/email-exception";

export { EmailException, openInbox, openComposer };
export { EmailException, openInbox, openComposer, getEmailClients };
72 changes: 72 additions & 0 deletions src/android.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,64 @@
import { NativeModules } from "react-native";
import { EmailException } from "./email-exception";

const titles = {
"com.google.android.gm": "Gmail",
"com.readdle.spark": "Spark",
"com.gemini.airmail": "AirMail",
"com.microsoft.office.outlook": "Outlook",
"com.yahoo.mobile.client.android.mail": "Yahoo Mail",
"com.superhuman.mail": "Superhuman",
"ru.yandex.mail": "Yandex Mail",
"com.fastmail.app": "Fastmail",
"ch.protonmail.android": "ProtonMail",
"cz.seznam.email": "Seznam Email",
};

/**
* Get available email clients
*
* @returns {Promise<{
* androidPackageName: string;
* title: string;
* prefix: string;
* iOSAppName: string;
* id: string;
* }[]>}
*/
export async function getEmailClients() {
if (!("Email" in NativeModules)) {
throw new EmailException(
"NativeModules.Email does not exist. Check if you installed the Android dependencies correctly."
);
}

try {
const clientsPackageNames = await NativeModules.Email.getEmailClients();

return clientsPackageNames.reduce((acc, packageName) => {
const title = titles[packageName] || "";

if (title) {
acc.push({
androidPackageName: packageName, // Android only
title,
prefix: "", // iOS only
iOSAppName: "", // iOS only
id: packageName,
});

return acc;
}

return acc;
}, []);
} catch (error) {
if (error.code === "NoEmailAppsAvailable") {
throw new EmailException("No email apps available");
}
}
}

/**
* Open an email app, or let the user choose what app to open.
*
Expand Down Expand Up @@ -39,8 +97,10 @@ export async function openInbox(options = {}) {

/**
* Open an email app on the compose screen, or let the user choose what app to open on the compose screen.
* You can pass `id` to open a specific app, or `null` to let the user choose. (`id` can be retrieved with `getEmailClients`
*
* @param {{
* app: string | undefined | null,
* title: string,
* removeText: boolean,
* to: string,
Expand All @@ -62,6 +122,18 @@ export async function openComposer(options = {}) {
body = encodeURIComponent(body);
}

if (options.app) {
return NativeModules.Email.composeWith(
options.app,
text,
options.to,
options.subject || "",
body,
options.cc,
options.bcc
);
}

return NativeModules.Email.compose(
text,
options.to,
Expand Down
48 changes: 48 additions & 0 deletions src/ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,53 @@ async function getApp(options, actionType) {
return app;
}

/**
* Get available email clients
*
* @returns {Promise<{
* androidPackageName: string;
* title: string;
* prefix: string;
* iOSAppName: string;
* id: string;
* }[]>}
*/
export function getEmailClients() {
return new Promise(async (resolve, reject) => {
let availableApps = [];
for (let app in prefixes) {
let avail = await isAppInstalled(app);
if (avail) {
availableApps.push(app);
}
}

if (availableApps.length === 0) {
return reject(new EmailException("No email apps available"));
}

const apps = availableApps.reduce((acc, app) => {
const title = titles[app] || "";

if (title) {
acc.push({
androidPackageName: "", // Android only
title,
prefix: prefixes[app], // iOS only
iOSAppName: app, // iOS only
id: app,
});

return acc;
}

return acc;
}, []);

return resolve(apps);
});
}

/**
* Open an email app, or let the user choose what app to open.
*
Expand All @@ -300,6 +347,7 @@ export async function openInbox(options = {}) {

/**
* Open an email app on the compose screen, or let the user choose what app to open on the compose screen.
* You can pass `id` to open a specific app, or `null` to let the user choose. (`id` can be retrieved with `getEmailClients`
*
* @param {{
* app: string | undefined | null,
Expand Down

0 comments on commit aa0c1f6

Please sign in to comment.