Procházet zdrojové kódy

add basic UIViewController containing a tableView and a dummy TextInputField. Animate TextInputField position on keyboard appearing/disappearing

cyberta před 2 roky
rodič
revize
e6ffe8b338

+ 8 - 0
DcCore/DcCore/Extensions/UIView+Extensions.swift

@@ -281,6 +281,14 @@ public extension UIView {
         return constraint
     }
 
+    func constraintMinHeightTo(_ height: CGFloat, priority: UILayoutPriority? = .none) -> NSLayoutConstraint {
+        let constraint = heightAnchor.constraint(greaterThanOrEqualToConstant: height)
+        if let priority = priority {
+            constraint.priority = priority
+        }
+        return constraint
+    }
+
     func constraintWidthTo(_ width: CGFloat, priority: UILayoutPriority? = .none) -> NSLayoutConstraint {
         let constraint = widthAnchor.constraint(equalToConstant: width)
         if let priority = priority {

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

@@ -100,6 +100,7 @@
 		3095A351237DD1F700AB07F7 /* MediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3095A350237DD1F700AB07F7 /* MediaPicker.swift */; };
 		309D14DA28F482D300F7BA29 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961862346125000C80F33 /* NSAttributedString+Extensions.swift */; };
 		30A4149724F6EFBE00EC91EB /* InfoMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A4149624F6EFBE00EC91EB /* InfoMessageCell.swift */; };
+		30A60515299A75D800633F3C /* ChatViewController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A60514299A75D800633F3C /* ChatViewController2.swift */; };
 		30AAD71B2762869600DE3DC1 /* SelectableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AAD71A2762869600DE3DC1 /* SelectableCell.swift */; };
 		30B0ACFA24AB5B99004D5E29 /* EphemeralMessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B0ACF924AB5B99004D5E29 /* EphemeralMessagesViewController.swift */; };
 		30B2BD02278F1C1900889AA4 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3011E8042787365D00214221 /* KeychainManager.swift */; };
@@ -385,6 +386,7 @@
 		30860EE826F49E64002651A6 /* DownloadOnDemandViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadOnDemandViewController.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>"; };
+		30A60514299A75D800633F3C /* ChatViewController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewController2.swift; sourceTree = "<group>"; };
 		30AAD71A2762869600DE3DC1 /* SelectableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableCell.swift; sourceTree = "<group>"; };
 		30AC265E237F1807002A943F /* AvatarHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarHelper.swift; sourceTree = "<group>"; };
 		30B0ACF924AB5B99004D5E29 /* EphemeralMessagesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralMessagesViewController.swift; sourceTree = "<group>"; };
@@ -751,6 +753,7 @@
 			children = (
 				3008CB7524F95B6D00E6A617 /* AudioController.swift */,
 				30FDB6F824D1C1000066C48D /* ChatViewController.swift */,
+				30A60514299A75D800633F3C /* ChatViewController2.swift */,
 				3080A00D277DDA1400E74565 /* InputBarAccessoryView */,
 				30FDB6B524D193DD0066C48D /* Views */,
 				303492942565AABC00A523D0 /* DraftModel.swift */,
@@ -1575,6 +1578,7 @@
 				3080A038277DE30100E74565 /* UIView+AutoLayout.swift in Sources */,
 				30B0ACFA24AB5B99004D5E29 /* EphemeralMessagesViewController.swift in Sources */,
 				B20462E42440A4A600367A57 /* AutodelOverviewViewController.swift in Sources */,
+				30A60515299A75D800633F3C /* ChatViewController2.swift in Sources */,
 				305962102346154D00C80F33 /* String+Extension.swift in Sources */,
 				789E879621D6CB58003ED1C5 /* QrCodeReaderController.swift in Sources */,
 				305961D22346125100C80F33 /* CGRect+Extensions.swift in Sources */,

+ 16 - 0
deltachat-ios/Chat/ChatViewController.swift

@@ -1370,6 +1370,12 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate {
                                                     style: isLocationStreaming ? .destructive : .default,
                                                     handler: locationStreamingButtonPressed(_:))
 
+        #if DEBUG
+        let showNewChatController = UIAlertAction(title: "show new chat controller",
+                                                    style: .default,
+                                                    handler: showNewChatController(_:))
+        #endif
+
         alert.addAction(cameraAction)
         alert.addAction(galleryAction)
         alert.addAction(documentAction)
@@ -1389,6 +1395,11 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate {
         if UserDefaults.standard.bool(forKey: "location_streaming") {
             alert.addAction(locationStreamingAction)
         }
+
+        #if DEBUG
+            alert.addAction(showNewChatController)
+        #endif
+
         alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
         self.present(alert, animated: true, completion: {
             // unfortunately, voiceMessageAction.accessibilityHint does not work,
@@ -1595,6 +1606,11 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate {
         showPhotoVideoLibrary(delegate: self)
     }
 
+    private func showNewChatController(_ action: UIAlertAction) {
+        let newChatViewController = ChatViewController2(dcContext: dcContext, chatId: chatId, highlightedMsg: highlightedMsg)
+        navigationController?.pushViewController(newChatViewController, animated: true)
+    }
+
     private func locationStreamingButtonPressed(_ action: UIAlertAction) {
         let isLocationStreaming = dcContext.isSendingLocationsToChat(chatId: chatId)
         if isLocationStreaming {

+ 223 - 0
deltachat-ios/Chat/ChatViewController2.swift

@@ -0,0 +1,223 @@
+import UIKit
+import DcCore
+
+class ChatViewController2: UIViewController {
+
+    var dcContext: DcContext
+    let chatId: Int
+    var messageIds: [Int] = []
+
+    var heightConstraint: NSLayoutConstraint?
+    var bottomInset: CGFloat {
+        get {
+            logger.debug("bottomInset - get: \(heightConstraint?.constant ?? 0)")
+            return heightConstraint?.constant ?? 0
+        }
+        set {
+            logger.debug("bottomInset - set: \(newValue)")
+            heightConstraint?.constant = newValue
+        }
+    }
+
+
+    lazy var isGroupChat: Bool = {
+        return dcContext.getChat(chatId: chatId).isGroup
+    }()
+
+    lazy var draft: DraftModel = {
+        let draft = DraftModel(dcContext: dcContext, chatId: chatId)
+        return draft
+    }()
+
+    lazy var tableView: UITableView = {
+        let tableView: UITableView = UITableView(frame: .zero)
+        tableView.translatesAutoresizingMaskIntoConstraints = false
+        tableView.delegate = self
+        tableView.dataSource = self
+        return tableView
+    }()
+
+    lazy var textView: ChatInputTextView = {
+        let textView = ChatInputTextView()
+        textView.translatesAutoresizingMaskIntoConstraints = false
+        textView.font = UIFont.preferredFont(forTextStyle: .body)
+        textView.backgroundColor = DcColors.inputFieldColor
+        textView.isEditable = true
+        return textView
+    }()
+
+    lazy var dummyView: UIView = {
+        let view = UIView(frame: .zero)
+        view.translatesAutoresizingMaskIntoConstraints = false
+        view.backgroundColor = .yellow
+        view.addSubview(textView)
+        return view
+    }()
+
+    private lazy var keyboardManager: KeyboardManager? = {
+        let manager = KeyboardManager()
+        return manager
+    }()
+
+    public lazy var backgroundContainer: UIImageView = {
+        let view = UIImageView()
+        view.contentMode = .scaleAspectFill
+        view.backgroundColor = .blue
+        if let backgroundImageName = UserDefaults.standard.string(forKey: Constants.Keys.backgroundImageName) {
+            view.sd_setImage(with: Utils.getBackgroundImageURL(name: backgroundImageName),
+                             placeholderImage: nil,
+                             options: [.retryFailed]) { [weak self] (_, error, _, _) in
+                if let error = error {
+                    logger.error("Error loading background image: \(error.localizedDescription)" )
+                    DispatchQueue.main.async { [weak self] in
+                        self?.setDefaultBackgroundImage(view: view)
+                    }
+                }
+            }
+        } else {
+             setDefaultBackgroundImage(view: view)
+        }
+        return view
+    }()
+
+    init(dcContext: DcContext, chatId: Int, highlightedMsg: Int? = nil) {
+        self.dcContext = dcContext
+        self.chatId = chatId
+        super.init(nibName: nil, bundle: nil)
+        hidesBottomBarWhenPushed = true
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        setupSubviews()
+        tableView.backgroundView = backgroundContainer
+        tableView.register(TextMessageCell.self, forCellReuseIdentifier: "text")
+        tableView.rowHeight = UITableView.automaticDimension
+        tableView.separatorStyle = .none
+        tableView.keyboardDismissMode = .interactive
+        navigationController?.setNavigationBarHidden(false, animated: false)
+
+        if #available(iOS 13.0, *) {
+            navigationController?.navigationBar.scrollEdgeAppearance = navigationController?.navigationBar.standardAppearance
+        }
+
+        navigationItem.backButtonTitle = String.localized("chat")
+        definesPresentationContext = true
+
+        if !dcContext.isConfigured() {
+            // TODO: display message about nothing being configured
+            return
+        }
+
+        // Binding to the tableView will enable interactive dismissal
+        keyboardManager?.bind(to: tableView)
+        keyboardManager?.on(event: .willChangeFrame) { [weak self] event in
+            guard let self = self else { return }
+            if self.keyboardManager?.isKeyboardHidden ?? true {
+                return
+            }
+            logger.debug("willChangeFrame \(event)")
+            let keyboardScreenEndFrame = event.endFrame
+            self.bottomInset = self.getInputTextHeight() + self.dummyView.convert(keyboardScreenEndFrame, from: self.view.window).height
+        }.on(event: .willHide) { [weak self] event in
+            guard let self = self else { return }
+            logger.debug("willHide \(event)")
+            self.bottomInset = self.getInputTextHeight()
+        }.on(event: .didHide) { [weak self] event in
+            guard let self = self else { return }
+            logger.debug("didHide \(event)")
+            self.bottomInset = self.getInputTextHeight()
+        }.on(event: .willShow) { [weak self] event in
+            guard let self = self else { return }
+            logger.debug("willShow \(event)")
+            self.bottomInset = self.getInputTextHeight() + self.dummyView.convert(event.endFrame, from: self.view.window).height
+            UIView.animate(withDuration: event.timeInterval, delay: 0, options: event.animationOptions, animations: {
+                self.dummyView.layoutIfNeeded()
+            })
+        }
+
+        loadMessages()
+    }
+
+    private func getInputTextHeight() -> CGFloat {
+        return 70
+    }
+
+    private func loadMessages() {
+        // update message ids
+        var msgIds = dcContext.getChatMsgs(chatId: chatId)
+        let freshMsgsCount = self.dcContext.getUnreadMessages(chatId: self.chatId)
+        if freshMsgsCount > 0 && msgIds.count >= freshMsgsCount {
+            let index = msgIds.count - freshMsgsCount
+            msgIds.insert(Int(DC_MSG_ID_MARKER1), at: index)
+        }
+        self.messageIds = msgIds
+        self.reloadData()
+    }
+
+    private func reloadData() {
+        let selectredRows = tableView.indexPathsForSelectedRows
+        tableView.reloadData()
+        // There's an iOS bug, filling up the console output but which can be ignored: https://developer.apple.com/forums/thread/668295
+        // [Assert] Attempted to call -cellForRowAtIndexPath: on the table view while it was in the process of updating its visible cells, which is not allowed.
+        selectredRows?.forEach({ (selectedRow) in
+            tableView.selectRow(at: selectedRow, animated: false, scrollPosition: .none)
+        })
+    }
+
+    func setupSubviews() {
+        view.addSubview(tableView)
+        view.addSubview(dummyView)
+        view.addConstraints([
+            tableView.constraintAlignTopToAnchor(view.safeAreaLayoutGuide.topAnchor),
+            tableView.constraintAlignLeadingToAnchor(view.safeAreaLayoutGuide.leadingAnchor),
+            tableView.constraintAlignTrailingToAnchor(view.safeAreaLayoutGuide.trailingAnchor),
+            tableView.constraintAlignBottomToAnchor(dummyView.topAnchor),
+            dummyView.constraintAlignLeadingToAnchor(view.safeAreaLayoutGuide.leadingAnchor),
+            dummyView.constraintAlignTrailingToAnchor(view.safeAreaLayoutGuide.trailingAnchor),
+            dummyView.constraintAlignBottomToAnchor(view.safeAreaLayoutGuide.bottomAnchor)
+            ,
+            textView.constraintAlignTopTo(dummyView),
+            textView.constraintAlignLeadingTo(dummyView),
+            textView.constraintAlignTrailingTo(dummyView),
+            textView.constraintAlignBottomTo(dummyView),
+        ])
+        heightConstraint = dummyView.constraintMinHeightTo(bottomInset)
+        bottomInset = getInputTextHeight()
+        heightConstraint?.isActive = true
+
+        navigationItem.title = "new Chat UI"
+
+    }
+
+    private func configureUIForWriting() {
+        tableView.allowsMultipleSelectionDuringEditing = true
+        tableView.dragInteractionEnabled = true
+    }
+
+    private func setDefaultBackgroundImage(view: UIImageView) {
+        if #available(iOS 12.0, *) {
+            view.image = UIImage(named: traitCollection.userInterfaceStyle == .light ? "background_light" : "background_dark")
+        } else {
+            view.image = UIImage(named: "background_light")
+        }
+    }
+}
+
+extension ChatViewController2: UITableViewDelegate {
+
+}
+
+extension ChatViewController2: UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        messageIds.count
+    }
+
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        return UITableViewCell()
+    }
+}