forked from subsurface/subsurface
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
iOS: add infrastructure to natively send email
This will allow us to send attachments, just like we do on Android. Signed-off-by: Dirk Hohndel <[email protected]>
- Loading branch information
Showing
3 changed files
with
140 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
#ifndef IOSSHARE_H | ||
#define IOSSHARE_H | ||
|
||
// onlt Qt headers and data structures allowed here | ||
#include <QString> | ||
|
||
class IosShare { | ||
public: | ||
IosShare(); | ||
~IosShare(); | ||
void supportEmail(const QString &firstPath, const QString &secondPath); | ||
void shareViaEmail(const QString &subject, const QString &recipient, const QString &body, const QString &firstPath, const QString &secondPath); | ||
private: | ||
void *self; | ||
}; | ||
|
||
#endif /* IOSSHARE_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
// | ||
// this code was inspired by the discussions in | ||
// https://forum.qt.io/topic/88297/native-objective-c-calls-from-cpp-qt-ios-email-call | ||
|
||
// this include file only has the C++/Qt headers that can be used from C++ | ||
#include "ios-share.h" | ||
|
||
// these are the required ObjC++ headers | ||
#import <Foundation/Foundation.h> | ||
#import <Foundation/NSString.h> | ||
#import <UIKit/UIKit.h> | ||
#import <MessageUI/MessageUI.h> | ||
|
||
// declare an ObjC++ class that will interact with the mail controller | ||
// that second member that is called when the mail app is finished is critical for this to work | ||
@interface IosShareObject : UIViewController <MFMailComposeViewControllerDelegate> | ||
{ | ||
} | ||
- (void)shareViaEmail:(const QString &) subject :(const QString &) recipient :(const QString &) body :(const QString &) firstPath :(const QString &) secondPath; | ||
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(nullable NSError *)error; | ||
@end | ||
|
||
@implementation IosShareObject | ||
// first, inside the implementation of the ObjC++ class, implement the Qt class | ||
IosShare::IosShare() : self(NULL) { | ||
// call init to ensure that the ObjC++ object is instantiated, which in return | ||
// apparently sets up the Controller | ||
self = [ [IosShareObject alloc] init]; | ||
} | ||
|
||
IosShare::~IosShare() { | ||
[(id)self dealloc]; | ||
} | ||
|
||
// simplified method that fills subject, recipient, and body for support emails | ||
void IosShare::supportEmail(const QString &firstPath, const QString &secondPath) { | ||
QString subject("Subsurface-mobile support request"); | ||
QString recipient("[email protected]"); | ||
QString body("Please describe your issue here and keep the attached logs.\n\n\n\n"); | ||
shareViaEmail(subject, recipient, body, firstPath, secondPath); | ||
} | ||
|
||
void IosShare::shareViaEmail(const QString &subject, const QString &recipient, const QString &body, const QString &firstPath, const QString &secondPath) { | ||
// ObjC++ syntax to call the shareViaEmail method of that class - so this is | ||
// where we transition from Qt/C++ code to ObjC++ code that can interact | ||
// directly with iOS | ||
[(id)self shareViaEmail:subject:recipient:body:firstPath:secondPath]; | ||
} | ||
|
||
// the rest is the ObjC++ implementation | ||
- (instancetype)init { | ||
// this is just boiler plate that I really don't understand | ||
// it appears to make sure that the ViewController infrastructure is initialized? | ||
return super.init; | ||
} | ||
|
||
- (void)shareViaEmail:(const QString &) subjectQS :(const QString &) recipientQS :(const QString &) bodyQS :(const QString &) firstPathQS :(const QString &) secondPathQS { | ||
// since we are mixing Qt and ObjC++ data structures, let's allocate copies | ||
// of our Qt strings and convert recipients into an array | ||
NSString *firstPath = [[NSString alloc] initWithUTF8String:firstPathQS.toUtf8().data()]; | ||
NSString *secondPath = [[NSString alloc] initWithUTF8String:secondPathQS.toUtf8().data()]; | ||
NSString *subject = [[NSString alloc] initWithUTF8String:subjectQS.toUtf8().data()]; | ||
NSString *recipient = [[NSString alloc] initWithUTF8String:recipientQS.toUtf8().data()]; | ||
NSString *body = [[NSString alloc] initWithUTF8String:bodyQS.toUtf8().data()]; | ||
NSArray *recipents = [NSArray arrayWithObject:recipient]; | ||
// create the mail controller and connect it with the object | ||
MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init]; | ||
mc.mailComposeDelegate = self; | ||
[mc setSubject:subject]; | ||
[mc setMessageBody:body isHTML:NO]; | ||
[mc setToRecipients:recipents]; | ||
// set up up to two attachments - only if we have a path and the file isn't empty (iOS throws up if you have an empty attachment) | ||
if (!firstPathQS.isEmpty()) { | ||
NSData *myData = [NSData dataWithContentsOfFile: firstPath]; | ||
if (myData != nil) | ||
[mc addAttachmentData:myData mimeType:@"text/plain" fileName:[firstPath lastPathComponent]]; | ||
} | ||
if (!secondPathQS.isEmpty()) { | ||
//NSString *path = [[NSBundle mainBundle] pathForResource:@"log2" ofType:@"txt"]; | ||
NSData *myData = [NSData dataWithContentsOfFile: secondPath]; | ||
if (myData != nil) | ||
[mc addAttachmentData:myData mimeType:@"text/plain" fileName:[secondPath lastPathComponent]]; | ||
} | ||
// more black magic; get a view controller that is connected to our application window | ||
UIViewController * topController = [UIApplication sharedApplication].keyWindow.rootViewController; | ||
while (topController.presentedViewController){ | ||
topController = topController.presentedViewController; | ||
} | ||
// finally, show the controller - the code returns right away, which is why we need the 'didFinishWithResult' method below | ||
[topController presentViewController:mc animated:YES completion:NULL]; | ||
} | ||
|
||
// I would have kinda liked to inform the caller that sending mail failed, but I can't figure | ||
// out how to get that information back to the Qt code calling us. Oh well. At least we log the results. | ||
// But the critically important part is that we dismiss the view controller. | ||
- (void) mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(nullable NSError *)error { | ||
switch (result) { | ||
case MFMailComposeResultCancelled: | ||
NSLog(@"Mail cancelled"); | ||
break; | ||
case MFMailComposeResultSaved: | ||
NSLog(@"Mail saved");break; | ||
case MFMailComposeResultSent: | ||
NSLog(@"Mail sent");break; | ||
case MFMailComposeResultFailed: | ||
NSLog(@"Mail sent failure: %@", [error localizedDescription]); | ||
break; | ||
default: | ||
break; | ||
} | ||
[controller dismissViewControllerAnimated:YES completion:NULL]; | ||
} | ||
@end |