From 4249191b372cecde6d7dccd34af1a0c9eafc1f20 Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Tue, 13 Dec 2022 21:18:28 -0300
Subject: [PATCH] Fixes scrolling of nickname drawer

---
 Package.swift                                 |   4 +
 .../CustomTransitions/BottomTransition.swift  |   8 +-
 .../BottomTransitioningDelegate.swift         |  23 ++++
 .../FullscreenTransition.swift                | 124 ++++++++++++++++++
 Sources/AppNavigation/PresentNickname.swift   |  19 ++-
 5 files changed, 166 insertions(+), 12 deletions(-)
 create mode 100644 Sources/AppNavigation/CustomTransitions/FullscreenTransition.swift

diff --git a/Package.swift b/Package.swift
index 3034bf51..b6116630 100644
--- a/Package.swift
+++ b/Package.swift
@@ -226,6 +226,10 @@ let package = Package(
           name: "XXModels",
           package: "client-ios-db"
         ),
+        .product(
+          name: "ScrollViewController",
+          package: "ScrollViewController"
+        ),
         .product(
           name: "Dependencies",
           package: "swift-composable-architecture"
diff --git a/Sources/AppNavigation/CustomTransitions/BottomTransition.swift b/Sources/AppNavigation/CustomTransitions/BottomTransition.swift
index 66b422c1..7c40eba0 100644
--- a/Sources/AppNavigation/CustomTransitions/BottomTransition.swift
+++ b/Sources/AppNavigation/CustomTransitions/BottomTransition.swift
@@ -1,5 +1,4 @@
 import UIKit
-import Combine
 
 final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning {
   enum Direction {
@@ -9,11 +8,10 @@ final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning {
 
   let isDismissableOnBackground: Bool
   var direction: Direction = .present
-  private let onDismissal: (() -> Void)?
+  private let onDismissal: () -> Void
   private weak var darkOverlayView: UIControl?
   private weak var topConstraint: NSLayoutConstraint?
   private weak var bottomConstraint: NSLayoutConstraint?
-  private var cancellables = Set<AnyCancellable>()
 
   private var presentedConstraints: [NSLayoutConstraint] = []
   private var dismissedConstraints: [NSLayoutConstraint] = []
@@ -21,7 +19,7 @@ final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning {
 
   init(
     _ isDismissableOnBackground: Bool = true,
-    onDismissal: (() -> Void)?
+    onDismissal: @escaping () -> Void
   ) {
     self.onDismissal = onDismissal
     self.isDismissableOnBackground = isDismissableOnBackground
@@ -123,7 +121,7 @@ final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning {
       },
       completion: { [weak self] _ in
         context.completeTransition(true)
-        self?.onDismissal?()
+        self?.onDismissal()
       }
     )
   }
diff --git a/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift b/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift
index b30e5497..41e5ef29 100644
--- a/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift
+++ b/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift
@@ -23,3 +23,26 @@ final class BottomTransitioningDelegate: NSObject, UIViewControllerTransitioning
     return transition
   }
 }
+
+final class FullscreenTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
+  private var transition: FullscreenTransition?
+
+  func animationController(
+    forPresented presented: UIViewController,
+    presenting: UIViewController,
+    source: UIViewController
+  ) -> UIViewControllerAnimatedTransitioning? {
+    transition = FullscreenTransition { [weak self] in
+      guard let self else { return }
+      self.transition = nil
+    }
+    return transition
+  }
+
+  func animationController(
+    forDismissed dismissed: UIViewController
+  ) -> UIViewControllerAnimatedTransitioning? {
+    transition?.direction = .dismiss
+    return transition
+  }
+}
diff --git a/Sources/AppNavigation/CustomTransitions/FullscreenTransition.swift b/Sources/AppNavigation/CustomTransitions/FullscreenTransition.swift
new file mode 100644
index 00000000..75d5fd04
--- /dev/null
+++ b/Sources/AppNavigation/CustomTransitions/FullscreenTransition.swift
@@ -0,0 +1,124 @@
+import UIKit
+
+final class FullscreenTransition: NSObject, UIViewControllerAnimatedTransitioning {
+  enum Direction {
+    case present
+    case dismiss
+  }
+
+  var direction: Direction = .present
+  private let onDismissal: () -> Void
+  private weak var darkOverlayView: UIControl?
+  private weak var topConstraint: NSLayoutConstraint?
+  private weak var bottomConstraint: NSLayoutConstraint?
+
+  private var presentedConstraints: [NSLayoutConstraint] = []
+  private var dismissedConstraints: [NSLayoutConstraint] = []
+  private var presentingController: UIViewController?
+
+  init(onDismissal: @escaping () -> Void) {
+    self.onDismissal = onDismissal
+    super.init()
+  }
+
+  func transitionDuration(
+    using context: UIViewControllerContextTransitioning?
+  ) -> TimeInterval { 0.5 }
+
+  func animateTransition(
+    using context: UIViewControllerContextTransitioning
+  ) {
+    switch direction {
+    case .present:
+      present(using: context)
+    case .dismiss:
+      dismiss(using: context)
+    }
+  }
+
+  private func present(using context: UIViewControllerContextTransitioning) {
+    guard let presentingController = context.viewController(forKey: .from),
+          let presentedView = context.view(forKey: .to) else {
+      context.completeTransition(false)
+      return
+    }
+
+    let darkOverlayView = UIControl()
+    self.darkOverlayView = darkOverlayView
+
+    darkOverlayView.alpha = 0.0
+    darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
+    context.containerView.addSubview(darkOverlayView)
+    darkOverlayView.frame = context.containerView.bounds
+
+    darkOverlayView.addTarget(self, action: #selector(didTapOverlay), for: .touchUpInside)
+    self.presentingController = presentingController
+
+    context.containerView.addSubview(presentedView)
+    presentedView.translatesAutoresizingMaskIntoConstraints = false
+
+    presentedConstraints = [
+      presentedView.topAnchor.constraint(equalTo: context.containerView.topAnchor),
+      presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
+      presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
+      presentedView.bottomAnchor.constraint(equalTo: context.containerView.bottomAnchor)
+    ]
+
+    dismissedConstraints = [
+      presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
+      presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
+      presentedView.topAnchor.constraint(equalTo: context.containerView.bottomAnchor),
+      presentedView.heightAnchor.constraint(equalTo: context.containerView.heightAnchor)
+    ]
+
+    NSLayoutConstraint.activate(dismissedConstraints)
+
+    context.containerView.setNeedsLayout()
+    context.containerView.layoutIfNeeded()
+
+    NSLayoutConstraint.deactivate(dismissedConstraints)
+    NSLayoutConstraint.activate(presentedConstraints)
+
+    UIView.animate(
+      withDuration: transitionDuration(using: context),
+      delay: 0,
+      usingSpringWithDamping: 1,
+      initialSpringVelocity: 0,
+      options: .curveEaseInOut,
+      animations: {
+        darkOverlayView.alpha = 1.0
+        context.containerView.setNeedsLayout()
+        context.containerView.layoutIfNeeded()
+      },
+      completion: { _ in
+        context.completeTransition(true)
+      })
+  }
+
+  private func dismiss(using context: UIViewControllerContextTransitioning) {
+    NSLayoutConstraint.deactivate(presentedConstraints)
+    NSLayoutConstraint.activate(dismissedConstraints)
+
+    UIView.animate(
+      withDuration: transitionDuration(using: context),
+      delay: 0,
+      usingSpringWithDamping: 1,
+      initialSpringVelocity: 0,
+      options: .curveEaseInOut,
+      animations: { [weak darkOverlayView] in
+        darkOverlayView?.alpha = 0.0
+        context.containerView.setNeedsLayout()
+        context.containerView.layoutIfNeeded()
+      },
+      completion: { [weak self] _ in
+        context.completeTransition(true)
+        self?.onDismissal()
+      })
+  }
+
+  @objc private func didTapOverlay() {
+    if let presentingController {
+      presentingController.dismiss(animated: true)
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentNickname.swift b/Sources/AppNavigation/PresentNickname.swift
index 80121bbf..96bb9295 100644
--- a/Sources/AppNavigation/PresentNickname.swift
+++ b/Sources/AppNavigation/PresentNickname.swift
@@ -1,4 +1,5 @@
 import UIKit
+import ScrollViewController
 
 /// Opens up `Nickname` on a given parent view controller
 public struct PresentNickname: Action {
@@ -35,27 +36,31 @@ public struct PresentNickname: Action {
 /// Performs `PresentNickname` action
 public struct PresentNicknameNavigator: TypedNavigator {
   /// Custom transitioning delegate
-  let transitioningDelegate = BottomTransitioningDelegate()
+  let transitioningDelegate = FullscreenTransitioningDelegate()
 
   /// View controller which should be opened up
   var viewController: (String, @escaping (String) -> Void) -> UIViewController
 
   /// - Parameters:
   ///   - viewController: view controller which should be presented
-  public init(_ viewController: @escaping (
-    String, @escaping (String) -> Void
-  ) -> UIViewController
-  ) {
+  public init(_ viewController: @escaping (String, @escaping (String) -> Void) -> UIViewController) {
     self.viewController = viewController
   }
 
   public func perform(_ action: PresentNickname, completion: @escaping () -> Void) {
+    let scrollViewController = ScrollViewController()
     let controller = viewController(action.prefilled, action.completion)
+    scrollViewController.addChild(controller)
+    scrollViewController.contentView = controller.view
+    scrollViewController.wrapperView.handlesTouchesOutsideContent = true
+    scrollViewController.wrapperView.alignContentToBottom = true
+    scrollViewController.scrollView.bounces = false
+    controller.didMove(toParent: scrollViewController)
     controller.transitioningDelegate = transitioningDelegate
-    controller.modalPresentationStyle = .overFullScreen
+    scrollViewController.modalPresentationStyle = .overFullScreen
 
     action.parent.present(
-      controller,
+      scrollViewController,
       animated: action.animated,
       completion: completion
     )
-- 
GitLab