Compare commits
No commits in common. "473ef018c9a815c5de1ddfc1bcadbf6002f47cee" and "203c1852d4d0bce087358626b1c5b2b8efd498f7" have entirely different histories.
473ef018c9
...
203c1852d4
|
@ -8,6 +8,8 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
public protocol DuckableViewController: UIViewController {
|
public protocol DuckableViewController: UIViewController {
|
||||||
|
var duckableDelegate: DuckableViewControllerDelegate? { get set }
|
||||||
|
|
||||||
func duckableViewControllerShouldDuck() -> DuckAttemptAction
|
func duckableViewControllerShouldDuck() -> DuckAttemptAction
|
||||||
|
|
||||||
func duckableViewControllerMayAttemptToDuck()
|
func duckableViewControllerMayAttemptToDuck()
|
||||||
|
@ -24,6 +26,10 @@ extension DuckableViewController {
|
||||||
public func duckableViewControllerDidFinishAnimatingDuck() {}
|
public func duckableViewControllerDidFinishAnimatingDuck() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public protocol DuckableViewControllerDelegate: AnyObject {
|
||||||
|
func duckableViewControllerWillDismiss(animated: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
public enum DuckAttemptAction {
|
public enum DuckAttemptAction {
|
||||||
case duck
|
case duck
|
||||||
case dismiss
|
case dismiss
|
||||||
|
|
|
@ -11,7 +11,7 @@ let duckedCornerRadius: CGFloat = 10
|
||||||
let detentHeight: CGFloat = 44
|
let detentHeight: CGFloat = 44
|
||||||
|
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
public class DuckableContainerViewController: UIViewController {
|
public class DuckableContainerViewController: UIViewController, DuckableViewControllerDelegate {
|
||||||
|
|
||||||
public let child: UIViewController
|
public let child: UIViewController
|
||||||
private var bottomConstraint: NSLayoutConstraint!
|
private var bottomConstraint: NSLayoutConstraint!
|
||||||
|
@ -87,6 +87,7 @@ public class DuckableContainerViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doPresentDuckable(_ viewController: DuckableViewController, animated: Bool, completion: (() -> Void)?) {
|
private func doPresentDuckable(_ viewController: DuckableViewController, animated: Bool, completion: (() -> Void)?) {
|
||||||
|
viewController.duckableDelegate = self
|
||||||
viewController.modalPresentationStyle = .custom
|
viewController.modalPresentationStyle = .custom
|
||||||
viewController.transitioningDelegate = self
|
viewController.transitioningDelegate = self
|
||||||
present(viewController, animated: animated) {
|
present(viewController, animated: animated) {
|
||||||
|
@ -95,10 +96,7 @@ public class DuckableContainerViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dismissalTransitionWillBegin() {
|
public func duckableViewControllerWillDismiss(animated: Bool) {
|
||||||
guard case .presentingDucked(_, _) = state else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
state = .idle
|
state = .idle
|
||||||
bottomConstraint.isActive = false
|
bottomConstraint.isActive = false
|
||||||
bottomConstraint = child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
bottomConstraint = child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||||
|
@ -146,7 +144,7 @@ public class DuckableContainerViewController: UIViewController {
|
||||||
case .block:
|
case .block:
|
||||||
viewController.sheetPresentationController!.selectedDetentIdentifier = .large
|
viewController.sheetPresentationController!.selectedDetentIdentifier = .large
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
// duckableViewControllerWillDismiss()
|
duckableViewControllerWillDismiss(animated: true)
|
||||||
dismiss(animated: true)
|
dismiss(animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +189,7 @@ public class DuckableContainerViewController: UIViewController {
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
extension DuckableContainerViewController: UIViewControllerTransitioningDelegate {
|
extension DuckableContainerViewController: UIViewControllerTransitioningDelegate {
|
||||||
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||||
let controller = DuckableSheetPresentationController(presentedViewController: presented, presenting: presenting)
|
let controller = UISheetPresentationController(presentedViewController: presented, presenting: presenting)
|
||||||
controller.delegate = self
|
controller.delegate = self
|
||||||
controller.prefersGrabberVisible = true
|
controller.prefersGrabberVisible = true
|
||||||
controller.selectedDetentIdentifier = .large
|
controller.selectedDetentIdentifier = .large
|
||||||
|
@ -217,14 +215,6 @@ extension DuckableContainerViewController: UIViewControllerTransitioningDelegate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 16.0, *)
|
|
||||||
class DuckableSheetPresentationController: UISheetPresentationController {
|
|
||||||
override func dismissalTransitionWillBegin() {
|
|
||||||
super.dismissalTransitionWillBegin()
|
|
||||||
(self.delegate as! DuckableContainerViewController).dismissalTransitionWillBegin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
extension DuckableContainerViewController: UISheetPresentationControllerDelegate {
|
extension DuckableContainerViewController: UISheetPresentationControllerDelegate {
|
||||||
public func presentationController(_ presentationController: UIPresentationController, willPresentWithAdaptiveStyle style: UIModalPresentationStyle, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
|
public func presentationController(_ presentationController: UIPresentationController, willPresentWithAdaptiveStyle style: UIModalPresentationStyle, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ protocol ComposeHostingControllerDelegate: AnyObject {
|
||||||
class ComposeHostingController: UIHostingController<ComposeHostingController.View>, DuckableViewController {
|
class ComposeHostingController: UIHostingController<ComposeHostingController.View>, DuckableViewController {
|
||||||
|
|
||||||
weak var delegate: ComposeHostingControllerDelegate?
|
weak var delegate: ComposeHostingControllerDelegate?
|
||||||
|
weak var duckableDelegate: DuckableViewControllerDelegate?
|
||||||
|
|
||||||
let controller: ComposeController
|
let controller: ComposeController
|
||||||
let mastodonController: MastodonController
|
let mastodonController: MastodonController
|
||||||
|
@ -106,6 +107,7 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Vie
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
dismiss(animated: true)
|
dismiss(animated: true)
|
||||||
|
duckableDelegate?.duckableViewControllerWillDismiss(animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
|
|
||||||
case let .tab(tab):
|
case let .tab(tab):
|
||||||
// sidebar items that map 1 <-> 1 can be transferred directly
|
// sidebar items that map 1 <-> 1 can be transferred directly
|
||||||
tabBarViewController.select(tab: tab, dismissPresented: false)
|
tabBarViewController.select(tab: tab)
|
||||||
|
|
||||||
case .explore:
|
case .explore:
|
||||||
// Search sidebar item maps to the Explore tab with the search controller/results visible
|
// Search sidebar item maps to the Explore tab with the search controller/results visible
|
||||||
|
@ -268,10 +268,10 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
||||||
// Transfer the navigation stack, dropping the search VC, to keep anything the user has opened
|
// Transfer the navigation stack, dropping the search VC, to keep anything the user has opened
|
||||||
transferNavigationStack(from: .explore, to: exploreNav, dropFirst: true, append: true)
|
transferNavigationStack(from: .explore, to: exploreNav, dropFirst: true, append: true)
|
||||||
|
|
||||||
tabBarViewController.select(tab: .explore, dismissPresented: false)
|
tabBarViewController.select(tab: .explore)
|
||||||
|
|
||||||
case .bookmarks, .favorites, .list(_), .savedHashtag(_), .savedInstance(_):
|
case .bookmarks, .favorites, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||||
tabBarViewController.select(tab: .explore, dismissPresented: false)
|
tabBarViewController.select(tab: .explore)
|
||||||
// Make sure the Explore VC doesn't show its search bar when it appears, in case the user was previously
|
// Make sure the Explore VC doesn't show its search bar when it appears, in case the user was previously
|
||||||
// in compact mode and performing a search.
|
// in compact mode and performing a search.
|
||||||
let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController
|
let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController
|
||||||
|
|
|
@ -111,13 +111,13 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
repositionFastSwitcherIndicator()
|
repositionFastSwitcherIndicator()
|
||||||
}
|
}
|
||||||
|
|
||||||
func select(tab: Tab, dismissPresented: Bool) {
|
func select(tab: Tab) {
|
||||||
if tab == .compose {
|
if tab == .compose {
|
||||||
compose(editing: nil)
|
compose(editing: nil)
|
||||||
} else {
|
} else {
|
||||||
// when switching tabs, dismiss the currently presented VC
|
// when switching tabs, dismiss the currently presented VC
|
||||||
// otherwise the selected tab changes behind the presented VC
|
// otherwise the selected tab changes behind the presented VC
|
||||||
if presentedViewController != nil && dismissPresented {
|
if presentedViewController != nil {
|
||||||
dismiss(animated: true) {
|
dismiss(animated: true) {
|
||||||
self.selectedIndex = tab.rawValue
|
self.selectedIndex = tab.rawValue
|
||||||
}
|
}
|
||||||
|
@ -141,8 +141,8 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
NSLayoutConstraint.deactivate(fastSwitcherConstraints)
|
NSLayoutConstraint.deactivate(fastSwitcherConstraints)
|
||||||
let isPortrait = view.bounds.width < view.bounds.height
|
// using interfaceOrientation isn't ideal, but UITabBar buttons may lay out horizontally even in the compact size class
|
||||||
if traitCollection.horizontalSizeClass == .compact && isPortrait {
|
if traitCollection.horizontalSizeClass == .compact && interfaceOrientation.isPortrait {
|
||||||
fastSwitcherConstraints = [
|
fastSwitcherConstraints = [
|
||||||
fastSwitcherIndicator.centerYAnchor.constraint(equalTo: myProfileButton.centerYAnchor, constant: -4),
|
fastSwitcherIndicator.centerYAnchor.constraint(equalTo: myProfileButton.centerYAnchor, constant: -4),
|
||||||
// tab bar button image width is 30
|
// tab bar button image width is 30
|
||||||
|
@ -291,18 +291,18 @@ extension MainTabBarViewController: TuskerRootViewController {
|
||||||
func select(route: TuskerRoute, animated: Bool) {
|
func select(route: TuskerRoute, animated: Bool) {
|
||||||
switch route {
|
switch route {
|
||||||
case .timelines:
|
case .timelines:
|
||||||
select(tab: .timelines, dismissPresented: true)
|
select(tab: .timelines)
|
||||||
case .notifications:
|
case .notifications:
|
||||||
select(tab: .notifications, dismissPresented: true)
|
select(tab: .notifications)
|
||||||
case .myProfile:
|
case .myProfile:
|
||||||
select(tab: .myProfile, dismissPresented: true)
|
select(tab: .myProfile)
|
||||||
case .explore:
|
case .explore:
|
||||||
select(tab: .explore, dismissPresented: true)
|
select(tab: .explore)
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
select(tab: .explore, dismissPresented: true)
|
select(tab: .explore)
|
||||||
getNavigationController().pushViewController(BookmarksViewController(mastodonController: mastodonController), animated: animated)
|
getNavigationController().pushViewController(BookmarksViewController(mastodonController: mastodonController), animated: animated)
|
||||||
case .list(id: let id):
|
case .list(id: let id):
|
||||||
select(tab: .explore, dismissPresented: true)
|
select(tab: .explore)
|
||||||
if let list = mastodonController.getCachedList(id: id) {
|
if let list = mastodonController.getCachedList(id: id) {
|
||||||
let nav = getNavigationController()
|
let nav = getNavigationController()
|
||||||
_ = nav.popToRootViewController(animated: animated)
|
_ = nav.popToRootViewController(animated: animated)
|
||||||
|
@ -325,7 +325,7 @@ extension MainTabBarViewController: TuskerRootViewController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
select(tab: .explore, dismissPresented: true)
|
select(tab: .explore)
|
||||||
exploreNavController.popToRootViewController(animated: false)
|
exploreNavController.popToRootViewController(animated: false)
|
||||||
|
|
||||||
// setting searchController.isActive directly doesn't work until the view has loaded/appeared for the first time
|
// setting searchController.isActive directly doesn't work until the view has loaded/appeared for the first time
|
||||||
|
|
Loading…
Reference in New Issue