forked from shadowfacts/Tusker
Lists in new sidebar
This commit is contained in:
parent
9891b601a8
commit
dffa5d8f75
|
@ -7,12 +7,20 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
@available(iOS 18.0, *)
|
@available(iOS 18.0, *)
|
||||||
class NewMainTabBarViewController: BaseMainTabBarViewController {
|
class NewMainTabBarViewController: BaseMainTabBarViewController {
|
||||||
|
|
||||||
private let composePlaceholder = UIViewController()
|
private let composePlaceholder = UIViewController()
|
||||||
|
|
||||||
|
private var listsGroup: UITabGroup!
|
||||||
|
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private var navigationStacks = [String: [UIViewController]]()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
@ -26,19 +34,54 @@ class NewMainTabBarViewController: BaseMainTabBarViewController {
|
||||||
self.makeViewController(for: tab)
|
self.makeViewController(for: tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
let topLevelTabs = [
|
let homeTab = UITab(title: "Home", image: UIImage(systemName: "house"), identifier: Tab.home.rawValue, viewControllerProvider: viewControllerProvider)
|
||||||
Tab.home,
|
let notificationsTab = UITab(title: "Notifications", image: UIImage(systemName: "bell"), identifier: Tab.notifications.rawValue, viewControllerProvider: viewControllerProvider)
|
||||||
.notifications,
|
let composeTab = UITab(title: "Compose", image: UIImage(systemName: "pencil"), identifier: Tab.compose.rawValue, viewControllerProvider: viewControllerProvider)
|
||||||
.compose,
|
let exploreTab = UITab(title: "Explore", image: UIImage(systemName: "magnifyingglass"), identifier: Tab.explore.rawValue, viewControllerProvider: viewControllerProvider)
|
||||||
.explore,
|
let bookmarksTab = UITab(title: "Bookmarks", image: UIImage(systemName: "bookmark"), identifier: Tab.bookmarks.rawValue, viewControllerProvider: viewControllerProvider)
|
||||||
.myProfile
|
bookmarksTab.preferredPlacement = .optional
|
||||||
].map {
|
let favoritesTab = UITab(title: "Favorites", image: UIImage(systemName: "star"), identifier: Tab.favorites.rawValue, viewControllerProvider: viewControllerProvider)
|
||||||
UITab(title: $0.title, image: UIImage(systemName: $0.imageName), identifier: $0.rawValue, viewControllerProvider: viewControllerProvider)
|
favoritesTab.preferredPlacement = .optional
|
||||||
|
let myProfileTab = UITab(title: "My Profile", image: UIImage(systemName: "person"), identifier: Tab.myProfile.rawValue, viewControllerProvider: viewControllerProvider)
|
||||||
|
|
||||||
|
listsGroup = UITabGroup(title: "Lists", image: nil, identifier: Tab.lists.rawValue, children: []) { _ in
|
||||||
|
// this closure is necessary to prevent UIKit from crashing (FB14860961)
|
||||||
|
return MultiColumnNavigationController()
|
||||||
|
}
|
||||||
|
listsGroup.preferredPlacement = .sidebarOnly
|
||||||
|
listsGroup.sidebarActions = [
|
||||||
|
UIAction(title: "New List…", image: UIImage(systemName: "plus"), handler: { _ in
|
||||||
|
fatalError("TODO")
|
||||||
|
})
|
||||||
|
]
|
||||||
|
reloadLists(mastodonController.lists)
|
||||||
|
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
|
self.tabs = [
|
||||||
|
homeTab,
|
||||||
|
notificationsTab,
|
||||||
|
composeTab,
|
||||||
|
exploreTab,
|
||||||
|
myProfileTab,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
self.tabs = [
|
||||||
|
homeTab,
|
||||||
|
notificationsTab,
|
||||||
|
exploreTab,
|
||||||
|
bookmarksTab,
|
||||||
|
favoritesTab,
|
||||||
|
myProfileTab,
|
||||||
|
composeTab,
|
||||||
|
listsGroup,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tabs = topLevelTabs
|
|
||||||
|
|
||||||
setupFastAccountSwitcher()
|
setupFastAccountSwitcher()
|
||||||
|
|
||||||
|
mastodonController.$lists
|
||||||
|
.sink { [unowned self] in self.reloadLists($0) }
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeViewController(for tab: UITab) -> UIViewController {
|
private func makeViewController(for tab: UITab) -> UIViewController {
|
||||||
|
@ -55,9 +98,19 @@ class NewMainTabBarViewController: BaseMainTabBarViewController {
|
||||||
return composePlaceholder
|
return composePlaceholder
|
||||||
case .explore:
|
case .explore:
|
||||||
root = ExploreViewController(mastodonController: mastodonController)
|
root = ExploreViewController(mastodonController: mastodonController)
|
||||||
|
case .bookmarks:
|
||||||
|
root = BookmarksViewController(mastodonController: mastodonController)
|
||||||
|
case .favorites:
|
||||||
|
root = FavoritesViewController(mastodonController: mastodonController)
|
||||||
case .myProfile:
|
case .myProfile:
|
||||||
root = MyProfileViewController(mastodonController: mastodonController)
|
root = MyProfileViewController(mastodonController: mastodonController)
|
||||||
|
case .lists:
|
||||||
|
fatalError("unreachable")
|
||||||
}
|
}
|
||||||
|
return NewMainTabBarViewController.embedInNavigationController(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func embedInNavigationController(_ vc: UIViewController) -> UIViewController {
|
||||||
let nav: any NavigationControllerProtocol
|
let nav: any NavigationControllerProtocol
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
nav = EnhancedNavigationViewController()
|
nav = EnhancedNavigationViewController()
|
||||||
|
@ -72,10 +125,18 @@ class NewMainTabBarViewController: BaseMainTabBarViewController {
|
||||||
nav = MultiColumnNavigationController()
|
nav = MultiColumnNavigationController()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nav.viewControllers = [root]
|
nav.viewControllers = [vc]
|
||||||
return nav
|
return nav
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func reloadLists(_ lists: [List]) {
|
||||||
|
listsGroup.children = lists.map { list in
|
||||||
|
UITab(title: list.title, image: UIImage(systemName: "list.bullet"), identifier: "list:\(list.id)") { [unowned self] _ in
|
||||||
|
NewMainTabBarViewController.embedInNavigationController(ListTimelineViewController(for: list, mastodonController: self.mastodonController))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc func handleComposeKeyCommand() {
|
@objc func handleComposeKeyCommand() {
|
||||||
compose(editing: nil)
|
compose(editing: nil)
|
||||||
}
|
}
|
||||||
|
@ -94,37 +155,11 @@ extension NewMainTabBarViewController {
|
||||||
case notifications
|
case notifications
|
||||||
case compose
|
case compose
|
||||||
case explore
|
case explore
|
||||||
|
case bookmarks
|
||||||
|
case favorites
|
||||||
case myProfile
|
case myProfile
|
||||||
|
|
||||||
var title: String {
|
case lists
|
||||||
switch self {
|
|
||||||
case .home:
|
|
||||||
"Home"
|
|
||||||
case .notifications:
|
|
||||||
"Notifications"
|
|
||||||
case .compose:
|
|
||||||
"Compose"
|
|
||||||
case .explore:
|
|
||||||
"Explore"
|
|
||||||
case .myProfile:
|
|
||||||
"My Profile"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageName: String {
|
|
||||||
switch self {
|
|
||||||
case .home:
|
|
||||||
"house"
|
|
||||||
case .notifications:
|
|
||||||
"bell"
|
|
||||||
case .compose:
|
|
||||||
"pencil"
|
|
||||||
case .explore:
|
|
||||||
"magnifyingglass"
|
|
||||||
case .myProfile:
|
|
||||||
"person"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +191,34 @@ extension NewMainTabBarViewController: UITabBarControllerDelegate {
|
||||||
if let vc = newTab.viewController as? MultiColumnNavigationController {
|
if let vc = newTab.viewController as? MultiColumnNavigationController {
|
||||||
self.updateViewControllerSafeAreaInsets(vc)
|
self.updateViewControllerSafeAreaInsets(vc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All tabs in a tab group deliberately share the same view controller, so we have to do this ourselves.
|
||||||
|
// I think this is pretty unfortunate API design--half the time, the tab bar controller takes care of
|
||||||
|
// this, but the rest of the time it's up to you.
|
||||||
|
// The managingNavigationController API would theoretically solve this, but split-screen/multi-column
|
||||||
|
// nav can't straightforwardly be implemented as UINavigationController subclasses.
|
||||||
|
// Unfortunately this, in turn, means that when switching between tabs in the same group, we don't
|
||||||
|
// get the new transition animation.
|
||||||
|
// This would be much less complicated if the controller just used the individual VCs of items in a group.
|
||||||
|
if let group = newTab.parent,
|
||||||
|
group.identifier == Tab.lists.rawValue,
|
||||||
|
let nav = group.viewController as? any NavigationControllerProtocol {
|
||||||
|
if let multiColumn = nav as? MultiColumnNavigationController {
|
||||||
|
updateViewControllerSafeAreaInsets(multiColumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let previousTab {
|
||||||
|
navigationStacks[previousTab.identifier] = nav.viewControllers
|
||||||
|
}
|
||||||
|
|
||||||
|
if let existing = navigationStacks[newTab.identifier] {
|
||||||
|
nav.viewControllers = existing
|
||||||
|
} else if let newNav = newTab.viewController as? any NavigationControllerProtocol {
|
||||||
|
nav.viewControllers = newNav.viewControllers
|
||||||
|
} else {
|
||||||
|
fatalError("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue