// // BaseViewController.swift // Lockdown // // Copyright © 2019 Confirmed Inc. All rights reserved. // import UIKit import MessageUI import CocoaLumberjackSwift import PopupDialog open class BaseViewController: UIViewController, MFMailComposeViewControllerDelegate { let interactionBlockViewTag = 84814 override open func viewDidLoad() { super.viewDidLoad() // disable swipe down to dismiss if #available(iOS 13.0, *) { self.isModalInPresentation = true } // let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(emailTeam)) // longPressRecognizer.minimumPressDuration = 4 // self.view.addGestureRecognizer(longPressRecognizer) // let doubleLongPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(signoutUser)) // doubleLongPressRecognizer.minimumPressDuration = 5 // doubleLongPressRecognizer.numberOfTouchesRequired = 2 // self.view.addGestureRecognizer(doubleLongPressRecognizer) } // MARK: - AwesomeSpotlight Helper func getRectForView(_ v: UIView) -> CGRect { if let sv = v.superview { return sv.convert(v.frame, to: self.view) } return CGRect.zero; } // MARK: - Handle NSURLError and APIErrors func popupErrorAsNSURLError(_ error: Error) -> Bool { let nsError = error as NSError if nsError.domain == NSURLErrorDomain { self.showPopupDialog(title: NSLocalizedString("Network Error", comment: ""), message: NSLocalizedString("Please check your internet connection. If this persists, please contact team@lockdownprivacy.com.\n\nError Description\n", comment: "") + nsError.localizedDescription, acceptButton: NSLocalizedString("Okay", comment: "")) return true } else { return false } } func popupErrorAsApiError(_ error: Error) -> Bool { if let e = error as? ApiError { self.showPopupDialog(title: NSLocalizedString("Error Code ", comment: "") + "\(e.code)", message: "\(e.message)" + NSLocalizedString("\n\n If this persists, please contact team@lockdownprivacy.com.", comment: ""), acceptButton: NSLocalizedString("Okay", comment: "")) return true } else { return false } } func showWhyTrustPopup() { let popup = PopupDialog( title: NSLocalizedString("Why Trust Lockdown?", comment: ""), message: NSLocalizedString("Lockdown is open source and fully transparent, which means anyone can see exactly what it's doing. Also, Lockdown Firewall has a simple, strict Privacy Policy, while Lockdown VPN is fully audited by security experts.", comment: ""), image: UIImage(named: "whyTrustImage")!, buttonAlignment: .vertical, transitionStyle: .bounceDown, preferredWidth: 300.0, tapGestureDismissal: true, panGestureDismissal: false, hideStatusBar: true, completion: nil) let privacyPolicyButton = DefaultButton(title: NSLocalizedString("Privacy Policy", comment: ""), dismissOnTap: true) { self.showPrivacyPolicyModal() } let auditReportsButton = DefaultButton(title: NSLocalizedString("Audit Reports", comment: ""), dismissOnTap: true) { self.showAuditModal() } let pressButton = DefaultButton(title: NSLocalizedString("Press & Media", comment: ""), dismissOnTap: true) { self.showWebsitePressModal() } let okayButton = CancelButton(title: NSLocalizedString("Done", comment: ""), dismissOnTap: true) { } popup.addButtons([privacyPolicyButton, auditReportsButton, pressButton, okayButton]) self.present(popup, animated: true, completion: nil) } func showVPNDetails() { self.showModalWebView(title: NSLocalizedString("Secure Tunnel VPN", comment: ""), urlString: "https://lockdownprivacy.com/secure-tunnel") // let popup = PopupDialog( // title: NSLocalizedString("About Lockdown VPN", comment: ""), // message: NSLocalizedString("Lockdown VPN is powered by Confirmed VPN, the open source, no-logs, and fully audited VPN.", comment: ""), // buttonAlignment: .vertical, // transitionStyle: .bounceDown, // preferredWidth: 300.0, // tapGestureDismissal: true, // panGestureDismissal: false, // hideStatusBar: true, // completion: nil) // // let whyUseVPNButton = DefaultButton(title: NSLocalizedString("Why Use VPN?", comment: ""), dismissOnTap: true) { // self.showModalWebView(title: NSLocalizedString("Why Use VPN?", comment: ""), urlString: "https://confirmedvpn.com/why-vpn") // } // // let auditReportsButton = DefaultButton(title: NSLocalizedString("Audit Reports", comment: ""), dismissOnTap: true) { // self.showAuditModal() // } // // let confirmedWebsiteButton = DefaultButton(title: NSLocalizedString("Confirmed Site", comment: ""), dismissOnTap: true) { // self.showModalWebView(title: NSLocalizedString("Why Use VPN?", comment: ""), urlString: "https://confirmedvpn.com") // } // let okayButton = CancelButton(title: NSLocalizedString("Done", comment: ""), dismissOnTap: true) { } // popup.addButtons([whyUseVPNButton, auditReportsButton, confirmedWebsiteButton, okayButton]) // // self.present(popup, animated: true, completion: nil) } // MARK: - WebView func showWhatsNewModal() { let vc = WhatsNewViewController() present(vc, animated: true) } func showPrivacyPolicyModal() { self.showModalWebView(title: NSLocalizedString("Privacy Policy", comment: ""), urlString: "https://lockdownprivacy.com/privacy") } func showTermsModal() { self.showModalWebView(title: NSLocalizedString("Terms", comment: ""), urlString: "https://lockdownprivacy.com/terms") } func showFAQsModal() { self.showModalWebView(title: NSLocalizedString("FAQs", comment: ""), urlString: "https://lockdownprivacy.com/faq") } func showWebsiteModal() { self.showModalWebView(title: NSLocalizedString("Website", comment: ""), urlString: "https://lockdownprivacy.com") } func showWebsitePressModal() { self.showModalWebView(title: NSLocalizedString("Press & Media", comment: ""), urlString: "https://lockdownprivacy.com/#press") } func showAuditModal() { self.showModalWebView(title: NSLocalizedString("Audit Reports", comment: ""), urlString: "https://openaudit.com/lockdownprivacy") } func showModalWebView(title: String, urlString: String) { if let url = URL(string: urlString) { let storyboardToUse = storyboard != nil ? storyboard! : UIStoryboard(name: "Main", bundle: nil) if let webViewVC = storyboardToUse.instantiateViewController(withIdentifier: "webview") as? WebViewViewController { webViewVC.titleLabelText = title webViewVC.url = url self.present(webViewVC, animated: true, completion: nil) } else { DDLogError("Unable to instantiate webview VC") } } else { DDLogError("Invalid URL \(urlString)") } } // MARK: - Block user interactions during transactions func unblockUserInteraction() { let view = self.view.viewWithTag(interactionBlockViewTag) if view != nil { view?.removeFromSuperview() } } func blockUserInteraction() { let view = UIView(frame: self.view.frame) view.tag = interactionBlockViewTag view.backgroundColor = UIColor.init(white: 1.0, alpha: 0.0) self.view.addSubview(view) } // MARK: - Popup Helper func showPopupDialog(title: String, message: String, acceptButton: String, completionHandler: @escaping () -> () = {}) { let popup = PopupDialog(title: title.uppercased(), message: message, image: nil, transitionStyle: .bounceDown, hideStatusBar: false) let acceptButton = DefaultButton(title: NSLocalizedString("OK", comment: ""), dismissOnTap: true) { completionHandler() } popup.addButtons([acceptButton]) let topVC = presentedViewController ?? self topVC.present(popup, animated: true, completion: nil) } enum PopupButton { case custom(PopupDialogButton) case defaultAccept(completion: () -> ()) static func custom(title: String, titleColor: UIColor? = nil, completion: @escaping () -> ()) -> PopupButton { let button = DefaultButton(title: title, dismissOnTap: true, action: completion) if let color = titleColor { button.titleColor = color } return .custom(button) } static func destructive(title: String, completion: @escaping () -> ()) -> PopupButton { return .custom(title: title, titleColor: UIColor.systemRed, completion: completion) } static func cancel(completion: @escaping () -> () = { }) -> PopupButton { return .custom(CancelButton(title: NSLocalizedString("Cancel", comment: ""), dismissOnTap: true, action: completion)) } static func preferredCancel(completion: @escaping () -> () = { }) -> PopupButton { return .custom(title: NSLocalizedString("Cancel", comment: ""), titleColor: nil, completion: completion) } fileprivate func makeButton() -> PopupDialogButton { switch self { case .custom(let button): return button case .defaultAccept(completion: let completion): let acceptButton = DefaultButton(title: NSLocalizedString("OK", comment: ""), dismissOnTap: true) { completion() } return acceptButton } } } func showPopupDialog(title: String?, message: String?, buttons: [PopupButton]) { let popup = PopupDialog(title: title?.uppercased(), message: message, image: nil, transitionStyle: .bounceDown, tapGestureDismissal: false, panGestureDismissal: false, hideStatusBar: false) for action in buttons { let button = action.makeButton() popup.addButton(button) } self.present(popup, animated: true, completion: nil) } func showFixFirewallConnectionDialog(completion: @escaping () -> ()) { VPNController.shared.isConfigurationExisting { (exists) in if exists { // if VPN configuration exists, the system will not show an alert, // so we do need to warn users about it completion() } else { // if there is no existing VPN configuration, // we need to show a dialog explaining the // upcoming popup self.showPopupDialog( title: "Tap \"Allow\" on the Next Popup", message: "Due to a recent iOS or Lockdown update, the Firewall needs to be refreshed to run properly.\n\nIf asked, tap \"Allow\" on the next dialog to automatically complete this process.", buttons: [ .cancel(), .defaultAccept(completion: { completion() }) ] ) } } } // func showPopupDialogSubmitError(title : String = "Sorry, An Error Occurred", message : String, error: Error?) { // let popup = PopupDialog(title: title, message: message, image: nil, transitionStyle: .zoomIn, hideStatusBar: false) // let acceptButton = DefaultButton(title: "Don't Submit", dismissOnTap: true) { } // let submitButton = DefaultButton(title: "Submit", dismissOnTap: true) { // self.emailTeam(messageBody: "Hey Lockdown Team, \nI encountered a bug while using Lockdown, and I'm reporting it here. \n (To the user: just tap Send at the top right to submit the bug report -- no need to do anything else and we'll get back to you ASAP.", messageErrorBody: error ? error! || "") // } // popup.addButtons([acceptButton, submitButton]) // self.present(popup, animated: true, completion: nil) // } @objc func emailTeam(messageBody: String = NSLocalizedString("Hi, my question or feedback for Lockdown is: ", comment: ""), messageErrorBody: String = "") { DDLogInfo("") DDLogInfo("UserId: \(keychain[kVPNCredentialsId] ?? "No User ID")") DDLogInfo("UserReceipt: \(keychain[kVPNCredentialsKeyBase64] ?? "No User Receipt")") if (Client.hasValidCookie()) { DDLogInfo("Has loaded cookie.") } DDLogInfo("") PacketTunnelProviderLogs.flush() DDLogInfo("") var appendString = "" if (getUserWantsVPNEnabled()) { appendString = appendString + " - S" } let subject = "Lockdown Question or Feedback (iOS \(Bundle.main.versionString))" + appendString var message = messageBody if messageErrorBody != "" { message = messageBody + "\n\nError Details: " + messageErrorBody } message += "\n\n\n" sendMessage(message, subject: subject) } func sendMessage(_ message: String, subject: String) { let recipient = "team@lockdownprivacy.com" // TODO: change email if MFMailComposeViewController.canSendMail() { let composeVC = MFMailComposeViewController() composeVC.mailComposeDelegate = self composeVC.setToRecipients([recipient]) composeVC.setSubject(subject) composeVC.setMessageBody(message, isHTML: false) let attachmentData = NSMutableData() for logFileData in logFileDataArray { attachmentData.append(logFileData as Data) } composeVC.addAttachmentData(attachmentData as Data, mimeType: "text/plain", fileName: "diagnostics.txt") let topVC = presentedViewController ?? self topVC.present(composeVC, animated: true, completion: nil) } else { guard let mailtoURL = Mailto.generateURL(recipient: recipient, subject: subject, body: message) else { DDLogError("Failed to generate mailto url") return } UIApplication.shared.open(mailtoURL, options: [:]) { (success) in if !success { self.showPopupDialog( title: NSLocalizedString("Couldn't Find Your Email Client", comment: ""), message: NSLocalizedString("Please make sure you have added an e-mail account to your iOS device and try again.", comment: ""), acceptButton: NSLocalizedString("OK", comment: "") ) } } } } // @objc func signoutUser() { // // TODO: complete this debug functionality // let title = "CLEAR RECEIPT DATA?" // let message = "Would you like to clear your local receipts?" // // let popup = PopupDialog(title: title, message: message, image: nil, buttonAlignment: .horizontal) // // let acceptButton = DefaultButton(title: "YES", dismissOnTap: true) { // // Auth.clearCookies() // // Auth.signoutUser() // } // let cancelButton = DefaultButton(title: "CANCEL", dismissOnTap: true) { } // popup.addButtons([cancelButton, acceptButton]) // // self.present(popup, animated: true, completion: nil) // } // MARK: - Email Team public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { controller.dismiss(animated: true) { [weak self] in self?.actionUponEmailComposeClosure() } } func actionUponEmailComposeClosure() {} } extension UIStoryboard { static let main = UIStoryboard(name: "Main", bundle: nil) }