mastodon-ios/Mastodon/Scene/Notification/NotificationViewController....

251 lines
8.5 KiB
Swift

//
// NotificationViewController.swift
// Mastodon
//
// Created by sxiaojian on 2021/4/12.
//
import os.log
import UIKit
import Combine
import MastodonAsset
import MastodonLocalization
import Tabman
import Pageboy
import MastodonCore
final class NotificationViewController: TabmanViewController, NeedsDependency {
let logger = Logger(subsystem: "NotificationViewController", category: "ViewController")
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
private(set) lazy var viewModel = NotificationViewModel(context: context)
let pageSegmentedControl = UISegmentedControl()
override func pageboyViewController(
_ pageboyViewController: PageboyViewController,
didScrollToPageAt index: TabmanViewController.PageIndex,
direction: PageboyViewController.NavigationDirection,
animated: Bool
) {
super.pageboyViewController(
pageboyViewController,
didScrollToPageAt: index,
direction: direction,
animated: animated
)
viewModel.currentPageIndex = index
}
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
}
extension NotificationViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor
ThemeService.shared.currentTheme
.receive(on: RunLoop.main)
.sink { [weak self] theme in
guard let self = self else { return }
self.view.backgroundColor = theme.secondarySystemBackgroundColor
}
.store(in: &disposeBag)
setupSegmentedControl(scopes: viewModel.scopes)
pageSegmentedControl.translatesAutoresizingMaskIntoConstraints = false
navigationItem.titleView = pageSegmentedControl
NSLayoutConstraint.activate([
pageSegmentedControl.widthAnchor.constraint(greaterThanOrEqualToConstant: 287)
])
pageSegmentedControl.addTarget(self, action: #selector(NotificationViewController.pageSegmentedControlValueChanged(_:)), for: .valueChanged)
dataSource = viewModel
viewModel.$viewControllers
.receive(on: DispatchQueue.main)
.sink { [weak self] viewControllers in
guard let self = self else { return }
self.reloadData()
self.bounces = viewControllers.count > 1
}
.store(in: &disposeBag)
viewModel.viewControllers = viewModel.scopes.map { scope in
createViewController(for: scope)
}
viewModel.$currentPageIndex
.receive(on: DispatchQueue.main)
.sink { [weak self] currentPageIndex in
guard let self = self else { return }
if self.pageSegmentedControl.selectedSegmentIndex != currentPageIndex {
self.pageSegmentedControl.selectedSegmentIndex = currentPageIndex
}
}
.store(in: &disposeBag)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// aspectViewWillAppear(animated)
// fetch latest notification when scroll position is within half screen height to prevent list reload
// if tableView.contentOffset.y < view.frame.height * 0.5 {
// viewModel.loadLatestStateMachine.enter(NotificationViewModel.LoadLatestState.Loading.self)
// }
// needs trigger manually after onboarding dismiss
setNeedsStatusBarAppearanceUpdate()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// reset notification count
context.notificationService.clearNotificationCountForActiveUser()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// reset notification count
context.notificationService.clearNotificationCountForActiveUser()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// aspectViewDidDisappear(animated)
}
}
extension NotificationViewController {
private func setupSegmentedControl(scopes: [NotificationTimelineViewModel.Scope]) {
pageSegmentedControl.removeAllSegments()
for (i, scope) in scopes.enumerated() {
pageSegmentedControl.insertSegment(withTitle: scope.title, at: i, animated: false)
}
// set initial selection
guard !pageSegmentedControl.isSelected else { return }
if viewModel.currentPageIndex < pageSegmentedControl.numberOfSegments {
pageSegmentedControl.selectedSegmentIndex = viewModel.currentPageIndex
} else {
pageSegmentedControl.selectedSegmentIndex = 0
}
}
private func createViewController(for scope: NotificationTimelineViewModel.Scope) -> UIViewController {
let viewController = NotificationTimelineViewController()
viewController.context = context
viewController.coordinator = coordinator
viewController.viewModel = NotificationTimelineViewModel(
context: context,
scope: scope
)
return viewController
}
}
extension NotificationViewController {
@objc private func pageSegmentedControlValueChanged(_ sender: UISegmentedControl) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
let index = sender.selectedSegmentIndex
scrollToPage(.at(index: index), animated: true, completion: nil)
}
}
// MARK: - ScrollViewContainer
extension NotificationViewController: ScrollViewContainer {
var scrollView: UIScrollView {
guard let viewController = currentViewController as? NotificationTimelineViewController else {
return UIScrollView()
}
return viewController.scrollView
}
}
extension NotificationViewController {
enum CategorySwitch: String, CaseIterable {
case everything
case mentions
var title: String {
switch self {
case .everything: return L10n.Scene.Notification.Keyobard.showEverything
case .mentions: return L10n.Scene.Notification.Keyobard.showMentions
}
}
// UIKeyCommand input
var input: String {
switch self {
case .everything: return "[" // + shift + command
case .mentions: return "]" // + shift + command
}
}
var modifierFlags: UIKeyModifierFlags {
switch self {
case .everything: return [.shift, .command]
case .mentions: return [.shift, .command]
}
}
var propertyList: Any {
return rawValue
}
}
var categorySwitchKeyCommands: [UIKeyCommand] {
CategorySwitch.allCases.map { category in
UIKeyCommand(
title: category.title,
image: nil,
action: #selector(NotificationViewController.showCategory(_:)),
input: category.input,
modifierFlags: category.modifierFlags,
propertyList: category.propertyList,
alternates: [],
discoverabilityTitle: nil,
attributes: [],
state: .off
)
}
}
@objc private func showCategory(_ sender: UIKeyCommand) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let rawValue = sender.propertyList as? String,
let category = CategorySwitch(rawValue: rawValue)
else { return }
switch category {
case .everything:
scrollToPage(.first, animated: true, completion: nil)
case .mentions:
scrollToPage(.last, animated: true, completion: nil)
}
}
override var keyCommands: [UIKeyCommand]? {
return categorySwitchKeyCommands
}
}