         ])
         
         self.contentView = contentView
+    }
+    
+    private var shouldAlwaysPinToTop: Bool {
+        guard let viewController = viewController else {
+            return false
+        }
+        
+        return viewController.traitCollection.verticalSizeClass == .compact
+    }
+    
+    @objc fileprivate func userDidPanContainerView(_ gestureRecognizer: UIPanGestureRecognizer) {
+        
+        guard let viewController = viewController,
+        let containerView = containerView,
+        let safeAreaBackgroundView = safeAreaBackgroundView else {
+            gestureRecognizer.state = .ended
+            return
+        }
+        
+        let translationY = gestureRecognizer.translation(in: viewController.view).y
+
+        switch gestureRecognizer.state {
+        case .began:
+            let containerViewSafeAreaFrame = viewController.view.convert(containerView.frame, to: safeAreaBackgroundView)
+            let containerViewYUponDragBegin = containerViewSafeAreaFrame.origin.y
+            self.containerViewYUponDragBegin = containerViewYUponDragBegin
+            self.containerViewWasPinnedToTopUponDragBegin = containerViewYUponDragBegin == containerPinnedTopSpacing
+
+            containerViewTopConstraint?.constant = containerViewYUponDragBegin + translationY // max(containerViewYUponDragBegin + translationY, containerPinnedTopSpacing)
+            toggleConstraints(shouldPinToTop: true)
+        case .changed:
+            
+            guard let containerViewYUponDragBegin = containerViewYUponDragBegin else {
+                gestureRecognizer.state = .ended
+                return
+            }
+            
+            containerViewTopConstraint?.constant = containerViewYUponDragBegin + translationY // max(containerViewYUponDragBegin + translationY, containerPinnedTopSpacing)
+            toggleConstraints(shouldPinToTop: true)
+        case .ended:
+
+            if translationY < -50 {
+                containerViewTopConstraint?.constant = containerPinnedTopSpacing
+                toggleConstraints(shouldPinToTop: true)
+            } else if translationY > 50 && !shouldAlwaysPinToTop {
+                toggleConstraints(shouldPinToTop: false)
+            } else {
+                // wasn't dragged far enough in either direction, so reset back to where it was when pan gesture started
+                toggleConstraints(shouldPinToTop: (containerViewWasPinnedToTopUponDragBegin ?? false))
+            }
+            
+            // reset top constraint
+            containerViewTopConstraint?.constant = containerPinnedTopSpacing
+            containerViewYUponDragBegin = nil
+            containerViewWasPinnedToTopUponDragBegin = nil
+            
+        default:
+            break
+        }
+        
+        viewController.view.setNeedsLayout()
+        UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseOut) {
+            viewController.view.layoutIfNeeded()
+        }
     }
     
     private func toggleConstraints(shouldPinToTop: Bool) {


/// Class for coordinating talk page reply compose views
class TalkPageReplyComposeController {
    
    private var containerView: UIView?
    private var containerViewTopConstraint: NSLayoutConstraint?
    private var containerViewBottomConstraint: NSLayoutConstraint?
    private var containerViewHeightConstraint: NSLayoutConstraint?
    
    private var contentView: TalkPageReplyComposeContentView?
    
    private let containerPinnedTopSpacing = CGFloat(10)


    // MARK: Public
    
    func setupAndDisplay(in viewController: ViewController, theme: Theme) {
        
        guard containerView == nil && contentView == nil else {
            return
        }
        
        self.viewController = viewController
        
        setupViews(in: viewController)
        calculateLayout(in: viewController)
