1 package expo.modules.mailcomposer
2 
3 import android.app.Activity
4 import android.content.Context
5 import android.content.Intent
6 import android.content.pm.LabeledIntent
7 import android.net.Uri
8 import android.os.Bundle
9 import expo.modules.core.ExportedModule
10 import expo.modules.core.ModuleRegistry
11 import expo.modules.core.ModuleRegistryDelegate
12 import expo.modules.core.Promise
13 import expo.modules.core.arguments.ReadableArguments
14 import expo.modules.core.interfaces.ActivityProvider
15 import expo.modules.core.interfaces.ExpoMethod
16 import expo.modules.core.interfaces.ActivityEventListener
17 import expo.modules.core.interfaces.services.UIManager
18 
19 class MailComposerModule(
20   context: Context,
21   private val moduleRegistryDelegate: ModuleRegistryDelegate = ModuleRegistryDelegate()
22 ) : ExportedModule(context), ActivityEventListener {
23   private var composerOpened = false
24   private val uiManager: UIManager by moduleRegistry()
25   private var pendingPromise: Promise? = null
26   override fun getName() = "ExpoMailComposer"
27   private val activityProvider: ActivityProvider by moduleRegistry()
28 
29   private inline fun <reified T> moduleRegistry() = moduleRegistryDelegate.getFromModuleRegistry<T>()
30 
31   override fun onCreate(moduleRegistry: ModuleRegistry) {
32     moduleRegistryDelegate.onCreate(moduleRegistry)
33     uiManager.registerActivityEventListener(this)
34   }
35 
36   override fun onDestroy() {
37     uiManager.unregisterActivityEventListener(this)
38   }
39 
40   @ExpoMethod
41   fun isAvailableAsync(promise: Promise) {
42     promise.resolve(true)
43   }
44 
45   @ExpoMethod
46   fun composeAsync(options: ReadableArguments, promise: Promise) {
47     val intent = Intent(Intent.ACTION_SENDTO).apply { data = Uri.parse("mailto:") }
48     val application = activityProvider.currentActivity.application
49     val resolveInfo = context.packageManager.queryIntentActivities(intent, 0)
50     val mailIntents = resolveInfo.map { info ->
51       val isHtml = options.containsKey("isHtml") && options.getBoolean("isHtml")
52       val mailIntentBuilder = MailIntentBuilder(options)
53         .setComponentName(info.activityInfo.packageName, info.activityInfo.name)
54         .putExtraIfKeyExists("recipients", Intent.EXTRA_EMAIL)
55         .putExtraIfKeyExists("ccRecipients", Intent.EXTRA_CC)
56         .putExtraIfKeyExists("bccRecipients", Intent.EXTRA_BCC)
57         .putExtraIfKeyExists("subject", Intent.EXTRA_SUBJECT)
58         .putExtraIfKeyExists("body", Intent.EXTRA_TEXT, isHtml)
59         .putParcelableArrayListExtraIfKeyExists(
60           "attachments",
61           Intent.EXTRA_STREAM,
62           application
63         )
64       LabeledIntent(
65         mailIntentBuilder.build(),
66         info.activityInfo.packageName,
67         info.loadLabel(context.packageManager),
68         info.icon
69       )
70     }.toMutableList()
71     val chooser = Intent.createChooser(
72       mailIntents.removeAt(mailIntents.size - 1),
73       null
74     ).apply {
75       putExtra(Intent.EXTRA_INITIAL_INTENTS, mailIntents.toTypedArray())
76       addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
77     }
78     pendingPromise = promise
79     activityProvider.currentActivity.startActivityForResult(chooser, REQUEST_CODE)
80     composerOpened = true
81   }
82 
83   override fun onNewIntent(intent: Intent) = Unit
84 
85   override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
86     if (requestCode == REQUEST_CODE && pendingPromise != null) {
87       val promise = pendingPromise ?: return
88       if (composerOpened) {
89         composerOpened = false
90         promise.resolve(Bundle().apply { putString("status", "sent") })
91       }
92     }
93   }
94 
95   companion object {
96     private const val REQUEST_CODE = 8675
97   }
98 }
99