[ios] move mail composing logic to the MailComposer class

to reuse both in the About screen and the Cloud sync manager

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
This commit is contained in:
Kiryl Kaveryn 2024-07-22 21:40:17 +04:00 committed by Roman Tsisyk
parent 69bc067ca9
commit edf982ae7d
3 changed files with 129 additions and 114 deletions

View file

@ -487,6 +487,7 @@
ED7CCC4F2C1362E300E2A737 /* FileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED7CCC4E2C1362E300E2A737 /* FileType.swift */; };
ED808D0F2C38407800D52585 /* CircleImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED808D0E2C38407800D52585 /* CircleImageButton.swift */; };
ED8270F02C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8270EF2C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift */; };
ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9857072C4ED02D00694F6C /* MailComposer.swift */; };
ED9966802B94FBC20083CE55 /* ColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED99667D2B94FBC20083CE55 /* ColorPicker.swift */; };
EDBD68072B625724005DD151 /* LocationServicesDisabledAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */; };
EDBD680B2B62572E005DD151 /* LocationServicesDisabledAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */; };
@ -1406,6 +1407,7 @@
ED7CCC4E2C1362E300E2A737 /* FileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileType.swift; sourceTree = "<group>"; };
ED808D0E2C38407800D52585 /* CircleImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleImageButton.swift; sourceTree = "<group>"; };
ED8270EF2C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewDetailedSwitchCell.swift; sourceTree = "<group>"; };
ED9857072C4ED02D00694F6C /* MailComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposer.swift; sourceTree = "<group>"; };
ED99667D2B94FBC20083CE55 /* ColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPicker.swift; sourceTree = "<group>"; };
EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LocationServicesDisabledAlert.xib; sourceTree = "<group>"; };
EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServicesDisabledAlert.swift; sourceTree = "<group>"; };
@ -3106,6 +3108,14 @@
path = iCloud;
sourceTree = "<group>";
};
ED9857022C4ECFFC00694F6C /* MailComposer */ = {
isa = PBXGroup;
children = (
ED9857072C4ED02D00694F6C /* MailComposer.swift */,
);
path = MailComposer;
sourceTree = "<group>";
};
ED99667C2B94FBC20083CE55 /* ColorPicker */ = {
isa = PBXGroup;
children = (
@ -3366,6 +3376,7 @@
F6E2FBFB1E097B9F0083EBEC /* UI */ = {
isa = PBXGroup;
children = (
ED9857022C4ECFFC00694F6C /* MailComposer */,
ED43B8B92C12061600D07BAA /* DocumentPicker */,
ED99667C2B94FBC20083CE55 /* ColorPicker */,
F69018B51E9E5FEB00B3C10B /* Autoupdate */,
@ -4557,6 +4568,7 @@
3404164C1E7BF42E00E2B6D6 /* UIView+Coordinates.swift in Sources */,
99F3EB0323F4178200C713F8 /* PlacePageCommonLayout.swift in Sources */,
99C6532223F2F506004322F3 /* IPlacePageLayout.swift in Sources */,
ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */,
99F8B4C623B644A6009FF0B4 /* MapStyleSheet.swift in Sources */,
99012851244732DB00C72B10 /* BottomTabBarViewController.swift in Sources */,
F63AF5061EA6162400A1DB98 /* FilterTypeCell.swift in Sources */,

View file

@ -282,7 +282,7 @@ private extension AboutController {
let logFileURL = Logger.getLogFileURL()
UIApplication.shared.hideLoadingOverlay {
guard let self else { return }
self.sendEmailWith(header: "Organic Maps Bugreport", toRecipients: [link], attachmentFileURL: logFileURL)
MailComposer.default.sendEmailWith(header: "Organic Maps Bugreport", toRecipients: [link], attachmentFileURL: logFileURL)
}
}
case .reportMapDataProblem, .volunteer, .news:
@ -314,7 +314,7 @@ private extension AboutController {
self?.openUrl(socialMedia.link, externally: true)
case .organicMapsEmail:
guard let link = socialMedia.link else { fatalError("The Organic Maps email link should be provided.") }
self?.sendEmailWith(header: "Organic Maps", toRecipients: [link])
MailComposer.default.sendEmailWith(header: "Organic Maps", toRecipients: [link])
}
})
}
@ -441,111 +441,6 @@ extension AboutController: UICollectionViewDelegateFlowLayout {
return Constants.socialMediaCollectionViewSpacing
}
}
// MARK: - Mail Composing
private extension AboutController {
func sendEmailWith(header: String, toRecipients: [String], attachmentFileURL: URL? = nil) {
func emailSubject(subject: String) -> String {
let appInfo = AppInfo.shared()
return String(format:"[%@-%@ iOS] %@", appInfo.bundleVersion, appInfo.buildNumber, subject)
}
func emailBody() -> String {
let appInfo = AppInfo.shared()
return String(format: "\n\n\n\n- %@ (%@)\n- Organic Maps %@-%@\n- %@-%@\n- %@\n",
appInfo.deviceModel, UIDevice.current.systemVersion,
appInfo.bundleVersion, appInfo.buildNumber,
Locale.current.languageCode ?? "",
Locale.current.regionCode ?? "",
Locale.preferredLanguages.joined(separator: ", "))
}
func openOutlook(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "ms-outlook://compose")!
components.queryItems = [
URLQueryItem(name: "to", value: recipients.joined(separator: ";")),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
if let url = components.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func openGmail(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "googlegmail://co")!
components.queryItems = [
URLQueryItem(name: "to", value: recipients.joined(separator: ";")),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
if let url = components.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func openDefaultMailApp(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "mailto:\(recipients.joined(separator: ";"))")
components?.queryItems = [
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body.replacingOccurrences(of: "\n", with: "\r\n")),
]
if let url = components?.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func showMailComposingAlert() {
let text = String(format:L("email_error_body"), toRecipients.joined(separator: ";"))
let alert = UIAlertController(title: L("email_error_title"), message: text, preferredStyle: .alert)
let action = UIAlertAction(title: L("ok"), style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
let subject = emailSubject(subject: header)
let body = emailBody()
// If the attachment file path is provided, the default mail composer should be used.
if let attachmentFileURL {
if MWMMailViewController.canSendMail(), let attachmentData = try? Data(contentsOf: attachmentFileURL) {
let mailViewController = MWMMailViewController()
mailViewController.mailComposeDelegate = self
mailViewController.setSubject(subject)
mailViewController.setMessageBody(body, isHTML:false)
mailViewController.setToRecipients(toRecipients)
mailViewController.addAttachmentData(attachmentData, mimeType: "application/zip", fileName: attachmentFileURL.lastPathComponent)
self.present(mailViewController, animated: true, completion:nil)
} else {
showMailComposingAlert()
}
return
}
// Before iOS 14, try to open alternate email apps first, assuming that if users installed them, they're using them.
let os = ProcessInfo().operatingSystemVersion
if (os.majorVersion < 14 && (openGmail(subject: subject, body: body, recipients: toRecipients) ||
openOutlook(subject: subject, body: body, recipients: toRecipients))) {
return
}
// From iOS 14, it is possible to change the default mail app, and mailto should open a default mail app.
if !openDefaultMailApp(subject: subject, body: body, recipients: toRecipients) {
showMailComposingAlert()
}
}
}
// MARK: - UIStackView + AddArrangedSubviewWithSeparator
private extension UIStackView {
func addArrangedSubviewWithSeparator(_ view: UIView) {
@ -564,10 +459,3 @@ private extension UIStackView {
addArrangedSubview(view)
}
}
// MARK: - MFMailComposeViewControllerDelegate
extension AboutController: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
dismiss(animated: true, completion: nil)
}
}

View file

@ -0,0 +1,115 @@
final class MailComposer: NSObject {
static let `default` = MailComposer()
private var topViewController: UIViewController { UIViewController.topViewController() }
private override init() {}
func sendEmailWith(header: String, toRecipients: [String], attachmentFileURL: URL? = nil) {
func emailSubject(subject: String) -> String {
let appInfo = AppInfo.shared()
return String(format:"[%@-%@ iOS] %@", appInfo.bundleVersion, appInfo.buildNumber, subject)
}
func emailBody() -> String {
let appInfo = AppInfo.shared()
return String(format: "\n\n\n\n- %@ (%@)\n- Organic Maps %@-%@\n- %@-%@\n- %@\n",
appInfo.deviceModel, UIDevice.current.systemVersion,
appInfo.bundleVersion, appInfo.buildNumber,
Locale.current.languageCode ?? "",
Locale.current.regionCode ?? "",
Locale.preferredLanguages.joined(separator: ", "))
}
func openOutlook(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "ms-outlook://compose")!
components.queryItems = [
URLQueryItem(name: "to", value: recipients.joined(separator: ";")),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
if let url = components.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func openGmail(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "googlegmail://co")!
components.queryItems = [
URLQueryItem(name: "to", value: recipients.joined(separator: ";")),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
if let url = components.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func openDefaultMailApp(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "mailto:\(recipients.joined(separator: ";"))")
components?.queryItems = [
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body.replacingOccurrences(of: "\n", with: "\r\n")),
]
if let url = components?.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func showMailComposingAlert() {
let text = String(format:L("email_error_body"), toRecipients.joined(separator: ";"))
let alert = UIAlertController(title: L("email_error_title"), message: text, preferredStyle: .alert)
let action = UIAlertAction(title: L("ok"), style: .default, handler: nil)
alert.addAction(action)
topViewController.present(alert, animated: true, completion: nil)
}
let subject = emailSubject(subject: header)
let body = emailBody()
// If the attachment file path is provided, the default mail composer should be used.
if let attachmentFileURL {
if MWMMailViewController.canSendMail(), let attachmentData = try? Data(contentsOf: attachmentFileURL) {
let mailViewController = MWMMailViewController()
mailViewController.mailComposeDelegate = self
mailViewController.setSubject(subject)
mailViewController.setMessageBody(body, isHTML:false)
mailViewController.setToRecipients(toRecipients)
mailViewController.addAttachmentData(attachmentData, mimeType: "application/zip", fileName: attachmentFileURL.lastPathComponent)
topViewController.present(mailViewController, animated: true, completion:nil)
} else {
showMailComposingAlert()
}
return
}
// Before iOS 14, try to open alternate email apps first, assuming that if users installed them, they're using them.
let os = ProcessInfo().operatingSystemVersion
if (os.majorVersion < 14 && (openGmail(subject: subject, body: body, recipients: toRecipients) ||
openOutlook(subject: subject, body: body, recipients: toRecipients))) {
return
}
// From iOS 14, it is possible to change the default mail app, and mailto should open a default mail app.
if !openDefaultMailApp(subject: subject, body: body, recipients: toRecipients) {
showMailComposingAlert()
}
}
}
// MARK: - MFMailComposeViewControllerDelegate
extension MailComposer: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}