Browse Source

refactoring ChatViewController, use contentInsetAdjustmentBehavior remove a lot of manual bottom and top inset calculation code

cyberta 3 years ago
parent
commit
f9fc1f20d0

+ 60 - 75
deltachat-ios/Chat/ChatViewController.swift

@@ -18,13 +18,7 @@ class ChatViewController: UITableViewController {
     var incomingMsgObserver: NSObjectProtocol?
     var chatModifiedObserver: NSObjectProtocol?
     var ephemeralTimerModifiedObserver: NSObjectProtocol?
-    // isDismissing indicates whether the ViewController is/was about to dismissed.
-    // The VC can be dismissed by pressing back '<' or by a swipe-to-dismiss gesture.
-    // The latter is cancelable and leads to viewWillAppear is called in case the gesture is cancelled
-    // We need the flag to handle that special case correctly in viewWillAppear
-    private var isDismissing = false
     private var isInitial = true
-    private var ignoreInputBarChange = false
     private var isVisibleToUser: Bool = false
     private var keepKeyboard: Bool = false
 
@@ -78,7 +72,10 @@ class ChatViewController: UITableViewController {
     }()
 
     /// The `InputBarAccessoryView` used as the `inputAccessoryView` in the view controller.
-    open var messageInputBar = ChatInputBar()
+    lazy var messageInputBar: ChatInputBar = {
+        let inputBar = ChatInputBar()
+        return inputBar
+    }()
 
     lazy var draftArea: DraftArea = {
         let view = DraftArea()
@@ -251,6 +248,11 @@ class ChatViewController: UITableViewController {
     /// The `BasicAudioController` controll the AVAudioPlayer state (play, pause, stop) and update audio cell UI accordingly.
     private lazy var audioController = AudioController(dcContext: dcContext, chatId: chatId, delegate: self)
 
+    private lazy var keyboardManager: KeyboardManager = {
+        let manager = KeyboardManager()
+        return manager
+    }()
+
     var showCustomNavBar = true
     var highlightedMsg: Int?
 
@@ -279,6 +281,7 @@ class ChatViewController: UITableViewController {
     }
 
     override func loadView() {
+        super.loadView()
         self.tableView = ChatTableView(messageInputBar: messageInputBar)
         self.tableView.delegate = self
         self.tableView.dataSource = self
@@ -297,7 +300,7 @@ class ChatViewController: UITableViewController {
         tableView.rowHeight = UITableView.automaticDimension
         tableView.separatorStyle = .none
         tableView.keyboardDismissMode = .interactive
-        tableView.contentInsetAdjustmentBehavior = .never
+       // tableView.contentInsetAdjustmentBehavior = .never
         navigationController?.setNavigationBarHidden(false, animated: false)
 
         if #available(iOS 13.0, *) {
@@ -307,6 +310,22 @@ class ChatViewController: UITableViewController {
         navigationItem.backButtonTitle = String.localized("chat")
         definesPresentationContext = true
 
+        // Binding to the tableView will enabled interactive dismissal
+        keyboardManager.bind(to: tableView)
+
+        // Add some extra handling to manage content inset
+        keyboardManager.on(event: .didChangeFrame) { [weak self] _ in
+            guard let self = self else { return }
+            if self.isLastRowVisible() && !self.tableView.isDragging && !self.tableView.isDecelerating && self.highlightedMsg == nil {
+                self.scrollToBottom()
+            }
+        }.on(event: .willChangeFrame) { [weak self] _ in
+            guard let self = self else { return }
+            if self.isLastRowVisible() && !self.tableView.isDragging && !self.tableView.isDecelerating && self.highlightedMsg == nil {
+                self.scrollToBottom()
+            }
+        }
+
         if !dcContext.isConfigured() {
             // TODO: display message about nothing being configured
             return
@@ -382,54 +401,39 @@ class ChatViewController: UITableViewController {
         if showCustomNavBar {
             updateTitle(chat: dcContext.getChat(chatId: chatId))
         }
-        if !isDismissing {
-            self.tableView.becomeFirstResponder()
-            if activateSearch {
-                activateSearch = false
-                DispatchQueue.main.async { [weak self] in
-                    self?.searchController.isActive = true
-                }
-                
-            }
-            var bottomInsets = self.messageInputBar.intrinsicContentSize.height + self.messageInputBar.keyboardHeight
-            if UIApplication.shared.statusBarOrientation.isLandscape,
-               let root = UIApplication.shared.keyWindow?.rootViewController {
-                // in landscape we need to take safeAreaInsets into account, in portrait they're already part of the keyboard height
-                bottomInsets += root.view.safeAreaInsets.bottom
+        tableView.becomeFirstResponder()
+        if activateSearch {
+            activateSearch = false
+            DispatchQueue.main.async { [weak self] in
+                self?.searchController.isActive = true
             }
-            self.tableView.contentInset = UIEdgeInsets(top: self.getTopInsetHeight(),
-                                                       left: 0,
-                                                       bottom: bottomInsets,
-                                                       right: 0)
-
-            if let msgId = self.highlightedMsg, self.messageIds.firstIndex(of: msgId) != nil {
-                UIView.animate(withDuration: 0.1, delay: 0, options: .allowAnimatedContent, animations: { [weak self] in
-                    self?.scrollToMessage(msgId: msgId, animated: false)
-                }, completion: { [weak self] finished in
-                    if finished {
-                        guard let self = self else { return }
-                        self.highlightedMsg = nil
-                        self.isInitial = false
-                        self.ignoreInputBarChange = false
-                        self.messageInputBar.scrollDownButton.isHidden = self.isLastRowVisible()
-                    }
-                })
-            } else {
-                UIView.animate(withDuration: 0.1, delay: 0, options: .allowAnimatedContent, animations: { [weak self] in
-                    guard let self = self else { return }
-                    if self.isInitial {
-                        self.scrollToLastUnseenMessage()
-                    }
-                }, completion: { [weak self] finished in
+        }
+
+        if let msgId = self.highlightedMsg, self.messageIds.firstIndex(of: msgId) != nil {
+            UIView.animate(withDuration: 0.1, delay: 0, options: .allowAnimatedContent, animations: { [weak self] in
+                self?.scrollToMessage(msgId: msgId, animated: false)
+            }, completion: { [weak self] finished in
+                if finished {
                     guard let self = self else { return }
-                    if finished {
-                        self.isInitial = false
-                        self.ignoreInputBarChange = false
-                    }
-                })
-            }
+                    self.highlightedMsg = nil
+                    self.isInitial = false
+                    self.messageInputBar.scrollDownButton.isHidden = self.isLastRowVisible()
+                }
+            })
+        } else {
+            UIView.animate(withDuration: 0.1, delay: 0, options: .allowAnimatedContent, animations: { [weak self] in
+                guard let self = self else { return }
+                if self.isInitial {
+                    self.scrollToLastUnseenMessage()
+                }
+            }, completion: { [weak self] finished in
+                guard let self = self else { return }
+                if finished {
+                    self.isInitial = false
+                }
+            })
         }
-        isDismissing = false
+
 
 
         if RelayHelper.sharedInstance.isForwarding() {
@@ -454,6 +458,7 @@ class ChatViewController: UITableViewController {
         }
 
         handleUserVisibility(isVisible: true)
+        tableView.becomeFirstResponder()
     }
 
     override func viewWillDisappear(_ animated: Bool) {
@@ -461,13 +466,12 @@ class ChatViewController: UITableViewController {
 
         // the navigationController will be used when chatDetail is pushed, so we have to remove that gestureRecognizer
         navigationController?.navigationBar.removeGestureRecognizer(navBarTap)
-        isDismissing = true
+        tableView.resignFirstResponder()
+        messageInputBar.inputTextView.resignFirstResponder()
     }
 
     override func viewDidDisappear(_ animated: Bool) {
         super.viewDidDisappear(animated)
-        isDismissing = false
-        ignoreInputBarChange = true
         AppStateRestorer.shared.resetLastActiveChat()
         handleUserVisibility(isVisible: false)
         audioController.stopAnyOngoingPlaying()
@@ -1824,25 +1828,6 @@ extension ChatViewController: InputBarAccessoryViewDelegate {
         draft.text = text
         evaluateInputBar(draft: draft)
     }
-
-    func inputBar(_ inputBar: InputBarAccessoryView, didChangeIntrinsicContentTo size: CGSize) {
-        if isDismissing {
-            return
-        }
-        var bottomInsets = size.height + messageInputBar.keyboardHeight
-        if UIApplication.shared.statusBarOrientation.isLandscape,
-           let root = UIApplication.shared.keyWindow?.rootViewController {
-            // in landscape we need to take safeAreaInsets into account, in portrait they're already part of the keyboard height
-            bottomInsets += root.view.safeAreaInsets.bottom
-        }
-        self.tableView.contentInset = UIEdgeInsets(top: self.getTopInsetHeight(),
-                                                   left: 0,
-                                                   bottom: bottomInsets,
-                                                   right: 0)
-        if isLastRowVisible() && !tableView.isDragging && !tableView.isDecelerating  && highlightedMsg == nil && !ignoreInputBarChange {
-            scrollToBottom()
-        }
-    }
 }
 
 // MARK: - DraftPreviewDelegate

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

@@ -7,7 +7,6 @@ public class ChatInputBar: InputBarAccessoryView {
 
     var hasDraft: Bool = false
     var hasQuote: Bool = false
-    var keyboardHeight: CGFloat = 0
     
     var onScrollDownButtonPressed: (() -> Void)?
     
@@ -23,16 +22,6 @@ public class ChatInputBar: InputBarAccessoryView {
         self.init(frame: .zero)
     }
 
-    public override init(frame: CGRect) {
-        super.init(frame: frame)
-        setupKeyboardObserver()
-    }
-
-    required public init?(coder aDecoder: NSCoder) {
-        super.init(coder: aDecoder)
-        setupKeyboardObserver()
-    }
-
     override open func setup() {
         replaceInputBar()
         setupScrollDownButton()
@@ -54,19 +43,6 @@ public class ChatInputBar: InputBarAccessoryView {
         inputTextView.translatesAutoresizingMaskIntoConstraints = false
         inputTextView.inputBarAccessoryView = self
     }
-
-    func setupKeyboardObserver() {
-        NotificationCenter.default.addObserver(
-            self,
-            selector: #selector(keyboardChanged),
-            name: UIResponder.keyboardWillChangeFrameNotification,
-            object: nil
-        )
-    }
-
-    deinit {
-        NotificationCenter.default.removeObserver(self)
-    }
     
     override open func calculateMaxTextViewHeight() -> CGFloat {
         if traitCollection.verticalSizeClass == .regular || UIDevice.current.userInterfaceIdiom == .pad {
@@ -82,6 +58,7 @@ public class ChatInputBar: InputBarAccessoryView {
         } else {
             // landscape phone layout
             let height = UIScreen.main.bounds.height - keyboardHeight - 12
+            logger.debug("HEIGHTS: calculateMaxTextViewHeight screen \(UIScreen.main.bounds.height) - \(keyboardHeight) >> \(UIScreen.main.bounds.height - keyboardHeight)")
             return height
         }
     }
@@ -100,40 +77,12 @@ public class ChatInputBar: InputBarAccessoryView {
         maxTextViewHeight = calculateMaxTextViewHeight()
     }
 
-    @objc func keyboardChanged(_ notification: Notification) {
-        if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
-            let keyboardRectangle = keyboardFrame.cgRectValue
-            if (keyboardRectangle.height - intrinsicContentSize.height) == keyboardHeight {
-                return
-            }
-            invalidateIntrinsicContentSize()
-            keyboardHeight = keyboardRectangle.height - intrinsicContentSize.height
-            updateTextViewHeight()
-            delegate?.inputBar(self, didChangeIntrinsicContentTo: intrinsicContentSize)
-        }
-    }
-
     public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
         super.traitCollectionDidChange(previousTraitCollection)
-        if (self.traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass)
-                || (self.traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass) {
-            invalidateIntrinsicContentSize()
-            updateTextViewHeight()
-            delegate?.inputBar(self, didChangeIntrinsicContentTo: intrinsicContentSize)
-        }
         scrollDownButton.layer.borderColor = DcColors.colorDisabled.cgColor
     }
 
-    private func updateTextViewHeight() {
-        maxTextViewHeight = calculateMaxTextViewHeight()
-        if keyboardHeight > 0,
-           UIApplication.shared.statusBarOrientation.isLandscape,
-           UIDevice.current.userInterfaceIdiom == .phone {
-            setShouldForceMaxTextViewHeight(to: true, animated: false)
-        } else if shouldForceTextViewMaxHeight {
-            setShouldForceMaxTextViewHeight(to: false, animated: false)
-        }
-    }
+    
     
     func setupScrollDownButton() {
         self.addSubview(scrollDownButton)

+ 1 - 0
deltachat-ios/Chat/Views/ChatTableView.swift

@@ -16,6 +16,7 @@ class ChatTableView: UITableView {
     public init(messageInputBar: InputBarAccessoryView) {
         self.messageInputBar = messageInputBar
         super.init(frame: .zero, style: .plain)
+        
     }
 
     required init?(coder: NSCoder) {

+ 1 - 1
deltachat-ios/libraries/InputBarAccessoryView

@@ -1 +1 @@
-Subproject commit d5de101e07fbf950eb84b68f2f91aee563e46ceb
+Subproject commit 17b5f9e2f689bfd9a80c495368b70c951160c1fd