expo-mail-composer 11.2.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 12.0.0 — 2022-10-25
14
+
15
+ ### 🛠 Breaking changes
16
+
17
+ - Bumped iOS deployment target to 13.0 and deprecated support for iOS 12. ([#18873](https://github.com/expo/expo/pull/18873) by [@tsapeta](https://github.com/tsapeta))
18
+
19
+ ## 11.3.0 — 2022-07-07
20
+
21
+ ### 🎉 New features
22
+
23
+ - Native module on iOS is now written in Swift using the new API and JSI. ([#17488](https://github.com/expo/expo/pull/17488) by [@tsapeta](https://github.com/tsapeta))
24
+
13
25
  ## 11.2.0 — 2022-04-18
14
26
 
15
27
  ### ⚠️ Notices
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven-publish'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '11.2.0'
6
+ version = '12.0.0'
7
7
 
8
8
  buildscript {
9
9
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
@@ -74,7 +74,7 @@ android {
74
74
  minSdkVersion safeExtGet("minSdkVersion", 21)
75
75
  targetSdkVersion safeExtGet("targetSdkVersion", 31)
76
76
  versionCode 17
77
- versionName "11.2.0"
77
+ versionName "12.0.0"
78
78
  }
79
79
  lintOptions {
80
80
  abortOnError false
@@ -1,3 +1,3 @@
1
- declare const _default: import("expo-modules-core").ProxyNativeModule;
1
+ declare const _default: any;
2
2
  export default _default;
3
3
  //# sourceMappingURL=ExpoMailComposer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoMailComposer.d.ts","sourceRoot":"","sources":["../src/ExpoMailComposer.ts"],"names":[],"mappings":";AACA,wBAAmD"}
1
+ {"version":3,"file":"ExpoMailComposer.d.ts","sourceRoot":"","sources":["../src/ExpoMailComposer.ts"],"names":[],"mappings":";AAEA,wBAAuD"}
@@ -1,3 +1,3 @@
1
- import { NativeModulesProxy } from 'expo-modules-core';
2
- export default NativeModulesProxy.ExpoMailComposer;
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ export default requireNativeModule('ExpoMailComposer');
3
3
  //# sourceMappingURL=ExpoMailComposer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoMailComposer.js","sourceRoot":"","sources":["../src/ExpoMailComposer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,eAAe,kBAAkB,CAAC,gBAAgB,CAAC","sourcesContent":["import { NativeModulesProxy } from 'expo-modules-core';\nexport default NativeModulesProxy.ExpoMailComposer;\n"]}
1
+ {"version":3,"file":"ExpoMailComposer.js","sourceRoot":"","sources":["../src/ExpoMailComposer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,eAAe,mBAAmB,CAAC,kBAAkB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nexport default requireNativeModule('ExpoMailComposer');\n"]}
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "expo-mail-composer",
3
+ "platforms": ["ios", "android"],
4
+ "ios": {
5
+ "modules": ["MailComposerModule"]
6
+ }
7
+ }
@@ -3,23 +3,30 @@ require 'json'
3
3
  package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
4
 
5
5
  Pod::Spec.new do |s|
6
- s.name = 'EXMailComposer'
6
+ s.name = 'ExpoMailComposer'
7
7
  s.version = package['version']
8
8
  s.summary = package['description']
9
9
  s.description = package['description']
10
10
  s.license = package['license']
11
11
  s.author = package['author']
12
12
  s.homepage = package['homepage']
13
- s.platform = :ios, '12.0'
13
+ s.platform = :ios, '13.0'
14
+ s.swift_version = '5.4'
14
15
  s.source = { git: 'https://github.com/expo/expo.git' }
15
16
  s.static_framework = true
16
17
 
17
18
  s.dependency 'ExpoModulesCore'
18
19
 
20
+ # Swift/Objective-C compatibility
21
+ s.pod_target_xcconfig = {
22
+ 'DEFINES_MODULE' => 'YES',
23
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
24
+ }
25
+
19
26
  if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
20
- s.source_files = "#{s.name}/**/*.h"
27
+ s.source_files = "**/*.h"
21
28
  s.vendored_frameworks = "#{s.name}.xcframework"
22
29
  else
23
- s.source_files = "#{s.name}/**/*.{h,m}"
30
+ s.source_files = "**/*.{h,m,swift}"
24
31
  end
25
32
  end
@@ -0,0 +1,46 @@
1
+ // Copyright 2022-present 650 Industries. All rights reserved.
2
+
3
+ import MessageUI
4
+ import ExpoModulesCore
5
+
6
+ internal final class CannotSendMailException: Exception {
7
+ override var reason: String {
8
+ "Mail services are not available, make sure you're signed into the Mail app"
9
+ }
10
+ }
11
+
12
+ internal final class OperationInProgressException: Exception {
13
+ override var reason: String {
14
+ "Cannot compose an email because another email composing operation is in progress"
15
+ }
16
+ }
17
+
18
+ internal final class MissingViewControllerException: Exception {
19
+ override var reason: String {
20
+ "Unable to find the current view controller to present the compose view controller"
21
+ }
22
+ }
23
+
24
+ internal final class SendingFailedException: GenericException<Error?> {
25
+ override var reason: String {
26
+ "Something went wrong while trying to send the email: \(param.debugDescription)"
27
+ }
28
+ }
29
+
30
+ internal final class UnknownResultException: GenericException<MFMailComposeResult> {
31
+ override var reason: String {
32
+ "Received unknown result: \(param)"
33
+ }
34
+ }
35
+
36
+ internal final class FileSystemNotFoundException: Exception {
37
+ override var reason: String {
38
+ "FileSystem module not found, make sure 'expo-file-system' is linked correctly"
39
+ }
40
+ }
41
+
42
+ internal final class FileSystemReadPermissionException: GenericException<String> {
43
+ override var reason: String {
44
+ "File '\(param)' is not readable"
45
+ }
46
+ }
@@ -0,0 +1,45 @@
1
+ // Copyright 2022-present 650 Industries. All rights reserved.
2
+
3
+ import MessageUI
4
+ import MobileCoreServices
5
+ import ExpoModulesCore
6
+
7
+ public class MailComposerModule: Module {
8
+ var currentSession: MailComposingSession?
9
+
10
+ public func definition() -> ModuleDefinition {
11
+ Name("ExpoMailComposer")
12
+
13
+ AsyncFunction("isAvailableAsync", canSendMail)
14
+
15
+ AsyncFunction("composeAsync") { (options: MailComposerOptions, promise: Promise) in
16
+ guard canSendMail() else {
17
+ throw CannotSendMailException()
18
+ }
19
+ guard self.currentSession == nil else {
20
+ throw OperationInProgressException()
21
+ }
22
+ guard let appContext = self.appContext else {
23
+ throw AppContextLostException()
24
+ }
25
+
26
+ let session = MailComposingSession(appContext)
27
+ try session.compose(options: options)
28
+
29
+ // This is important to retain the session until it finishes
30
+ self.currentSession = session
31
+
32
+ session.presentViewController { result in
33
+ promise.settle(with: result)
34
+
35
+ // Release the session so it can get deallocated
36
+ self.currentSession = nil
37
+ }
38
+ }
39
+ .runOnQueue(.main)
40
+ }
41
+ }
42
+
43
+ private func canSendMail() -> Bool {
44
+ return MFMailComposeViewController.canSendMail()
45
+ }
@@ -0,0 +1,26 @@
1
+ // Copyright 2022-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+
5
+ internal struct MailComposerOptions: Record {
6
+ @Field
7
+ var recipients: [String]?
8
+
9
+ @Field
10
+ var ccRecipients: [String]?
11
+
12
+ @Field
13
+ var bccRecipients: [String]?
14
+
15
+ @Field
16
+ var subject: String = ""
17
+
18
+ @Field
19
+ var body: String = ""
20
+
21
+ @Field
22
+ var isHtml: Bool = false
23
+
24
+ @Field
25
+ var attachments: [URL]?
26
+ }
@@ -0,0 +1,82 @@
1
+ // Copyright 2022-present 650 Industries. All rights reserved.
2
+
3
+ import MessageUI
4
+ import MobileCoreServices
5
+ import ExpoModulesCore
6
+
7
+ internal final class MailComposingSession: NSObject, MFMailComposeViewControllerDelegate {
8
+ typealias ComposingCallback = (Result<[String: String], Exception>) -> ()
9
+
10
+ let appContext: AppContext
11
+ let composeController: MFMailComposeViewController
12
+ var callback: ComposingCallback?
13
+
14
+ init(_ appContext: AppContext) {
15
+ self.appContext = appContext
16
+ self.composeController = MFMailComposeViewController()
17
+ super.init()
18
+
19
+ self.composeController.mailComposeDelegate = self
20
+ }
21
+
22
+ func compose(options: MailComposerOptions) throws {
23
+ composeController.setToRecipients(options.recipients)
24
+ composeController.setCcRecipients(options.ccRecipients)
25
+ composeController.setBccRecipients(options.bccRecipients)
26
+ composeController.setSubject(options.subject)
27
+ composeController.setMessageBody(options.body, isHTML: options.isHtml)
28
+
29
+ if let attachments = options.attachments {
30
+ guard let fileSystem = appContext.fileSystem else {
31
+ throw FileSystemNotFoundException()
32
+ }
33
+ for attachment in attachments {
34
+ guard fileSystem.permissions(forURI: attachment).contains(.read) else {
35
+ throw FileSystemReadPermissionException(attachment.path)
36
+ }
37
+ let mimeType = findMimeType(forAttachment: attachment)
38
+ let fileData = try Data(contentsOf: attachment)
39
+ composeController.addAttachmentData(fileData, mimeType: mimeType, fileName: attachment.lastPathComponent)
40
+ }
41
+ }
42
+ }
43
+
44
+ func presentViewController(_ callback: @escaping ComposingCallback) {
45
+ guard let currentViewController = appContext.utilities?.currentViewController() else {
46
+ callback(.failure(MissingViewControllerException()))
47
+ return
48
+ }
49
+ self.callback = callback
50
+ currentViewController.present(composeController, animated: true)
51
+ }
52
+
53
+ // MARK: - MFMailComposeViewControllerDelegate
54
+
55
+ func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
56
+ composeController.dismiss(animated: true) {
57
+ guard let callback = self.callback else {
58
+ return
59
+ }
60
+ switch result {
61
+ case .cancelled:
62
+ callback(.success(["status": "cancelled"]))
63
+ case .saved:
64
+ callback(.success(["status": "saved"]))
65
+ case .sent:
66
+ callback(.success(["status": "sent"]))
67
+ case .failed:
68
+ callback(.failure(SendingFailedException(error)))
69
+ @unknown default:
70
+ callback(.failure(UnknownResultException(result)))
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ private func findMimeType(forAttachment attachment: URL) -> String {
77
+ if let identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, attachment.pathExtension as CFString, nil)?.takeRetainedValue(),
78
+ let type = UTTypeCopyPreferredTagWithClass(identifier, kUTTagClassMIMEType)?.takeRetainedValue() {
79
+ return type as String
80
+ }
81
+ return "application/octet-stream"
82
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-mail-composer",
3
- "version": "11.2.0",
3
+ "version": "12.0.0",
4
4
  "description": "Provides an API to compose mails using OS specific UI",
5
5
  "main": "build/MailComposer.js",
6
6
  "types": "build/MailComposer.d.ts",
@@ -38,10 +38,10 @@
38
38
  "query-string": "^6.2.0"
39
39
  },
40
40
  "devDependencies": {
41
- "expo-module-scripts": "^2.0.0"
41
+ "expo-module-scripts": "^3.0.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "expo": "*"
45
45
  },
46
- "gitHead": "22dce752354bb429c84851bc4389abe47a766b1f"
46
+ "gitHead": "eab2b09c735fb0fc2bf734a3f29a6593adba3838"
47
47
  }
@@ -1,2 +1,3 @@
1
- import { NativeModulesProxy } from 'expo-modules-core';
2
- export default NativeModulesProxy.ExpoMailComposer;
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+
3
+ export default requireNativeModule('ExpoMailComposer');
package/tsconfig.json CHANGED
@@ -5,5 +5,5 @@
5
5
  "outDir": "./build"
6
6
  },
7
7
  "include": ["./src"],
8
- "exclude": ["**/__mocks__/*", "**/__tests__/*"]
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__stories__/*"]
9
9
  }
@@ -1,9 +0,0 @@
1
- // Copyright 2017-present 650 Industries. All rights reserved.
2
-
3
- #import <ExpoModulesCore/EXExportedModule.h>
4
- #import <ExpoModulesCore/EXModuleRegistryConsumer.h>
5
-
6
- #import <MessageUI/MessageUI.h>
7
-
8
- @interface EXMailComposer : EXExportedModule <EXModuleRegistryConsumer, MFMailComposeViewControllerDelegate>
9
- @end
@@ -1,164 +0,0 @@
1
- // Copyright 2017-present 650 Industries. All rights reserved.
2
-
3
- #import <EXMailComposer/EXMailComposer.h>
4
- #import <MobileCoreServices/MobileCoreServices.h>
5
-
6
- #import <ExpoModulesCore/EXUtilitiesInterface.h>
7
- #import <ExpoModulesCore/EXFileSystemInterface.h>
8
-
9
- @interface EXMailComposer ()
10
-
11
- @property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
12
-
13
- @property (nonatomic, strong) EXPromiseResolveBlock resolve;
14
- @property (nonatomic, strong) EXPromiseRejectBlock reject;
15
-
16
- @end
17
-
18
- @implementation EXMailComposer
19
-
20
- EX_EXPORT_MODULE(ExpoMailComposer);
21
-
22
- - (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
23
- {
24
- _moduleRegistry = moduleRegistry;
25
- }
26
-
27
- - (dispatch_queue_t)methodQueue
28
- {
29
- return dispatch_get_main_queue();
30
- }
31
-
32
- EX_EXPORT_METHOD_AS(isAvailableAsync,
33
- isAvailable:(EXPromiseResolveBlock)resolve
34
- rejecter:(EXPromiseRejectBlock)reject)
35
- {
36
- resolve(@([MFMailComposeViewController canSendMail]));
37
- }
38
-
39
-
40
- EX_EXPORT_METHOD_AS(composeAsync,
41
- composeAsync:(NSDictionary *)options
42
- resolver:(EXPromiseResolveBlock)resolve
43
- rejecter:(EXPromiseRejectBlock)reject)
44
- {
45
- if (![MFMailComposeViewController canSendMail]) {
46
- reject(@"E_COMPOSE_UNAVAILABLE", @"Mail services are not available. Make sure you're signed into the Mail app", nil);
47
- return;
48
- }
49
-
50
- MFMailComposeViewController* composeController = [[MFMailComposeViewController alloc] init];
51
- composeController.mailComposeDelegate = self;
52
-
53
- NSMutableArray *recipients = [[NSMutableArray alloc] init];
54
- for (NSString *recipient in options[@"recipients"]) {
55
- [recipients addObject:recipient];
56
- }
57
- [composeController setToRecipients:recipients];
58
-
59
- NSMutableArray *ccRecipients = [[NSMutableArray alloc] init];
60
- for (NSString *ccRecipient in options[@"ccRecipients"]) {
61
- [ccRecipients addObject:ccRecipient];
62
- }
63
- [composeController setCcRecipients:ccRecipients];
64
-
65
- NSMutableArray *bccRecipients = [[NSMutableArray alloc] init];
66
- for (NSString *bccRecipient in options[@"bccRecipients"]) {
67
- [bccRecipients addObject:bccRecipient];
68
- }
69
- [composeController setBccRecipients:bccRecipients];
70
-
71
- if (options[@"subject"] != nil) {
72
- [composeController setSubject:options[@"subject"]];
73
- }
74
-
75
- if (options[@"body"] != nil) {
76
- BOOL isHTML = NO;
77
- if (options[@"isHtml"]) {
78
- isHTML = YES;
79
- }
80
- [composeController setMessageBody:options[@"body"] isHTML:isHTML];
81
- }
82
-
83
- if (options[@"attachments"] != nil) {
84
- for (NSString *uri in options[@"attachments"]) {
85
- NSURL *url = [NSURL URLWithString:uri];
86
- NSString *path = [url.path stringByStandardizingPath];
87
- id<EXFileSystemInterface> fileSystem = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXFileSystemInterface)];
88
- if (!fileSystem) {
89
- reject(@"E_MISSING_MODULE", @"No FileSystem module.", nil);
90
- return;
91
- }
92
- if (!([fileSystem permissionsForURI:url] & EXFileSystemPermissionRead)) {
93
- reject(@"E_FILESYSTEM_PERMISSIONS", [NSString stringWithFormat:@"File '%@' isn't readable.", uri], nil);
94
- return;
95
- }
96
-
97
- if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
98
- reject(@"E_INVALID_ATTACHMENT", [NSString stringWithFormat:@"The file does not exist. Given path: `%@`.", path], nil);
99
- return;
100
- }
101
-
102
- NSString *mimeType = @"application/octet-stream";
103
- if ([path pathExtension]) {
104
- CFStringRef identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
105
- CFStringRef typeRef = UTTypeCopyPreferredTagWithClass (identifier, kUTTagClassMIMEType);
106
- CFRelease(identifier);
107
- if (typeRef != NULL) {
108
- mimeType = [NSString stringWithString:(__bridge NSString *)(typeRef)];
109
- CFRelease(typeRef);
110
- }
111
- }
112
-
113
- NSData *fileData = [NSData dataWithContentsOfFile:path];
114
-
115
- [composeController addAttachmentData:fileData mimeType:mimeType fileName:[path lastPathComponent]];
116
- }
117
- }
118
-
119
- self.resolve = resolve;
120
- self.reject = reject;
121
- id<EXUtilitiesInterface> utilities = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXUtilitiesInterface)];
122
- [utilities.currentViewController presentViewController:composeController animated:YES completion:nil];
123
- }
124
-
125
- - (void)mailComposeController:(MFMailComposeViewController *)controller
126
- didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
127
- __weak typeof(self) weakSelf = self;
128
- dispatch_async(dispatch_get_main_queue(), ^{
129
- [controller dismissViewControllerAnimated:YES completion:^{
130
- __strong typeof(self) strongSelf = weakSelf;
131
- if (strongSelf && strongSelf.resolve != nil && strongSelf.reject != nil) {
132
- if (error != nil) {
133
- strongSelf.reject(@"E_MAIL_ERROR", @"An error occurred while trying to send the e-mail.", error);
134
- strongSelf.reject = nil;
135
- strongSelf.resolve = nil;
136
- return;
137
- }
138
-
139
- switch (result) {
140
- case MFMailComposeResultSent:
141
- strongSelf.resolve(@{ @"status": @"sent" });
142
- break;
143
- case MFMailComposeResultSaved:
144
- strongSelf.resolve(@{ @"status": @"saved" });
145
- break;
146
- case MFMailComposeResultCancelled:
147
- strongSelf.resolve(@{ @"status": @"cancelled" });
148
- break;
149
- case MFMailComposeResultFailed:
150
- strongSelf.reject(@"E_MAIL_ERROR", @"Something went wrong while trying to send the e-mail.", error);
151
- break;
152
- default:
153
- strongSelf.reject(@"E_MAIL_ERROR", @"Something went wrong while trying to send the e-mail.", error);
154
- break;
155
- }
156
-
157
- strongSelf.reject = nil;
158
- strongSelf.resolve = nil;
159
- }
160
- }];
161
- });
162
- }
163
-
164
- @end
package/unimodule.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "name": "expo-mail-composer",
3
- "platforms": ["ios", "android"]
4
- }