Refactor tab/MainTabBarController to add viewcontrollers as properties

This is a WIP-step for account-stuff. Also: iPhone only, iPad should come next
This commit is contained in:
Nathan Mattes 2024-01-11 23:36:13 +01:00
parent cea6129229
commit 2c653320fb
8 changed files with 180 additions and 155 deletions

View File

@ -166,6 +166,7 @@
D8B5E4F22A4EBCF90008970C /* NotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */; }; D8B5E4F22A4EBCF90008970C /* NotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */; };
D8B5E4F42A4ED0240008970C /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4F32A4ED0240008970C /* NotificationSettingsViewModel.swift */; }; D8B5E4F42A4ED0240008970C /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4F32A4ED0240008970C /* NotificationSettingsViewModel.swift */; };
D8BE30B32A179E26006B8270 /* SuggestionAccountTableViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */; }; D8BE30B32A179E26006B8270 /* SuggestionAccountTableViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */; };
D8CF45832B50893900C84D70 /* Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8CF45822B50893900C84D70 /* Tab.swift */; };
D8D688F62AB869CB000F651A /* SearchResultsProfileTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */; }; D8D688F62AB869CB000F651A /* SearchResultsProfileTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */; };
D8D688F92AB8B970000F651A /* SearchResultOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */; }; D8D688F92AB8B970000F651A /* SearchResultOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */; };
D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; }; D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; };
@ -833,6 +834,7 @@
D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettings.swift; sourceTree = "<group>"; }; D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettings.swift; sourceTree = "<group>"; };
D8B5E4F32A4ED0240008970C /* NotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewModel.swift; sourceTree = "<group>"; }; D8B5E4F32A4ED0240008970C /* NotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewModel.swift; sourceTree = "<group>"; };
D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewFooter.swift; sourceTree = "<group>"; }; D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewFooter.swift; sourceTree = "<group>"; };
D8CF45822B50893900C84D70 /* Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tab.swift; sourceTree = "<group>"; };
D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsProfileTableViewCell.swift; sourceTree = "<group>"; }; D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsProfileTableViewCell.swift; sourceTree = "<group>"; };
D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultOverviewCoordinator.swift; sourceTree = "<group>"; }; D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultOverviewCoordinator.swift; sourceTree = "<group>"; };
D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = "<group>"; }; D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = "<group>"; };
@ -2557,6 +2559,7 @@
DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */, DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */,
DB852D1A26FAED0100FC9D81 /* Sidebar */, DB852D1A26FAED0100FC9D81 /* Sidebar */,
DB8AF54E25C13703002E6C99 /* MainTab */, DB8AF54E25C13703002E6C99 /* MainTab */,
D8CF45822B50893900C84D70 /* Tab.swift */,
); );
path = Root; path = Root;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3970,6 +3973,7 @@
2A3F6FE5292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift in Sources */, 2A3F6FE5292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift in Sources */,
C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */, C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */,
DB023D2A27A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift in Sources */, DB023D2A27A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift in Sources */,
D8CF45832B50893900C84D70 /* Tab.swift in Sources */,
D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */, D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */,
DB98EB6027B10E150082E365 /* ReportCommentTableViewCell.swift in Sources */, DB98EB6027B10E150082E365 /* ReportCommentTableViewCell.swift in Sources */,
DB0FCB962797E6C2006C02E2 /* SearchResultViewController+DataSourceProvider.swift in Sources */, DB0FCB962797E6C2006C02E2 /* SearchResultViewController+DataSourceProvider.swift in Sources */,

View File

@ -387,7 +387,7 @@ extension SceneCoordinator {
return viewController return viewController
} }
func switchToTabBar(tab: MainTabBarController.Tab) { func switchToTabBar(tab: Tab) {
splitViewController?.contentSplitViewController.currentSupplementaryTab = tab splitViewController?.contentSplitViewController.currentSupplementaryTab = tab
splitViewController?.compactMainTabBarViewController.selectedIndex = tab.rawValue splitViewController?.compactMainTabBarViewController.selectedIndex = tab.rawValue

View File

@ -11,8 +11,8 @@ import CoreDataStack
import MastodonCore import MastodonCore
protocol ContentSplitViewControllerDelegate: AnyObject { protocol ContentSplitViewControllerDelegate: AnyObject {
func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didSelectTab tab: Tab)
func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didDoubleTapTab tab: MainTabBarController.Tab) func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didDoubleTapTab tab: Tab)
} }
final class ContentSplitViewController: UIViewController, NeedsDependency { final class ContentSplitViewController: UIViewController, NeedsDependency {
@ -37,7 +37,7 @@ final class ContentSplitViewController: UIViewController, NeedsDependency {
return sidebarViewController return sidebarViewController
}() }()
@Published var currentSupplementaryTab: MainTabBarController.Tab = .home @Published var currentSupplementaryTab: Tab = .home
private(set) lazy var mainTabBarController: MainTabBarController = { private(set) lazy var mainTabBarController: MainTabBarController = {
let mainTabBarController = MainTabBarController(context: context, coordinator: coordinator, authContext: authContext) let mainTabBarController = MainTabBarController(context: context, coordinator: coordinator, authContext: authContext)
if let homeTimelineViewController = mainTabBarController.viewController(of: HomeTimelineViewController.self) { if let homeTimelineViewController = mainTabBarController.viewController(of: HomeTimelineViewController.self) {
@ -102,7 +102,7 @@ extension ContentSplitViewController {
// MARK: - SidebarViewControllerDelegate // MARK: - SidebarViewControllerDelegate
extension ContentSplitViewController: SidebarViewControllerDelegate { extension ContentSplitViewController: SidebarViewControllerDelegate {
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) { func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: Tab) {
delegate?.contentSplitViewController(self, sidebarViewController: sidebarViewController, didSelectTab: tab) delegate?.contentSplitViewController(self, sidebarViewController: sidebarViewController, didSelectTab: tab)
} }

View File

@ -34,102 +34,13 @@ class MainTabBarController: UITabBarController {
) )
@Published var currentTab: Tab = .home @Published var currentTab: Tab = .home
enum Tab: Int, CaseIterable {
case home
case search
case compose
case notifications
case me
var tag: Int { let homeTimelineViewController: HomeTimelineViewController
return rawValue let searchViewController: SearchViewController
} let composeViewController: UIViewController // placeholder
let notificationViewController: NotificationViewController
var title: String { let meProfileViewController: ProfileViewController
switch self {
case .home: return L10n.Common.Controls.Tabs.home
case .search: return L10n.Common.Controls.Tabs.searchAndExplore
case .compose: return L10n.Common.Controls.Actions.compose
case .notifications: return L10n.Common.Controls.Tabs.notifications
case .me: return L10n.Common.Controls.Tabs.profile
}
}
var inputLabels: [String]? {
switch self {
case .home, .compose, .notifications, .me:
return nil
case .search:
return [
L10n.Common.Controls.Tabs.A11Y.search,
L10n.Common.Controls.Tabs.A11Y.explore,
L10n.Common.Controls.Tabs.searchAndExplore
]
}
}
var image: UIImage {
switch self {
case .home: return UIImage(systemName: "house")!
case .search: return UIImage(systemName: "magnifyingglass")!
case .compose: return UIImage(systemName: "square.and.pencil")!
case .notifications: return UIImage(systemName: "bell")!
case .me: return UIImage(systemName: "person")!
}
}
var selectedImage: UIImage {
return image.withTintColor(Asset.Colors.Brand.blurple.color, renderingMode: .alwaysOriginal)
}
var largeImage: UIImage {
return image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80))
}
@MainActor
func viewController(context: AppContext, authContext: AuthContext?, coordinator: SceneCoordinator) -> UIViewController {
guard let authContext else { return UITableViewController() }
let viewController: UIViewController
switch self {
case .home:
let _viewController = HomeTimelineViewController()
_viewController.context = context
_viewController.coordinator = coordinator
_viewController.viewModel = HomeTimelineViewModel(context: context, authContext: authContext)
viewController = _viewController
case .search:
let _viewController = SearchViewController()
_viewController.context = context
_viewController.coordinator = coordinator
_viewController.viewModel = .init(context: context, authContext: authContext)
viewController = _viewController
case .compose:
viewController = UIViewController()
case .notifications:
let _viewController = NotificationViewController()
_viewController.context = context
_viewController.coordinator = coordinator
_viewController.viewModel = .init(context: context, authContext: authContext)
viewController = _viewController
case .me:
#warning("What happens if there's no me at the beginning? I guess we _do_ need another migration?")
guard let me = authContext.mastodonAuthenticationBox.authentication.account() else { return UIViewController() }
let _viewController = ProfileViewController()
_viewController.context = context
_viewController.coordinator = coordinator
_viewController.viewModel = ProfileViewModel(context: context, authContext: authContext, account: me, relationship: nil, me: me)
viewController = _viewController
}
viewController.title = self.title
return AdaptiveStatusBarStyleNavigationController(rootViewController: viewController)
}
}
var _viewControllers: [UIViewController] = []
private(set) var isReadyForWizardAvatarButton = false private(set) var isReadyForWizardAvatarButton = false
// output // output
@ -146,15 +57,43 @@ class MainTabBarController: UITabBarController {
self.context = context self.context = context
self.coordinator = coordinator self.coordinator = coordinator
self.authContext = authContext self.authContext = authContext
homeTimelineViewController = HomeTimelineViewController()
homeTimelineViewController.configureTabBarItem(with: .home)
homeTimelineViewController.context = context
homeTimelineViewController.coordinator = coordinator
searchViewController = SearchViewController()
searchViewController.configureTabBarItem(with: .search)
searchViewController.context = context
searchViewController.coordinator = coordinator
composeViewController = UIViewController()
composeViewController.configureTabBarItem(with: .compose)
notificationViewController = NotificationViewController()
notificationViewController.configureTabBarItem(with: .notifications)
notificationViewController.context = context
notificationViewController.coordinator = coordinator
meProfileViewController = ProfileViewController()
meProfileViewController.context = context
meProfileViewController.coordinator = coordinator
meProfileViewController.configureTabBarItem(with: .me)
if let authContext {
notificationViewController.viewModel = NotificationViewModel(context: context, authContext: authContext)
homeTimelineViewController.viewModel = HomeTimelineViewModel(context: context, authContext: authContext)
searchViewController.viewModel = SearchViewModel(context: context, authContext: authContext)
}
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
viewControllers = [homeTimelineViewController, searchViewController, composeViewController, notificationViewController, meProfileViewController].map { AdaptiveStatusBarStyleNavigationController(rootViewController: $0) }
tabBar.addInteraction(largeContentViewerInteraction) tabBar.addInteraction(largeContentViewerInteraction)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} }
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
} }
extension MainTabBarController { extension MainTabBarController {
@ -171,26 +110,9 @@ extension MainTabBarController {
view.backgroundColor = .systemBackground view.backgroundColor = .systemBackground
// seealso: `ThemeService.apply(theme:)` // seealso: `ThemeService.apply(theme:)`
let tabs = Tab.allCases
var viewControllers = [UIViewController]()
for tab in tabs {
let viewController = tab.viewController(context: context, authContext: authContext, coordinator: coordinator)
viewController.tabBarItem.tag = tab.tag
viewController.tabBarItem.title = tab.title // needs for acessiblity large content label
viewController.tabBarItem.image = tab.image.imageWithoutBaseline()
viewController.tabBarItem.largeContentSizeImage = tab.largeImage.imageWithoutBaseline()
viewController.tabBarItem.accessibilityLabel = tab.title
viewController.tabBarItem.accessibilityUserInputLabels = tab.inputLabels
viewController.tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
viewControllers.append(viewController)
}
_viewControllers = viewControllers
setViewControllers(viewControllers, animated: false) setViewControllers(viewControllers, animated: false)
selectedIndex = 0 selectedIndex = 0
// hacky workaround for FB11986255 (Setting accessibilityUserInputLabels on a UITabBarItem has no effect) // hacky workaround for FB11986255 (Setting accessibilityUserInputLabels on a UITabBarItem has no effect)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) {
if let searchItem = self.tabBar.subviews.first(where: { $0.accessibilityLabel == Tab.search.title }) { if let searchItem = self.tabBar.subviews.first(where: { $0.accessibilityLabel == Tab.search.title }) {
@ -201,7 +123,7 @@ extension MainTabBarController {
context.apiService.error context.apiService.error
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] error in .sink { [weak self] error in
guard let self = self, let coordinator = self.coordinator else { return } guard let self, let coordinator = self.coordinator else { return }
switch error { switch error {
case .implicit: case .implicit:
break break
@ -228,15 +150,14 @@ extension MainTabBarController {
) )
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] authentication, currentTab in .sink { [weak self] authentication, currentTab in
guard let self = self else { return } guard let self else { return }
guard let notificationViewController = self.notificationViewController else { return }
let authentication = self.authContext?.mastodonAuthenticationBox.userAuthorization let authentication = self.authContext?.mastodonAuthenticationBox.userAuthorization
let hasUnreadPushNotification: Bool = authentication.flatMap { authentication in let hasUnreadPushNotification: Bool = authentication.flatMap { authentication in
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authentication.accessToken) let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authentication.accessToken)
return count > 0 return count > 0
} ?? false } ?? false
let image: UIImage let image: UIImage
if hasUnreadPushNotification { if hasUnreadPushNotification {
let imageConfiguration = UIImage.SymbolConfiguration(paletteColors: [.red, SystemTheme.tabBarItemNormalIconColor]) let imageConfiguration = UIImage.SymbolConfiguration(paletteColors: [.red, SystemTheme.tabBarItemNormalIconColor])
@ -244,17 +165,18 @@ extension MainTabBarController {
} else { } else {
image = Tab.notifications.image image = Tab.notifications.image
} }
notificationViewController.tabBarItem.image = image.imageWithoutBaseline() notificationViewController.tabBarItem.image = image.imageWithoutBaseline()
notificationViewController.navigationController?.tabBarItem.image = image.imageWithoutBaseline() notificationViewController.navigationController?.tabBarItem.image = image.imageWithoutBaseline()
} }
.store(in: &disposeBag) .store(in: &disposeBag)
layoutAvatarButton() layoutAvatarButton()
$avatarURL $avatarURL
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] avatarURL in .sink { [weak self] avatarURL in
guard let self = self else { return } guard let self else { return }
self.avatarButton.avatarImageView.setImage( self.avatarButton.avatarImageView.setImage(
url: avatarURL, url: avatarURL,
placeholder: .placeholder(color: .systemFill), placeholder: .placeholder(color: .systemFill),
@ -262,7 +184,7 @@ extension MainTabBarController {
) )
} }
.store(in: &disposeBag) .store(in: &disposeBag)
NotificationCenter.default.publisher(for: .userFetched) NotificationCenter.default.publisher(for: .userFetched)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] _ in .sink { [weak self] _ in
@ -300,11 +222,11 @@ extension MainTabBarController {
$currentTab $currentTab
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] tab in .sink { [weak self] tab in
guard let self = self else { return } guard let self else { return }
self.updateAvatarButtonAppearance() self.updateAvatarButtonAppearance()
} }
.store(in: &disposeBag) .store(in: &disposeBag)
updateTabBarDisplay() updateTabBarDisplay()
} }
@ -358,7 +280,7 @@ extension MainTabBarController {
case .search: case .search:
assert(Thread.isMainThread) assert(Thread.isMainThread)
// double tapping search tab opens the search bar without additional taps // double tapping search tab opens the search bar without additional taps
searchViewController?.searchBar.becomeFirstResponder() searchViewController.searchBar.becomeFirstResponder()
default: default:
break break
} }
@ -460,18 +382,6 @@ extension MainTabBarController {
} }
} }
extension MainTabBarController {
var notificationViewController: NotificationViewController? {
return viewController(of: NotificationViewController.self)
}
var searchViewController: SearchViewController? {
return viewController(of: SearchViewController.self)
}
}
// MARK: - UITabBarControllerDelegate // MARK: - UITabBarControllerDelegate
extension MainTabBarController: UITabBarControllerDelegate { extension MainTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {

View File

@ -127,8 +127,8 @@ extension RootSplitViewController {
// MARK: - ContentSplitViewControllerDelegate // MARK: - ContentSplitViewControllerDelegate
extension RootSplitViewController: ContentSplitViewControllerDelegate { extension RootSplitViewController: ContentSplitViewControllerDelegate {
func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) { func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didSelectTab tab: Tab) {
guard let _ = MainTabBarController.Tab.allCases.firstIndex(of: tab) else { guard let _ = Tab.allCases.firstIndex(of: tab) else {
assertionFailure() assertionFailure()
return return
} }
@ -158,8 +158,8 @@ extension RootSplitViewController: ContentSplitViewControllerDelegate {
} }
} }
func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didDoubleTapTab tab: MainTabBarController.Tab) { func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didDoubleTapTab tab: Tab) {
guard let _ = MainTabBarController.Tab.allCases.firstIndex(of: tab) else { guard let _ = Tab.allCases.firstIndex(of: tab) else {
assertionFailure() assertionFailure()
return return
} }
@ -170,7 +170,7 @@ extension RootSplitViewController: ContentSplitViewControllerDelegate {
guard !isPrimaryDisplay else { guard !isPrimaryDisplay else {
return return
} }
contentSplitViewController.mainTabBarController.searchViewController?.searchBar.becomeFirstResponder() contentSplitViewController.mainTabBarController.searchViewController.searchBar.becomeFirstResponder()
default: default:
break break
} }

View File

@ -12,7 +12,7 @@ import MastodonCore
import MastodonUI import MastodonUI
protocol SidebarViewControllerDelegate: AnyObject { protocol SidebarViewControllerDelegate: AnyObject {
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: Tab)
func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView) func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView)
func sidebarViewController(_ sidebarViewController: SidebarViewController, didDoubleTapItem item: SidebarViewModel.Item, sourceView: UIView) func sidebarViewController(_ sidebarViewController: SidebarViewController, didDoubleTapItem item: SidebarViewModel.Item, sourceView: UIView)
} }

View File

@ -23,7 +23,7 @@ final class SidebarViewModel {
let authContext: AuthContext? let authContext: AuthContext?
@Published private var isSidebarDataSourceReady = false @Published private var isSidebarDataSourceReady = false
@Published private var isAvatarButtonDataReady = false @Published private var isAvatarButtonDataReady = false
@Published var currentTab: MainTabBarController.Tab = .home @Published var currentTab: Tab = .home
// output // output
var diffableDataSource: UICollectionViewDiffableDataSource<Section, Item>? var diffableDataSource: UICollectionViewDiffableDataSource<Section, Item>?
@ -57,7 +57,7 @@ extension SidebarViewModel {
} }
enum Item: Hashable { enum Item: Hashable {
case tab(MainTabBarController.Tab) case tab(Tab)
case setting case setting
case compose case compose
} }
@ -69,7 +69,7 @@ extension SidebarViewModel {
collectionView: UICollectionView, collectionView: UICollectionView,
secondaryCollectionView: UICollectionView secondaryCollectionView: UICollectionView
) { ) {
let tabCellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, MainTabBarController.Tab> { [weak self] cell, indexPath, item in let tabCellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, Tab> { [weak self] cell, indexPath, item in
guard let self else { return } guard let self else { return }
let imageURL: URL? let imageURL: URL?
@ -125,7 +125,7 @@ extension SidebarViewModel {
let imageConfiguration = UIImage.SymbolConfiguration(paletteColors: [.red, SystemTheme.tabBarItemNormalIconColor]) let imageConfiguration = UIImage.SymbolConfiguration(paletteColors: [.red, SystemTheme.tabBarItemNormalIconColor])
image = UIImage(systemName: "bell.badge", withConfiguration: imageConfiguration)! image = UIImage(systemName: "bell.badge", withConfiguration: imageConfiguration)!
} else { } else {
image = MainTabBarController.Tab.notifications.image image = Tab.notifications.image
} }
cell.item?.image = image cell.item?.image = image
cell.item?.activeImage = image.withTintColor(Asset.Colors.Brand.blurple.color, renderingMode: .alwaysOriginal) cell.item?.activeImage = image.withTintColor(Asset.Colors.Brand.blurple.color, renderingMode: .alwaysOriginal)

View File

@ -0,0 +1,111 @@
// Copyright © 2024 Mastodon gGmbH. All rights reserved.
import UIKit
import MastodonLocalization
import MastodonAsset
enum Tab: Int, CaseIterable {
case home
case search
case compose
case notifications
case me
var tag: Int {
return rawValue
}
var title: String {
switch self {
case .home: return L10n.Common.Controls.Tabs.home
case .search: return L10n.Common.Controls.Tabs.searchAndExplore
case .compose: return L10n.Common.Controls.Actions.compose
case .notifications: return L10n.Common.Controls.Tabs.notifications
case .me: return L10n.Common.Controls.Tabs.profile
}
}
var inputLabels: [String]? {
switch self {
case .home, .compose, .notifications, .me:
return nil
case .search:
return [
L10n.Common.Controls.Tabs.A11Y.search,
L10n.Common.Controls.Tabs.A11Y.explore,
L10n.Common.Controls.Tabs.searchAndExplore
]
}
}
var image: UIImage {
switch self {
case .home: return UIImage(systemName: "house")!
case .search: return UIImage(systemName: "magnifyingglass")!
case .compose: return UIImage(systemName: "square.and.pencil")!
case .notifications: return UIImage(systemName: "bell")!
case .me: return UIImage(systemName: "person")!
}
}
var selectedImage: UIImage {
return image.withTintColor(Asset.Colors.Brand.blurple.color, renderingMode: .alwaysOriginal)
}
var largeImage: UIImage {
return image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80))
}
// @MainActor
// func viewController(context: AppContext, authContext: AuthContext?, coordinator: SceneCoordinator) -> UIViewController {
// guard let authContext else { return UITableViewController() }
//
// let viewController: UIViewController
// switch self {
// case .home:
// let _viewController = HomeTimelineViewController()
// _viewController.context = context
// _viewController.coordinator = coordinator
// _viewController.viewModel = HomeTimelineViewModel(context: context, authContext: authContext)
// viewController = _viewController
// case .search:
// let _viewController = SearchViewController()
// _viewController.context = context
// _viewController.coordinator = coordinator
// _viewController.viewModel = SearchViewModel(context: context, authContext: authContext)
// viewController = _viewController
// case .compose:
// viewController = UIViewController()
// case .notifications:
// let _viewController = NotificationViewController()
// _viewController.context = context
// _viewController.coordinator = coordinator
// _viewController.viewModel = NotificationViewModel(context: context, authContext: authContext)
// viewController = _viewController
// case .me:
// #warning("What happens if there's no me at the beginning? I guess we _do_ need another migration?")
// guard let me = authContext.mastodonAuthenticationBox.authentication.account() else { return UIViewController() }
//
// let _viewController = ProfileViewController()
// _viewController.context = context
// _viewController.coordinator = coordinator
// _viewController.viewModel = ProfileViewModel(context: context, authContext: authContext, account: me, relationship: nil, me: me)
// viewController = _viewController
// }
// viewController.title = self.title
// return AdaptiveStatusBarStyleNavigationController(rootViewController: viewController)
// }
}
extension UIViewController {
func configureTabBarItem(with tab: Tab) {
title = tab.title
tabBarItem.tag = tab.tag
tabBarItem.title = tab.title // needs for acessiblity large content label
tabBarItem.image = tab.image.imageWithoutBaseline()
tabBarItem.largeContentSizeImage = tab.largeImage.imageWithoutBaseline()
tabBarItem.accessibilityLabel = tab.title
tabBarItem.accessibilityUserInputLabels = tab.inputLabels
tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}