From 8164ca56ec6ae25fac6a8adaa0001891f2e4eded Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 29 Aug 2022 16:40:04 -0700 Subject: [PATCH] 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 --- Subsurface-mobile.pro | 8 +++ ios/ios-share.h | 18 +++++++ ios/ios-share.mm | 114 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 ios/ios-share.h create mode 100644 ios/ios-share.mm diff --git a/Subsurface-mobile.pro b/Subsurface-mobile.pro index 287f9eff6..f1afbceaf 100644 --- a/Subsurface-mobile.pro +++ b/Subsurface-mobile.pro @@ -407,6 +407,12 @@ ios { images.files = icons/subsurface-mobile-icon.png QMAKE_BUNDLE_DATA += app_launch_images images + OBJECTIVE_SOURCES += ios/ios-share.mm + HEADERS += ios/ios-share.h + Q_ENABLE_BITCODE.name = ENABLE_BITCODE + Q_ENABLE_BITCODE.value = NO + QMAKE_MAC_XCODE_SETTINGS += Q_ENABLE_BITCODE + LIBS += ../install-root/ios/lib/libdivecomputer.a \ ../install-root/ios/lib/libgit2.a \ ../install-root/ios/lib/libzip.a \ @@ -417,6 +423,8 @@ ios { -lsqlite3 \ -lxml2 + LIBS += -framework MessageUI + INCLUDEPATH += ../install-root/ios/include/ \ ../install-root/lib/libzip/include \ ../install-root/ios/include/libxstl \ diff --git a/ios/ios-share.h b/ios/ios-share.h new file mode 100644 index 000000000..5c079221e --- /dev/null +++ b/ios/ios-share.h @@ -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 + +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 */ diff --git a/ios/ios-share.mm b/ios/ios-share.mm new file mode 100644 index 000000000..e063fb972 --- /dev/null +++ b/ios/ios-share.mm @@ -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 +#import +#import +#import + +// 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 +{ +} +- (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("in-app-support@subsurface-divelog.org"); + 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