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:
parent
cea6129229
commit
2c653320fb
@ -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 */,
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
111
Mastodon/Scene/Root/Tab.swift
Normal file
111
Mastodon/Scene/Root/Tab.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user