Эх сурвалжийг харах

implement editing bar, cancel multi-select mode, allows delete multiple messagesd

cyberta 4 жил өмнө
parent
commit
06d543a070

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

@@ -266,6 +266,10 @@ public class DcContext {
         dc_delete_msgs(contextPointer, [UInt32(msgId)], 1)
     }
 
+    public func deleteMessages(msgIds: [Int]) {
+        dc_delete_msgs(contextPointer, msgIds.compactMap{ UInt32($0) }, Int32(msgIds.count))
+    }
+
     public func forwardMessage(with msgId: Int, to chat: Int) {
         dc_forward_msgs(contextPointer, [UInt32(msgId)], 1, UInt32(chat))
     }

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

@@ -55,6 +55,7 @@
 		30653081254358B10093E196 /* QuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30653080254358B10093E196 /* QuoteView.swift */; };
 		306C32322445CDE9001D89F3 /* DcLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306C32312445CDE9001D89F3 /* DcLogger.swift */; };
 		30734326249A280B00BF9AD1 /* MediaQualityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30734325249A280B00BF9AD1 /* MediaQualityController.swift */; };
+		307A82CC25B8D26700748B57 /* ChatEditingBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A82CB25B8D26700748B57 /* ChatEditingBar.swift */; };
 		307D822E241669C7006D2490 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307D822D241669C7006D2490 /* LocationManager.swift */; };
 		3095A351237DD1F700AB07F7 /* MediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3095A350237DD1F700AB07F7 /* MediaPicker.swift */; };
 		30A4149724F6EFBE00EC91EB /* InfoMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A4149624F6EFBE00EC91EB /* InfoMessageCell.swift */; };
@@ -291,6 +292,7 @@
 		30653080254358B10093E196 /* QuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView.swift; sourceTree = "<group>"; };
 		306C32312445CDE9001D89F3 /* DcLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DcLogger.swift; sourceTree = "<group>"; };
 		30734325249A280B00BF9AD1 /* MediaQualityController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaQualityController.swift; sourceTree = "<group>"; };
+		307A82CB25B8D26700748B57 /* ChatEditingBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatEditingBar.swift; sourceTree = "<group>"; };
 		307D822D241669C7006D2490 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
 		3095A350237DD1F700AB07F7 /* MediaPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPicker.swift; sourceTree = "<group>"; };
 		30A4149624F6EFBE00EC91EB /* InfoMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMessageCell.swift; sourceTree = "<group>"; };
@@ -558,6 +560,7 @@
 				30FDB6B624D193DD0066C48D /* Cells */,
 				30E348DE24F3F819005C93D1 /* ChatTableView.swift */,
 				303492CE2587C2DC00A523D0 /* ChatInputBar.swift */,
+				307A82CB25B8D26700748B57 /* ChatEditingBar.swift */,
 				302E1BB3252B5AB4008F4264 /* PlayButtonView.swift */,
 				30F8817524DA97DA0023780E /* BackgroundContainer.swift */,
 				3008CB7324F9436C00E6A617 /* AudioPlayerView.swift */,
@@ -1236,6 +1239,7 @@
 				AEE700252438E0E500D6992E /* ProgressAlertHandler.swift in Sources */,
 				30E348E524F6647D005C93D1 /* FileTextCell.swift in Sources */,
 				306C32322445CDE9001D89F3 /* DcLogger.swift in Sources */,
+				307A82CC25B8D26700748B57 /* ChatEditingBar.swift in Sources */,
 				303492952565AABC00A523D0 /* DraftModel.swift in Sources */,
 				78E45E3A21D3CFBC00D4B15E /* SettingsController.swift in Sources */,
 				AE8519EA2272FDCA00ED86F0 /* DeviceContactsHandler.swift in Sources */,

+ 23 - 0
deltachat-ios/Assets.xcassets/ic_delete.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "outline_delete_white_36pt_1x.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "outline_delete_white_36pt_2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "outline_delete_white_36pt_3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
deltachat-ios/Assets.xcassets/ic_delete.imageset/outline_delete_white_36pt_1x.png


BIN
deltachat-ios/Assets.xcassets/ic_delete.imageset/outline_delete_white_36pt_2x.png


BIN
deltachat-ios/Assets.xcassets/ic_delete.imageset/outline_delete_white_36pt_3x.png


+ 59 - 11
deltachat-ios/Chat/ChatViewController.swift

@@ -39,6 +39,13 @@ class ChatViewController: UITableViewController {
         return view
     }()
 
+    public lazy var editingBar: ChatEditingBar = {
+        let view = ChatEditingBar()
+        view.delegate = self
+        view.translatesAutoresizingMaskIntoConstraints = false
+        return view
+    }()
+
     open override var shouldAutorotate: Bool {
         return false
     }
@@ -128,7 +135,7 @@ class ChatViewController: UITableViewController {
 
         let deleteItem = ContextMenuProvider.ContextMenuItem(
             title: String.localized("delete"),
-            imageName: "trash",
+            imageName: "ic_delete",
             isDestructive: true,
             action: #selector(BaseMessageCell.messageDelete),
             onPerform: { [weak self] indexPath in
@@ -172,13 +179,7 @@ class ChatViewController: UITableViewController {
                 DispatchQueue.main.async { [weak self] in
                     guard let self = self else { return }
                     let messageId = self.messageIds[indexPath.row]
-                    self.tableView.setEditing(true, animated: true)
-                    UIView.performWithoutAnimation {
-                        if let indexPaths = self.tableView.indexPathsForVisibleRows {
-                            self.tableView.reloadRows(at: indexPaths, with: .none)
-                        }
-                        self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
-                    }
+                    self.setEditing(isEditing: true, selectedAtIndexPath: indexPath)
                 }
             }
         )
@@ -257,6 +258,7 @@ class ChatViewController: UITableViewController {
             draft.parse(draftMsg: dcContext.getDraft(chatId: chatId))
             messageInputBar.inputTextView.text = draft.text
             configureDraftArea(draft: draft)
+            editingBar.delegate = self
         }
 
         let notificationCenter = NotificationCenter.default
@@ -501,7 +503,11 @@ class ChatViewController: UITableViewController {
 
     private func configureDraftArea(draft: DraftModel) {
         draftArea.configure(draft: draft)
-        // setStackViewItems recalculates the proper messageInputBar height
+        if draft.isEditing {
+            messageInputBar.setMiddleContentView(editingBar, animated: false)
+        } else {
+            messageInputBar.setMiddleContentView(messageInputBar.inputTextView, animated: false)
+        }
         messageInputBar.setStackViewItems([draftArea], forStack: .top, animated: true)
     }
 
@@ -872,10 +878,17 @@ class ChatViewController: UITableViewController {
     }
 
     private func askToDeleteMessage(id: Int) {
-        let title = String.localized(stringID: "ask_delete_messages", count: 1)
+        self.askToDeleteMessages(ids: [id])
+    }
+
+    private func askToDeleteMessages(ids: [Int]) {
+        let title = String.localized(stringID: "ask_delete_messages", count: ids.count)
         confirmationAlert(title: title, actionTitle: String.localized("delete"), actionStyle: .destructive,
                           actionHandler: { _ in
-                            self.dcContext.deleteMessage(msgId: id)
+                            self.dcContext.deleteMessages(msgIds: ids)
+                            if self.tableView.isEditing {
+                                self.setEditing(isEditing: false)
+                            }
                           })
     }
 
@@ -1209,6 +1222,20 @@ class ChatViewController: UITableViewController {
         }
         return false
     }
+
+    func setEditing(isEditing: Bool, selectedAtIndexPath: IndexPath? = nil) {
+        self.tableView.setEditing(isEditing, animated: true)
+        self.draft.isEditing = isEditing
+        self.configureDraftArea(draft: self.draft)
+        UIView.performWithoutAnimation {
+            if let indexPaths = self.tableView.indexPathsForVisibleRows {
+                self.tableView.reloadRows(at: indexPaths, with: .none)
+            }
+            if isEditing {
+                self.tableView.selectRow(at: selectedAtIndexPath, animated: false, scrollPosition: .none)
+            }
+        }
+    }
 }
 
 // MARK: - BaseMessageCellDelegate
@@ -1360,6 +1387,27 @@ extension ChatViewController: DraftPreviewDelegate {
     }
 }
 
+// MARK: - ChatEditingDelegate
+extension ChatViewController: ChatEditingDelegate {
+    func onDeletePressed() {
+        if let rows = tableView.indexPathsForSelectedRows {
+            let messageIdsToDelete = rows.compactMap { messageIds[$0.row] }
+            askToDeleteMessages(ids: messageIdsToDelete)
+        }
+    }
+
+    func onForwardPressed() {
+        logger.debug("onForward pressed")
+    }
+
+    func onCancelPressed() {
+        setEditing(isEditing: false)
+    }
+
+
+}
+
+// MARK: - QLPreviewControllerDelegate
 extension ChatViewController: QLPreviewControllerDelegate {
     @available(iOS 13.0, *)
     func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {

+ 1 - 0
deltachat-ios/Chat/DraftModel.swift

@@ -10,6 +10,7 @@ public class DraftModel {
     var attachmentMimeType: String?
     var viewType: Int32?
     let chatId: Int
+    var isEditing: Bool = false
 
     public init(chatId: Int) {
         self.chatId = chatId

+ 115 - 0
deltachat-ios/Chat/Views/ChatEditingBar.swift

@@ -0,0 +1,115 @@
+import UIKit
+import InputBarAccessoryView
+import DcCore
+
+public protocol ChatEditingDelegate: class {
+    func onDeletePressed()
+    func onForwardPressed()
+    func onCancelPressed()
+}
+
+public class ChatEditingBar: UIView, InputItem {
+    public var inputBarAccessoryView: InputBarAccessoryView?
+    public var parentStackViewPosition: InputStackView.Position?
+    public func textViewDidChangeAction(with textView: InputTextView) {}
+    public func keyboardSwipeGestureAction(with gesture: UISwipeGestureRecognizer) {}
+    public func keyboardEditingEndsAction() {}
+    public func keyboardEditingBeginsAction() {}
+
+
+    weak var delegate: ChatEditingDelegate?
+
+    private lazy var cancelImageView: UIImageView = {
+        let view = UIImageView()
+        view.tintColor = .systemBlue
+        view.image = #imageLiteral(resourceName: "ic_close_36pt").withRenderingMode(.alwaysTemplate)
+        view.translatesAutoresizingMaskIntoConstraints = false
+        view.contentMode = .scaleAspectFit
+        view.isUserInteractionEnabled = true
+        return view
+    }()
+
+    private lazy var deleteImageView: UIImageView = {
+        let view = UIImageView()
+        view.tintColor = .red
+        view.image = #imageLiteral(resourceName: "ic_delete").withRenderingMode(.alwaysTemplate)
+        view.translatesAutoresizingMaskIntoConstraints = false
+        view.isUserInteractionEnabled = true
+        view.contentMode = .scaleAspectFit
+        return view
+    }()
+
+    private lazy var forwardImageView: UIImageView = {
+        let view = UIImageView()
+        view.tintColor = DcColors.defaultTextColor
+        view.image = #imageLiteral(resourceName: "ic_forward_white_36pt").withRenderingMode(.alwaysTemplate)
+        view.translatesAutoresizingMaskIntoConstraints = false
+        view.contentMode = .scaleAspectFit
+        view.isUserInteractionEnabled = true
+        return view
+    }()
+
+    private lazy var mainContentView: UIStackView = {
+        let view = UIStackView(arrangedSubviews: [deleteImageView, forwardImageView, cancelImageView])
+        view.axis = .horizontal
+        view.distribution = .equalSpacing
+        view.alignment = .center
+        view.translatesAutoresizingMaskIntoConstraints = false
+        view.spacing = 16
+        return view
+    }()
+
+    convenience init() {
+        self.init(frame: .zero)
+
+    }
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        self.setupSubviews()
+    }
+
+    required init(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    public func setupSubviews() {
+        addSubview(mainContentView)
+
+        addConstraints([
+            mainContentView.constraintAlignTopTo(self, paddingTop: 4),
+            mainContentView.constraintAlignBottomTo(self, paddingBottom: 4),
+            mainContentView.constraintAlignLeadingTo(self),
+            mainContentView.constraintAlignTrailingTo(self),
+            deleteImageView.constraintHeightTo(36),
+            deleteImageView.constraintWidthTo(36),
+            forwardImageView.constraintHeightTo(36),
+            forwardImageView.constraintWidthTo(36),
+            cancelImageView.constraintHeightTo(36),
+            cancelImageView.constraintWidthTo(36)
+        ])
+
+        backgroundColor = DcColors.chatBackgroundColor
+
+        let cancelGestureListener = UITapGestureRecognizer(target: self, action: #selector(onCancelPressed))
+        cancelImageView.addGestureRecognizer(cancelGestureListener)
+
+        let forwardGestureListener = UITapGestureRecognizer(target: self, action: #selector(onForwardPressed))
+        forwardImageView.addGestureRecognizer(forwardGestureListener)
+
+        let deleteGestureListener = UITapGestureRecognizer(target: self, action: #selector(onDeletePressed))
+        deleteImageView.addGestureRecognizer(deleteGestureListener)
+    }
+
+    @objc func onCancelPressed() {
+        delegate?.onCancelPressed()
+    }
+
+    @objc func onForwardPressed() {
+        delegate?.onForwardPressed()
+    }
+
+    @objc func onDeletePressed() {
+        delegate?.onDeletePressed()
+    }
+}

+ 6 - 2
deltachat-ios/Chat/Views/ChatInputBar.swift

@@ -1,5 +1,6 @@
 import UIKit
 import InputBarAccessoryView
+import DcCore
 
 
 public class ChatInputBar: InputBarAccessoryView {
@@ -20,6 +21,7 @@ public class ChatInputBar: InputBarAccessoryView {
     required public init?(coder aDecoder: NSCoder) {
         super.init(coder: aDecoder)
         setupKeyboardObserver()
+        backgroundColor = DcColors.chatBackgroundColor
     }
 
     func setupKeyboardObserver() {
@@ -56,8 +58,10 @@ public class ChatInputBar: InputBarAccessoryView {
     }
 
     public func configure(draft: DraftModel) {
-        hasDraft = draft.attachment != nil
-        hasQuote = draft.quoteText != nil
+        hasDraft = !draft.isEditing && draft.attachment != nil
+        hasQuote = !draft.isEditing && draft.quoteText != nil
+        leftStackView.isHidden = draft.isEditing
+        rightStackView.isHidden = draft.isEditing
         maxTextViewHeight = calculateMaxTextViewHeight()
     }
 

+ 3 - 1
deltachat-ios/Chat/Views/DocumentPreview.swift

@@ -34,7 +34,9 @@ public class DocumentPreview: DraftPreview {
     }
 
     override public func configure(draft: DraftModel) {
-        if draft.viewType == DC_MSG_FILE, let path = draft.attachment {
+        if !draft.isEditing,
+           draft.viewType == DC_MSG_FILE,
+           let path = draft.attachment {
             let tmpMsg = DcMsg(viewType: DC_MSG_FILE)
             tmpMsg.setFile(filepath: path)
             tmpMsg.text = draft.text

+ 5 - 0
deltachat-ios/Chat/Views/MediaPreview.swift

@@ -31,6 +31,11 @@ class MediaPreview: DraftPreview {
     }
 
     override func configure(draft: DraftModel) {
+        if draft.isEditing {
+            self.isHidden = true
+            return
+        }
+        
         if (draft.viewType == DC_MSG_GIF || draft.viewType == DC_MSG_IMAGE), let path = draft.attachment {
             contentImageView.sd_setImage(with: URL(fileURLWithPath: path, isDirectory: false), completed: { image, error, _, _ in
                 if let error = error {

+ 2 - 1
deltachat-ios/Chat/Views/QuotePreview.swift

@@ -26,7 +26,8 @@ public class QuotePreview: DraftPreview {
     }
 
     override public func configure(draft: DraftModel) {
-        if let quoteText = draft.quoteText {
+        if !draft.isEditing,
+           let quoteText = draft.quoteText {
             quoteView.quote.text = quoteText
             compactView = draft.attachment != nil
             calculateQuoteHeight(compactView: compactView)