Lists in new sidebar

This commit is contained in:
Shadowfacts 2024-08-20 11:55:19 -04:00
parent 9891b601a8
commit dffa5d8f75
1 changed files with 103 additions and 40 deletions

View File

@ -7,11 +7,19 @@
//
import UIKit
import Combine
import Pachyderm
@available(iOS 18.0, *)
class NewMainTabBarViewController: BaseMainTabBarViewController {
private let composePlaceholder = UIViewController()
private var listsGroup: UITabGroup!
private var cancellables = Set<AnyCancellable>()
private var navigationStacks = [String: [UIViewController]]()
override func viewDidLoad() {
super.viewDidLoad()
@ -26,19 +34,54 @@ class NewMainTabBarViewController: BaseMainTabBarViewController {
self.makeViewController(for: tab)
}
let topLevelTabs = [
Tab.home,
.notifications,
.compose,
.explore,
.myProfile
].map {
UITab(title: $0.title, image: UIImage(systemName: $0.imageName), identifier: $0.rawValue, viewControllerProvider: viewControllerProvider)
let homeTab = UITab(title: "Home", image: UIImage(systemName: "house"), identifier: Tab.home.rawValue, viewControllerProvider: viewControllerProvider)
let notificationsTab = UITab(title: "Notifications", image: UIImage(systemName: "bell"), identifier: Tab.notifications.rawValue, viewControllerProvider: viewControllerProvider)
let composeTab = UITab(title: "Compose", image: UIImage(systemName: "pencil"), identifier: Tab.compose.rawValue, viewControllerProvider: viewControllerProvider)
let exploreTab = UITab(title: "Explore", image: UIImage(systemName: "magnifyingglass"), identifier: Tab.explore.rawValue, viewControllerProvider: viewControllerProvider)
let bookmarksTab = UITab(title: "Bookmarks", image: UIImage(systemName: "bookmark"), identifier: Tab.bookmarks.rawValue, viewControllerProvider: viewControllerProvider)
bookmarksTab.preferredPlacement = .optional
let favoritesTab = UITab(title: "Favorites", image: UIImage(systemName: "star"), identifier: Tab.favorites.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()
mastodonController.$lists
.sink { [unowned self] in self.reloadLists($0) }
.store(in: &cancellables)
}
private func makeViewController(for tab: UITab) -> UIViewController {
@ -55,9 +98,19 @@ class NewMainTabBarViewController: BaseMainTabBarViewController {
return composePlaceholder
case .explore:
root = ExploreViewController(mastodonController: mastodonController)
case .bookmarks:
root = BookmarksViewController(mastodonController: mastodonController)
case .favorites:
root = FavoritesViewController(mastodonController: mastodonController)
case .myProfile:
root = MyProfileViewController(mastodonController: mastodonController)
case .lists:
fatalError("unreachable")
}
return NewMainTabBarViewController.embedInNavigationController(root)
}
private static func embedInNavigationController(_ vc: UIViewController) -> UIViewController {
let nav: any NavigationControllerProtocol
if UIDevice.current.userInterfaceIdiom == .phone {
nav = EnhancedNavigationViewController()
@ -72,10 +125,18 @@ class NewMainTabBarViewController: BaseMainTabBarViewController {
nav = MultiColumnNavigationController()
}
}
nav.viewControllers = [root]
nav.viewControllers = [vc]
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() {
compose(editing: nil)
}
@ -94,37 +155,11 @@ extension NewMainTabBarViewController {
case notifications
case compose
case explore
case bookmarks
case favorites
case myProfile
var title: String {
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"
}
}
case lists
}
}
@ -156,6 +191,34 @@ extension NewMainTabBarViewController: UITabBarControllerDelegate {
if let vc = newTab.viewController as? MultiColumnNavigationController {
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")
}
}
}
}