// // ProfilePagingViewController.swift // Mastodon // // Created by MainasuK Cirno on 2021-3-29. // import os.log import UIKit import Combine import XLPagerTabStrip import TabBarPager import MastodonAsset import MastodonUI protocol ProfilePagingViewControllerDelegate: AnyObject { func profilePagingViewController(_ viewController: ProfilePagingViewController, didScrollToPostCustomScrollViewContainerController customScrollViewContainerController: ScrollViewContainer, atIndex index: Int) } final class ProfilePagingViewController: ButtonBarPagerTabStripViewController, TabBarPageViewController { weak var tabBarPageViewDelegate: TabBarPageViewDelegate? weak var pagingDelegate: ProfilePagingViewControllerDelegate? var disposeBag = Set() var viewModel: ProfilePagingViewModel! let buttonBarShadowView = UIView() private var buttonBarShadowAlpha: CGFloat = 0.0 // MARK: - TabBarPageViewController var currentPage: TabBarPage? { return viewModel.viewControllers[currentIndex] } var currentPageIndex: Int? { currentIndex } // MARK: - ButtonBarPagerTabStripViewController override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] { return viewModel.viewControllers } override func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) { super.updateIndicator(for: viewController, fromIndex: fromIndex, toIndex: toIndex, withProgressPercentage: progressPercentage, indexWasChanged: indexWasChanged) guard indexWasChanged else { return } let page = viewModel.viewControllers[toIndex] tabBarPageViewDelegate?.pageViewController(self, didPresentingTabBarPage: page, at: toIndex) } // make key commands works override var canBecomeFirstResponder: Bool { return true } deinit { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) } } extension ProfilePagingViewController { override func viewDidLoad() { // configure style before viewDidLoad settings.style.buttonBarBackgroundColor = ThemeService.shared.currentTheme.value.systemBackgroundColor settings.style.buttonBarItemBackgroundColor = .clear settings.style.buttonBarItemsShouldFillAvailableWidth = false // alignment from leading to trailing settings.style.selectedBarHeight = 3 settings.style.selectedBarBackgroundColor = Asset.Colors.Label.primary.color settings.style.buttonBarItemFont = UIFont.systemFont(ofSize: 17, weight: .semibold) changeCurrentIndexProgressive = { [weak self] (oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, progressPercentage: CGFloat, changeCurrentIndex: Bool, animated: Bool) -> Void in guard let _ = self else { return } guard changeCurrentIndex == true else { return } oldCell?.label.textColor = Asset.Colors.Label.secondary.color newCell?.label.textColor = Asset.Colors.Label.primary.color } super.viewDidLoad() ThemeService.shared.currentTheme .receive(on: DispatchQueue.main) .sink { [weak self] theme in guard let self = self else { return } self.settings.style.buttonBarBackgroundColor = theme.systemBackgroundColor self.buttonBarView.backgroundColor = self.settings.style.buttonBarBackgroundColor self.barButtonLayout?.invalidateLayout() } .store(in: &disposeBag) updateBarButtonInsets() if let buttonBarView = self.buttonBarView { buttonBarShadowView.translatesAutoresizingMaskIntoConstraints = false view.insertSubview(buttonBarShadowView, belowSubview: buttonBarView) NSLayoutConstraint.activate([ buttonBarShadowView.topAnchor.constraint(equalTo: buttonBarView.topAnchor), buttonBarShadowView.leadingAnchor.constraint(equalTo: buttonBarView.leadingAnchor), buttonBarShadowView.trailingAnchor.constraint(equalTo: buttonBarView.trailingAnchor), buttonBarShadowView.bottomAnchor.constraint(equalTo: buttonBarView.bottomAnchor), ]) viewModel.$needsSetupBottomShadow .receive(on: DispatchQueue.main) .sink { [weak self] needsSetupBottomShadow in guard let self = self else { return } self.setupBottomShadow() } .store(in: &disposeBag) } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) becomeFirstResponder() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() setupBottomShadow() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) updateBarButtonInsets() } } extension ProfilePagingViewController { private func updateBarButtonInsets() { let margin: CGFloat = { switch traitCollection.userInterfaceIdiom { case .phone: return ProfileViewController.containerViewMarginForCompactHorizontalSizeClass default: return traitCollection.horizontalSizeClass == .regular ? ProfileViewController.containerViewMarginForRegularHorizontalSizeClass : ProfileViewController.containerViewMarginForCompactHorizontalSizeClass } }() settings.style.buttonBarLeftContentInset = margin settings.style.buttonBarRightContentInset = margin barButtonLayout?.sectionInset.left = margin barButtonLayout?.sectionInset.right = margin barButtonLayout?.invalidateLayout() } private var barButtonLayout: UICollectionViewFlowLayout? { let layout = buttonBarView.collectionViewLayout as? UICollectionViewFlowLayout return layout } func setupBottomShadow() { guard viewModel.needsSetupBottomShadow else { buttonBarShadowView.layer.shadowColor = nil buttonBarShadowView.layer.shadowRadius = 0 return } buttonBarShadowView.layer.setupShadow( color: UIColor.black.withAlphaComponent(0.12), alpha: Float(buttonBarShadowAlpha), x: 0, y: 2, blur: 2, spread: 0, roundedRect: buttonBarShadowView.bounds, byRoundingCorners: .allCorners, cornerRadii: .zero ) } func updateButtonBarShadow(progress: CGFloat) { let alpha = min(max(0, 10 * progress - 9), 1) if buttonBarShadowAlpha != alpha { buttonBarShadowAlpha = alpha setupBottomShadow() buttonBarShadowView.setNeedsLayout() } } } extension ProfilePagingViewController { var currentViewController: (UIViewController & TabBarPage)? { guard !viewModel.viewControllers.isEmpty, currentIndex < viewModel.viewControllers.count else { return nil } return viewModel.viewControllers[currentIndex] } } // workaround to fix tab man responder chain issue extension ProfilePagingViewController { override var keyCommands: [UIKeyCommand]? { return currentViewController?.keyCommands } @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { (currentViewController as? StatusTableViewControllerNavigateable)?.navigateKeyCommandHandlerRelay(sender) } @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { (currentViewController as? StatusTableViewControllerNavigateable)?.statusKeyCommandHandlerRelay(sender) } }