Browse Source

fix sending from share extension, add sending progress UI in order to ensure extension runs until the message was sent

cyberta 5 years ago
parent
commit
9774325964

+ 4 - 0
DcCore/DcCore.xcodeproj/project.pbxproj

@@ -21,6 +21,7 @@
 		30421964243F0B8400516852 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30421963243F0B8400516852 /* String+Extensions.swift */; };
 		30421986243F209E00516852 /* events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30421985243F209E00516852 /* events.swift */; };
 		30421988243F23E500516852 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30421987243F23E500516852 /* Constants.swift */; };
+		304F5E41244F2F3200462538 /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304F5E40244F2F3200462538 /* UIImage+Extensions.swift */; };
 		306C324824460CDE001D89F3 /* DateUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306C324724460CDE001D89F3 /* DateUtils.swift */; };
 		30E8F2212447357500CE2C90 /* DatabaseHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F2202447357500CE2C90 /* DatabaseHelper.swift */; };
 		30E8F2482449C98600CE2C90 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F2472449C98600CE2C90 /* UIView+Extensions.swift */; };
@@ -56,6 +57,7 @@
 		30421963243F0B8400516852 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
 		30421985243F209E00516852 /* events.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = events.swift; path = ../../DcCore/DcCore/DC/events.swift; sourceTree = "<group>"; };
 		30421987243F23E500516852 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
+		304F5E40244F2F3200462538 /* UIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = "<group>"; };
 		306C324724460CDE001D89F3 /* DateUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateUtils.swift; sourceTree = "<group>"; };
 		30E8F2202447357500CE2C90 /* DatabaseHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseHelper.swift; sourceTree = "<group>"; };
 		30E8F2472449C98600CE2C90 /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
@@ -152,6 +154,7 @@
 				3042195F243E257100516852 /* UIColor+Extensions.swift */,
 				30421963243F0B8400516852 /* String+Extensions.swift */,
 				30E8F2472449C98600CE2C90 /* UIView+Extensions.swift */,
+				304F5E40244F2F3200462538 /* UIImage+Extensions.swift */,
 			);
 			path = Extensions;
 			sourceTree = "<group>";
@@ -321,6 +324,7 @@
 				30421964243F0B8400516852 /* String+Extensions.swift in Sources */,
 				30421960243E257100516852 /* UIColor+Extensions.swift in Sources */,
 				30E8F2482449C98600CE2C90 /* UIView+Extensions.swift in Sources */,
+				304F5E41244F2F3200462538 /* UIImage+Extensions.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 4 - 0
DcCore/DcCore/DC/Wrapper.swift

@@ -202,6 +202,10 @@ public class DcContext {
         dc_perform_mvbox_idle(contextPointer)
     }
 
+    public func performSmtpJobs() {
+        dc_perform_smtp_jobs(contextPointer)
+    }
+    
     public func performSmtp() {
         dc_perform_smtp_jobs(contextPointer)
         dc_perform_smtp_idle(contextPointer)

+ 65 - 0
DcCore/DcCore/Extensions/UIImage+Extensions.swift

@@ -0,0 +1,65 @@
+import UIKit
+
+public extension UIImage {
+
+    private func getResizedRectangle(toMax: Float) -> CGRect {
+        var actualHeight = Float(size.height)
+        var actualWidth = Float(size.width)
+        let maxHeight: Float = toMax
+        let maxWidth: Float = toMax
+        var imgRatio: Float = actualWidth / actualHeight
+        let maxRatio: Float = maxWidth / maxHeight
+        if actualHeight > maxHeight || actualWidth > maxWidth {
+            if imgRatio < maxRatio {
+                //adjust width according to maxHeight
+                imgRatio = maxHeight / actualHeight
+                actualWidth = imgRatio * actualWidth
+                actualHeight = maxHeight
+            } else if imgRatio > maxRatio {
+                //adjust height according to maxWidth
+                imgRatio = maxWidth / actualWidth
+                actualHeight = imgRatio * actualHeight
+                actualWidth = maxWidth
+            } else {
+                actualHeight = maxHeight
+                actualWidth = maxWidth
+            }
+        }
+        return CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
+    }
+
+    func scaleDownImage(toMax: CGFloat) -> UIImage? {
+        let rect = getResizedRectangle(toMax: Float(toMax))
+        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
+        draw(in: rect)
+        let newImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+        return newImage
+    }
+
+    // if an image has an alpha channel we try to keep it, using PNG formatting instead of JPEG
+    // PNGs are less compressed than JPEGs - to keep the message sizes small,
+    // the size of PNG imgaes will be scaled down
+    public func scaleDownAndCompress(toMax: Float) -> UIImage? {
+        let rect = getResizedRectangle(toMax: self.isTransparent() ?
+            min(Float(self.size.width) / 2, toMax / 2) :
+            toMax)
+
+        UIGraphicsBeginImageContextWithOptions(rect.size, !self.isTransparent(), 0.0)
+        draw(in: rect)
+        let img = UIGraphicsGetImageFromCurrentImageContext()
+
+        let imageData = self.isTransparent() ?
+            img?.pngData() :
+            img?.jpegData(compressionQuality: 0.85)
+
+        UIGraphicsEndImageContext()
+        return UIImage(data: imageData!)
+    }
+
+    func isTransparent() -> Bool {
+      guard let alpha: CGImageAlphaInfo = self.cgImage?.alphaInfo else { return false }
+      return alpha == .first || alpha == .last || alpha == .premultipliedFirst || alpha == .premultipliedLast
+    }
+
+}

+ 78 - 0
DcShare/SendingController.swift

@@ -0,0 +1,78 @@
+import UIKit
+import DcCore
+
+protocol SendingControllerDelegate: class {
+    func onSendingAttemptStopped()
+}
+
+class SendingController: UIViewController {
+
+    private let dcMsg: DcMsg
+    private let chatId: Int
+    private let dcContext: DcContext
+    weak var delegate: SendingControllerDelegate?
+
+    private var progressLabel: UILabel = {
+        let view = UILabel()
+        view.translatesAutoresizingMaskIntoConstraints = false
+        /// TODO: translation!
+        view.text = "Sending..."
+        return view
+    }()
+
+    private var activityIndicator: UIActivityIndicatorView = {
+        let view: UIActivityIndicatorView
+        if #available(iOS 13, *) {
+             view = UIActivityIndicatorView(style: .large)
+        } else {
+            view = UIActivityIndicatorView(style: .whiteLarge)
+            view.color = UIColor.gray
+        }
+        view.startAnimating()
+        view.translatesAutoresizingMaskIntoConstraints = false
+        return view
+    }()
+
+    init(chatId: Int, dcMsg: DcMsg, dcContext: DcContext) {
+        self.chatId = chatId
+        self.dcMsg = dcMsg
+        self.dcContext = dcContext
+        super.init(nibName: nil, bundle: nil)
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+
+    override func viewDidLoad() {
+        view.backgroundColor = DcColors.defaultBackgroundColor
+        setupViews()
+        sendMessage()
+    }
+
+    private func setupViews() {
+        view.addSubview(progressLabel)
+        view.addSubview(activityIndicator)
+        view.addConstraints([
+            progressLabel.constraintCenterXTo(view),
+            progressLabel.constraintAlignTopTo(view, paddingTop: 25),
+            activityIndicator.constraintCenterXTo(view),
+            activityIndicator.constraintCenterYTo(view)
+        ])
+        setupNavigationBar()
+    }
+
+    private func setupNavigationBar() {
+        self.navigationItem.leftBarButtonItem = UIBarButtonItem()
+        self.navigationItem.titleView = UIImageView(image: UIImage(named: "ic_chat")?.scaleDownImage(toMax: 26))
+    }
+
+    private func sendMessage() {
+        DispatchQueue.global(qos: .utility).async {
+            self.dcMsg.sendInChat(id: self.chatId)
+            self.dcContext.performSmtpJobs()
+            self.delegate?.onSendingAttemptStopped()
+        }
+    }
+}

+ 40 - 10
DcShare/ShareViewController.swift

@@ -31,9 +31,11 @@ class ShareViewController: SLComposeServiceViewController {
     let dcContext = DcContext.shared
     var selectedChatId: Int?
     var selectedChat: DcChat?
+    let dbHelper = DatabaseHelper()
 
     override func viewDidLoad() {
         super.viewDidLoad()
+        setupNavigationBar()
         // workaround for iOS13 bug
         if #available(iOS 13.0, *) {
             _ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { (_) in
@@ -45,8 +47,6 @@ class ShareViewController: SLComposeServiceViewController {
     }
 
     override func presentationAnimationDidFinish() {
-
-        let dbHelper = DatabaseHelper()
         if dbHelper.currentDatabaseLocation == dbHelper.sharedDbFile {
             dcContext.logger = self.logger
             dcContext.openDatabase(dbFile: dbHelper.sharedDbFile)
@@ -65,18 +65,35 @@ class ShareViewController: SLComposeServiceViewController {
         return  !(contentText?.isEmpty ?? true)
     }
 
-    override func didSelectPost() {
-        // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
-        logger.debug("did select post - \(String(describing: selectedChatId))")
-        if let chatId = selectedChatId {
+    private func setupNavigationBar() {
+        guard let item = navigationController?.navigationBar.items?.first else { return }
+        let button = UIBarButtonItem(
+            title: String.localized("menu_send"),
+            style: .done,
+            target: self,
+            action: #selector(appendPostTapped))
+        item.rightBarButtonItem? = button
+        item.titleView = UIImageView(image: UIImage(named: "ic_chat")?.scaleDownImage(toMax: 26))
+    }
+
+    /// Invoked when the user wants to post.
+    @objc
+    private func appendPostTapped() {
+        if let chatId = self.selectedChatId {
             let message = DcMsg(viewType: DC_MSG_TEXT)
             message.text = self.contentText
-            message.sendInChat(id: chatId)
+            let chatListController = SendingController(chatId: chatId, dcMsg: message, dcContext: dcContext)
+            chatListController.delegate = self
+            self.pushConfigurationViewController(chatListController)
+        }
+    }
+
+    func quit() {
+        if dbHelper.currentDatabaseLocation == dbHelper.sharedDbFile {
+            dcContext.closeDatabase()
         }
 
-        logger.debug("did select post - closeDatabase")
-        dcContext.closeDatabase()
-        // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
+        // Inform the host that we're done, so it un-blocks its UI.
         self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
     }
 
@@ -95,6 +112,10 @@ class ShareViewController: SLComposeServiceViewController {
 
         return [item as Any]
     }
+
+    override func didSelectCancel() {
+        quit()
+    }
 }
 
 extension ShareViewController: ChatListDelegate {
@@ -105,3 +126,12 @@ extension ShareViewController: ChatListDelegate {
         popConfigurationViewController()
     }
 }
+
+extension ShareViewController: SendingControllerDelegate {
+    func onSendingAttemptStopped() {
+        DispatchQueue.main.async {
+            self.popConfigurationViewController()
+            self.quit()
+        }
+    }
+}

+ 6 - 0
deltachat-ios.xcodeproj/project.pbxproj

@@ -21,6 +21,7 @@
 		3040F462234F550300FA34D5 /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3040F461234F550300FA34D5 /* AudioPlayerView.swift */; };
 		304219D3243F588500516852 /* DcCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 304219D1243F588500516852 /* DcCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		304219D92440734A00516852 /* DcMsg+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304219D82440734A00516852 /* DcMsg+Extension.swift */; };
+		304F5E44244F571C00462538 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A9FB14A1FB061E2001FEA36 /* Assets.xcassets */; };
 		305961CC2346125100C80F33 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961822346125000C80F33 /* UIView+Extensions.swift */; };
 		305961CD2346125100C80F33 /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961832346125000C80F33 /* UIEdgeInsets+Extensions.swift */; };
 		305961CF2346125100C80F33 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961852346125000C80F33 /* UIColor+Extensions.swift */; };
@@ -102,6 +103,7 @@
 		30E8F2422448B77600CE2C90 /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F2412448B77600CE2C90 /* ChatListController.swift */; };
 		30E8F2442449C64100CE2C90 /* ChatListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F2432449C64100CE2C90 /* ChatListCell.swift */; };
 		30E8F2512449EA0E00CE2C90 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; };
+		30E8F253244DAD0E00CE2C90 /* SendingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F252244DAD0E00CE2C90 /* SendingController.swift */; };
 		30F9B9EC235F2116006E7ACF /* MessageCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F9B9EB235F2116006E7ACF /* MessageCounter.swift */; };
 		6795F63A82E94FF7CD2CEC0F /* Pods_deltachat_iosTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F7009234DB9408201A6CDCB /* Pods_deltachat_iosTests.framework */; };
 		7070FB9B2101ECBB000DC258 /* NewGroupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7070FB9A2101ECBB000DC258 /* NewGroupController.swift */; };
@@ -379,6 +381,7 @@
 		30E8F21F24472AAE00CE2C90 /* DcShare.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DcShare.entitlements; sourceTree = "<group>"; };
 		30E8F2412448B77600CE2C90 /* ChatListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = "<group>"; };
 		30E8F2432449C64100CE2C90 /* ChatListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListCell.swift; sourceTree = "<group>"; };
+		30E8F252244DAD0E00CE2C90 /* SendingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendingController.swift; sourceTree = "<group>"; };
 		30F9B9EB235F2116006E7ACF /* MessageCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCounter.swift; sourceTree = "<group>"; };
 		6241BE1534A653E79AD5D01D /* Pods_deltachat_ios.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_deltachat_ios.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		7070FB9A2101ECBB000DC258 /* NewGroupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupController.swift; sourceTree = "<group>"; };
@@ -690,6 +693,7 @@
 				30E8F2142447285600CE2C90 /* MainInterface.storyboard */,
 				30E8F2172447285600CE2C90 /* Info.plist */,
 				30E8F2432449C64100CE2C90 /* ChatListCell.swift */,
+				30E8F252244DAD0E00CE2C90 /* SendingController.swift */,
 			);
 			path = DcShare;
 			sourceTree = "<group>";
@@ -1087,6 +1091,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				30E8F2162447285600CE2C90 /* MainInterface.storyboard in Resources */,
+				304F5E44244F571C00462538 /* Assets.xcassets in Resources */,
 				30E8F2512449EA0E00CE2C90 /* Localizable.strings in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -1249,6 +1254,7 @@
 			files = (
 				30E8F2442449C64100CE2C90 /* ChatListCell.swift in Sources */,
 				30E8F2132447285600CE2C90 /* ShareViewController.swift in Sources */,
+				30E8F253244DAD0E00CE2C90 /* SendingController.swift in Sources */,
 				30E8F2422448B77600CE2C90 /* ChatListController.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 1 - 60
deltachat-ios/Extensions/UIImage+Extension.swift

@@ -1,4 +1,5 @@
 import UIKit
+import DcCore
 
 extension UIImage {
 
@@ -36,66 +37,6 @@ extension UIImage {
         return CGSize(width: widthInPixels, height: heightInPixels)
     }
 
-    private func getResizedRectangle(toMax: Float) -> CGRect {
-        var actualHeight = Float(size.height)
-        var actualWidth = Float(size.width)
-        let maxHeight: Float = toMax
-        let maxWidth: Float = toMax
-        var imgRatio: Float = actualWidth / actualHeight
-        let maxRatio: Float = maxWidth / maxHeight
-        if actualHeight > maxHeight || actualWidth > maxWidth {
-            if imgRatio < maxRatio {
-                //adjust width according to maxHeight
-                imgRatio = maxHeight / actualHeight
-                actualWidth = imgRatio * actualWidth
-                actualHeight = maxHeight
-            } else if imgRatio > maxRatio {
-                //adjust height according to maxWidth
-                imgRatio = maxWidth / actualWidth
-                actualHeight = imgRatio * actualHeight
-                actualWidth = maxWidth
-            } else {
-                actualHeight = maxHeight
-                actualWidth = maxWidth
-            }
-        }
-        return CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
-    }
-
-    func scaleDownImage(toMax: CGFloat) -> UIImage? {
-        let rect = getResizedRectangle(toMax: Float(toMax))
-        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
-        draw(in: rect)
-        let newImage = UIGraphicsGetImageFromCurrentImageContext()
-        UIGraphicsEndImageContext()
-        return newImage
-    }
-
-    // if an image has an alpha channel we try to keep it, using PNG formatting instead of JPEG
-    // PNGs are less compressed than JPEGs - to keep the message sizes small,
-    // the size of PNG imgaes will be scaled down
-    func scaleDownAndCompress(toMax: Float) -> UIImage? {
-        let rect = getResizedRectangle(toMax: self.isTransparent() ?
-            min(Float(self.size.width) / 2, toMax / 2) :
-            toMax)
-
-        UIGraphicsBeginImageContextWithOptions(rect.size, !self.isTransparent(), 0.0)
-        draw(in: rect)
-        let img = UIGraphicsGetImageFromCurrentImageContext()
-
-        let imageData = self.isTransparent() ?
-            img?.pngData() :
-            img?.jpegData(compressionQuality: 0.85)
-
-        UIGraphicsEndImageContext()
-        return UIImage(data: imageData!)
-    }
-
-    public func isTransparent() -> Bool {
-        guard let alpha: CGImageAlphaInfo = self.cgImage?.alphaInfo else { return false }
-        return alpha == .first || alpha == .last || alpha == .premultipliedFirst || alpha == .premultipliedLast
-      }
-
 }
 
 public enum ImageType: String {