// // TimelinesPageViewController.swift // Tusker // // Created by Shadowfacts on 9/14/19. // Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit import SwiftUI import Pachyderm import Combine class TimelinesPageViewController: SegmentedPageViewController { static let jumpToPresentTitle: NSAttributedString = { let s = NSMutableAttributedString("Jump to Present") // otherwise it pronounces it as 'pɹizˈənt' // its IPA is also bad, this should be an alveolar approximant not a trill s.addAttribute(.accessibilitySpeechIPANotation, value: "ˈprɛ.zənt", range: NSRange(location: "Jump to ".count, length: "Present".count)) return s }() private let homeTitle = NSLocalizedString("Home", comment: "home timeline tab title") private let federatedTitle = NSLocalizedString("Federated", comment: "federated timeline tab title") private let localTitle = NSLocalizedString("Local", comment: "local timeline tab title") weak var mastodonController: MastodonController! private let jumpButton = TimelineJumpButton() private var cancellables = Set() init(mastodonController: MastodonController) { self.mastodonController = mastodonController let pages = mastodonController.accountPreferences.pinnedTimelines.map { Page(mastodonController: mastodonController, timeline: $0) } super.init(pages: pages) { page in let vc: TimelineViewController if case .instance(let url) = page.timeline { vc = InstanceTimelineViewController(for: url, parentMastodonController: mastodonController) } else { vc = TimelineViewController(for: page.timeline.timeline!, mastodonController: mastodonController) } vc.title = page.segmentedControlTitle vc.persistsState = true return vc } title = homeTitle tabBarItem.image = UIImage(systemName: "house.fill") let customizeItem = UIBarButtonItem(image: UIImage(systemName: "slider.horizontal.3"), style: .plain, target: self, action: #selector(customizePressed)) customizeItem.accessibilityLabel = "Customize Timelines" navigationItem.rightBarButtonItem = customizeItem jumpButton.action = { [unowned self] mode in switch mode { case .jump: await (self.currentViewController as! TimelineViewController).checkPresent(jumpImmediately: true, animateImmediateJump: true) case .sync: _ = await (self.currentViewController as! TimelineViewController).syncPositionIfNecessary(alwaysPrompt: false) } } let jumpItem = UIBarButtonItem(customView: jumpButton) jumpItem.accessibilityAttributedLabel = Self.jumpToPresentTitle navigationItem.leftBarButtonItem = jumpItem mastodonController.accountPreferences.publisher(for: \.pinnedTimelinesData) .map { _ in () } .merge(with: NotificationCenter.default.publisher(for: .accountPreferencesChangedRemotely).map { _ in () }) .sink { _ in let pages = self.mastodonController.accountPreferences.pinnedTimelines.map { Page(mastodonController: self.mastodonController, timeline: $0) } self.setPages(pages, animated: false) } .store(in: &cancellables) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func configureViewController(_ viewController: UIViewController) { let vc = viewController as! TimelineViewController vc.delegate = self } override func selectPage(_ page: Page, animated: Bool) { super.selectPage(page, animated: animated) if jumpButton.offscreen { jumpButton.setMode(.jump, animated: false) UIView.animate(withDuration: 0.2, delay: 0) { self.jumpButton.offscreen = false } } else { jumpButton.setMode(.jump, animated: true) } } func selectTimeline(_ timeline: PinnedTimeline, animated: Bool) { self.selectPage(Page(mastodonController: mastodonController, timeline: timeline), animated: animated) } @objc private func customizePressed() { present(UIHostingController(rootView: CustomizeTimelinesView(mastodonController: mastodonController)), animated: true) } } extension TimelinesPageViewController { struct Page: SegmentedPageViewControllerPage { let mastodonController: MastodonController let timeline: PinnedTimeline static func ==(lhs: Page, rhs: Page) -> Bool { return lhs.timeline == rhs.timeline } func hash(into hasher: inout Hasher) { hasher.combine(timeline) } var segmentedControlTitle: String { if case let .list(id) = timeline, let list = try? mastodonController.persistentContainer.viewContext.fetch(ListMO.fetchRequest(id: id)).first { return list.title } else { return timeline.title } } } } extension TimelinesPageViewController: TimelineViewControllerDelegate { func timelineViewController(_ timelineViewController: TimelineViewController, willShowJumpToPresentToastWith animator: UIViewPropertyAnimator?) { guard timelineViewController === currentViewController else { return } if let animator { animator.addAnimations { self.jumpButton.offscreen = true } } else { self.jumpButton.offscreen = true } } func timelineViewController(_ timelineViewController: TimelineViewController, willDismissJumpToPresentToastWith animator: UIViewPropertyAnimator?) { guard timelineViewController === currentViewController else { return } jumpButton.setMode(.jump, animated: false) if let animator { animator.addAnimations { self.jumpButton.offscreen = false } } else { self.jumpButton.offscreen = false } } func timelineViewController(_ timelineViewController: TimelineViewController, willShowSyncToastWith animator: UIViewPropertyAnimator?) { guard timelineViewController === currentViewController else { return } if let animator { animator.addAnimations { self.jumpButton.offscreen = true } } else { self.jumpButton.offscreen = true } } func timelineViewController(_ timelineViewController: TimelineViewController, willDismissSyncToastWith animator: UIViewPropertyAnimator?) { guard timelineViewController === currentViewController else { return } jumpButton.setMode(.sync, animated: false) func resetJumpButtonMode() { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) { [weak self] in guard let self, timelineViewController === self.currentViewController, !self.jumpButton.isSyncing else { return } self.jumpButton.setMode(.jump, animated: true) } } if let animator { animator.addAnimations { self.jumpButton.offscreen = false } animator.addCompletion { position in if position == .end { resetJumpButtonMode() } } } else { self.jumpButton.offscreen = false resetJumpButtonMode() } } } extension TimelinesPageViewController: StateRestorableViewController { func stateRestorationActivity() -> NSUserActivity? { return (currentViewController as? TimelineViewController)?.stateRestorationActivity() } }