Add saved/followed hashtags to new sidebar

This commit is contained in:
Shadowfacts 2024-08-21 16:58:16 -04:00
parent 59d43fd3f6
commit 0d9eed73dd
1 changed files with 103 additions and 20 deletions

View File

@ -24,6 +24,7 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
private var favoritesTab: UITab! private var favoritesTab: UITab!
private var myProfileTab: UITab! private var myProfileTab: UITab!
private var listsGroup: UITabGroup! private var listsGroup: UITabGroup!
private var hashtagsGroup: UITabGroup!
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@ -67,6 +68,17 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
] ]
reloadLists(mastodonController.lists) reloadLists(mastodonController.lists)
hashtagsGroup = UITabGroup(title: "Hashtags", image: nil, identifier: Tab.hashtags.rawValue, children: []) { _ in
return AdaptableNavigationController()
}
hashtagsGroup.preferredPlacement = .sidebarOnly
hashtagsGroup.sidebarActions = [
UIAction(title: "Add Hashtag…", image: UIImage(systemName: "plus"), handler: { [unowned self] _ in
self.showAddSavedHashtag()
})
]
reloadHashtags()
if UIDevice.current.userInterfaceIdiom == .phone { if UIDevice.current.userInterfaceIdiom == .phone {
self.tabs = [ self.tabs = [
homeTab, homeTab,
@ -83,13 +95,19 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
let vcToUpdate = self.selectedTab!.parent?.viewController ?? self.selectedTab!.viewController! let vcToUpdate = self.selectedTab!.parent?.viewController ?? self.selectedTab!.viewController!
self.updateViewControllerSafeAreaInsets(vcToUpdate) self.updateViewControllerSafeAreaInsets(vcToUpdate)
} }
mastodonController.$lists
.sink { [unowned self] in self.reloadLists($0) }
.store(in: &cancellables)
mastodonController.$followedHashtags
.map { _ in () }
.merge(with: NotificationCenter.default.publisher(for: .savedHashtagsChanged).map { _ in () })
.sink { [unowned self] in self.reloadHashtags() }
.store(in: &cancellables)
} }
setupFastAccountSwitcher() setupFastAccountSwitcher()
mastodonController.$lists
.sink { [unowned self] in self.reloadLists($0) }
.store(in: &cancellables)
} }
private func updatePadTabs() { private func updatePadTabs() {
@ -99,8 +117,9 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
isCompact = true isCompact = true
var exploreNavStack: [UIViewController]? = nil var exploreNavStack: [UIViewController]? = nil
if selectedTab?.parent == listsGroup { if let parent = selectedTab?.parent,
let nav = listsGroup.viewController as! any NavigationControllerProtocol parent === listsGroup || parent === hashtagsGroup {
let nav = parent.viewController as! any NavigationControllerProtocol
exploreNavStack = nav.viewControllers exploreNavStack = nav.viewControllers
nav.viewControllers = [] nav.viewControllers = []
} }
@ -121,24 +140,33 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
} else { } else {
isCompact = false isCompact = false
var newTab: (UITab, [UIViewController])? = nil var newTabAndNavigationStack: (UITab, [UIViewController])? = nil
if wasCompact == true, if wasCompact == true,
selectedTab == exploreTab { selectedTab == exploreTab {
let nav = exploreTab.viewController as! any NavigationControllerProtocol let nav = exploreTab.viewController as! any NavigationControllerProtocol
// skip over the ExploreViewController // skip over the ExploreViewController
if nav.viewControllers.count > 1 { if nav.viewControllers.count > 1 {
var newTab: UITab?
switch nav.viewControllers[1] { switch nav.viewControllers[1] {
case let listVC as ListTimelineViewController: case let listVC as ListTimelineViewController:
if let tab = listsGroup.tab(forIdentifier: ListTab.identifier(for: listVC.list)) { if let tab = listsGroup.tab(forIdentifier: ListTab.identifier(for: listVC.list)) {
newTab = (tab, Array(nav.viewControllers[1...])) newTab = tab
nav.viewControllers = [ }
nav.viewControllers[0], // leave the ExploreVC in place case let hashtagVC as HashtagTimelineViewController:
InlineTrendsViewController(mastodonController: mastodonController), // re-insert an InlineTrendsVC if let tab = hashtagsGroup.tab(forIdentifier: HashtagTab.identifier(for: hashtagVC.hashtagName)) {
] newTab = tab
} }
default: default:
break break
} }
if let newTab {
newTabAndNavigationStack = (newTab, Array(nav.viewControllers[1...]))
nav.viewControllers = [
nav.viewControllers[0], // leave the ExploreVC in place
InlineTrendsViewController(mastodonController: mastodonController), // re-insert an InlineTrendsVC
]
}
} }
} }
@ -151,9 +179,10 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
myProfileTab, myProfileTab,
composeTab, composeTab,
listsGroup, listsGroup,
hashtagsGroup,
] ]
if let (tab, navStack) = newTab { if let (tab, navStack) = newTabAndNavigationStack {
let nav = tab.parent!.viewController as! any NavigationControllerProtocol let nav = tab.parent!.viewController as! any NavigationControllerProtocol
nav.viewControllers = navStack nav.viewControllers = navStack
// Setting the tab now seems to be clobbered by the UITabBarController itself updating in response // Setting the tab now seems to be clobbered by the UITabBarController itself updating in response
@ -193,7 +222,7 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
root = FavoritesViewController(mastodonController: mastodonController) root = FavoritesViewController(mastodonController: mastodonController)
case .myProfile: case .myProfile:
root = MyProfileViewController(mastodonController: mastodonController) root = MyProfileViewController(mastodonController: mastodonController)
case .lists: case .lists, .hashtags:
fatalError("unreachable") fatalError("unreachable")
} }
return embedInNavigationController(root) return embedInNavigationController(root)
@ -217,11 +246,37 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
} }
private func reloadLists(_ lists: [List]) { private func reloadLists(_ lists: [List]) {
listsGroup.children = lists.map { list in let viewControllerProvider = { [unowned self] (tab: UITab) in
ListTab(list: list) { [unowned self] _ in let tab = tab as! ListTab
return ListTimelineViewController(for: list, mastodonController: self.mastodonController) return ListTimelineViewController(for: tab.list, mastodonController: self.mastodonController)
}
} }
listsGroup.children = lists.map { list in
ListTab(list: list, viewControllerProvider: viewControllerProvider)
}
}
private func reloadHashtags() {
let viewControllerProvider = { [unowned self] (tab: UITab) in
let tab = tab as! HashtagTab
return HashtagTimelineViewController(forNamed: tab.hashtagName, mastodonController: self.mastodonController)
}
var seenTags: Set<String> = []
var tabs: [UITab] = []
let savedReq = SavedHashtag.fetchRequest(account: mastodonController.accountInfo!)
let saved = (try? mastodonController.persistentContainer.viewContext.fetch(savedReq)) ?? []
for hashtag in saved {
seenTags.insert(hashtag.name)
tabs.append(HashtagTab(hashtagName: hashtag.name, viewControllerProvider: viewControllerProvider))
}
let followedReq = FollowedHashtag.fetchRequest()
let followed = (try? mastodonController.persistentContainer.viewContext.fetch(followedReq)) ?? []
for hashtag in followed where !seenTags.contains(hashtag.name) {
tabs.append(HashtagTab(hashtagName: hashtag.name, viewControllerProvider: viewControllerProvider))
}
tabs.sort(using: SemiCaseSensitiveComparator.keyPath(\.title))
hashtagsGroup.children = tabs
} }
@objc func handleComposeKeyCommand() { @objc func handleComposeKeyCommand() {
@ -244,6 +299,12 @@ final class NewMainTabBarViewController: BaseMainTabBarViewController {
service.run() service.run()
} }
private func showAddSavedHashtag() {
let addController = AddSavedHashtagViewController(mastodonController: mastodonController)
let nav = EnhancedNavigationViewController(rootViewController: addController)
present(nav, animated: true)
}
fileprivate func updateViewControllerSafeAreaInsets(_ vc: UIViewController) { fileprivate func updateViewControllerSafeAreaInsets(_ vc: UIViewController) {
guard vc is MultiColumnNavigationController || (vc as? AdaptableNavigationController)?.current is MultiColumnNavigationController else { guard vc is MultiColumnNavigationController || (vc as? AdaptableNavigationController)?.current is MultiColumnNavigationController else {
return return
@ -317,6 +378,7 @@ extension NewMainTabBarViewController {
case myProfile case myProfile
case lists case lists
case hashtags
} }
} }
@ -356,7 +418,7 @@ extension NewMainTabBarViewController: UITabBarControllerDelegate {
// get the new transition animation. // get the new transition animation.
// This would be much less complicated if the controller just used the individual VCs of items in a group. // This would be much less complicated if the controller just used the individual VCs of items in a group.
if let group = newTab.parent, if let group = newTab.parent,
group.identifier == Tab.lists.rawValue, group === listsGroup || group === hashtagsGroup,
let nav = group.viewController as? any NavigationControllerProtocol { let nav = group.viewController as? any NavigationControllerProtocol {
updateViewControllerSafeAreaInsets(nav) updateViewControllerSafeAreaInsets(nav)
@ -430,6 +492,13 @@ extension NewMainTabBarViewController: UITabBarController.Sidebar.Delegate {
} else { } else {
return nil return nil
} }
} else if let hashtagTab = tab as? HashtagTab {
let timelineActivity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtagTab.hashtagName), accountID: id)
if let timelineActivity {
activity = timelineActivity
} else {
return nil
}
} else if let tabID = Tab(rawValue: tab.identifier) { } else if let tabID = Tab(rawValue: tab.identifier) {
switch tabID { switch tabID {
case .home: case .home:
@ -448,7 +517,7 @@ extension NewMainTabBarViewController: UITabBarController.Sidebar.Delegate {
return nil return nil
case .compose: case .compose:
activity = UserActivityManager.newPostActivity(accountID: id) activity = UserActivityManager.newPostActivity(accountID: id)
case .lists: case .lists, .hashtags:
return nil return nil
} }
} else { } else {
@ -671,3 +740,17 @@ private class ListTab: UITab {
"list:\(list.id)" "list:\(list.id)"
} }
} }
@available(iOS 18.0, *)
private class HashtagTab: UITab {
let hashtagName: String
init(hashtagName: String, viewControllerProvider: @escaping (UITab) -> UIViewController) {
self.hashtagName = hashtagName
super.init(title: hashtagName, image: UIImage(systemName: "number"), identifier: Self.identifier(for: hashtagName), viewControllerProvider: viewControllerProvider)
}
static func identifier(for name: String) -> String {
"hashtag:\(name)"
}
}