mirror of
https://github.com/zhigang1992/GitHawk.git
synced 2026-06-12 00:14:44 +08:00
308 lines
10 KiB
Swift
308 lines
10 KiB
Swift
//
|
|
// NotificationsViewController.swift
|
|
// Freetime
|
|
//
|
|
// Created by Ryan Nystrom on 6/8/18.
|
|
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import IGListKit
|
|
import FlatCache
|
|
import Squawk
|
|
|
|
final class NotificationsViewController: BaseListViewController2<Int>,
|
|
BaseListViewController2DataSource,
|
|
ForegroundHandlerDelegate,
|
|
FlatCacheListener,
|
|
TabNavRootViewControllerType,
|
|
BaseListViewController2EmptyDataSource {
|
|
|
|
private let modelController: NotificationModelController
|
|
private let foreground = ForegroundHandler(threshold: 5 * 60)
|
|
private let inboxType: InboxType
|
|
private var notificationIDs = [String]()
|
|
|
|
private var notifications: [NotificationViewModel] {
|
|
return notificationIDs.compactMap { modelController.githubClient.cache.get(id: $0) }
|
|
}
|
|
|
|
init(modelController: NotificationModelController, inboxType: InboxType) {
|
|
self.modelController = modelController
|
|
self.inboxType = inboxType
|
|
|
|
super.init(emptyErrorMessage: NSLocalizedString("Cannot load your inbox.", comment: ""))
|
|
|
|
self.dataSource = self
|
|
self.emptyDataSource = self
|
|
self.foreground.delegate = self
|
|
|
|
switch inboxType {
|
|
case .all: title = NSLocalizedString("All", comment: "")
|
|
case .unread: title = NSLocalizedString("Inbox", comment: "")
|
|
case .repo(let repo): title = repo.name
|
|
}
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
makeBackBarItemEmpty()
|
|
resetRightBarItem()
|
|
|
|
switch inboxType {
|
|
case .unread:
|
|
let item = UIBarButtonItem(
|
|
image: UIImage(named: "bullets-hollow"),
|
|
style: .plain,
|
|
target: self,
|
|
action: #selector(NotificationsViewController.onMore(sender:))
|
|
)
|
|
item.accessibilityLabel = Constants.Strings.moreOptions
|
|
navigationItem.leftBarButtonItem = item
|
|
case .repo, .all: break
|
|
}
|
|
|
|
navigationController?.tabBarItem.badgeColor = Styles.Colors.Red.medium.color
|
|
}
|
|
|
|
override func fetch(page: Int?) {
|
|
let width = view.bounds.width
|
|
let showAll = inboxType.showAll
|
|
|
|
let repo: Repository?
|
|
switch inboxType {
|
|
case .repo(let r): repo = r
|
|
case .all, .unread: repo = nil
|
|
}
|
|
|
|
if let page = page {
|
|
modelController.fetchNotifications(repo: repo, all: showAll, page: page, width: width) { [weak self] result in
|
|
self?.handle(result: result, append: true, animated: false, page: page)
|
|
}
|
|
} else {
|
|
let first = 1
|
|
modelController.fetchNotifications(repo: repo, all: showAll, page: first, width: width) { [weak self] result in
|
|
self?.handle(result: result, append: false, animated: trueUnlessReduceMotionEnabled, page: first)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func handle(result: Result<([NotificationViewModel], Int?)>, append: Bool, animated: Bool, page: Int) {
|
|
switch result {
|
|
case .success(let notifications, let next):
|
|
var ids = [String]()
|
|
notifications.forEach {
|
|
modelController.githubClient.cache.add(listener: self, value: $0)
|
|
ids.append($0.id)
|
|
}
|
|
rebuildAndUpdate(ids: ids, append: append, page: next, animated: animated)
|
|
case .error:
|
|
error(animated: trueUnlessReduceMotionEnabled)
|
|
Squawk.showNetworkError()
|
|
}
|
|
|
|
// set after updating so self.models has already been changed
|
|
updateUnreadState()
|
|
}
|
|
|
|
private func updateUnreadState() {
|
|
// don't update tab bar and badges when not showing only new notifications
|
|
// prevents archives updating badge and tab #s
|
|
switch inboxType {
|
|
case .repo: return
|
|
case .all, .unread: break
|
|
}
|
|
|
|
var unread = 0
|
|
for id in notificationIDs {
|
|
guard let model = modelController.githubClient.cache.get(id: id) as NotificationViewModel?,
|
|
!model.read
|
|
else { continue }
|
|
unread += 1
|
|
}
|
|
|
|
let hasUnread = unread > 0
|
|
navigationItem.rightBarButtonItem?.isEnabled = hasUnread
|
|
navigationController?.tabBarItem.badgeValue = hasUnread ? "\(unread)" : nil
|
|
BadgeNotifications.update(count: unread)
|
|
}
|
|
|
|
@objc func onMore(sender: UIBarButtonItem) {
|
|
let alert = UIAlertController.configured(preferredStyle: .actionSheet)
|
|
|
|
alert.add(action: UIAlertAction(
|
|
title: NSLocalizedString("View All", comment: ""),
|
|
style: .default,
|
|
handler: { [weak self] _ in
|
|
self?.onViewAll()
|
|
}))
|
|
|
|
let cache = modelController.githubClient.cache
|
|
var repoNames = Set<String>()
|
|
for id in notificationIDs {
|
|
guard let model = cache.get(id: id) as NotificationViewModel?,
|
|
!repoNames.contains(model.repo)
|
|
else { continue }
|
|
repoNames.insert(model.repo)
|
|
alert.add(action: UIAlertAction(title: model.repo, style: .default, handler: { [weak self] _ in
|
|
self?.pushRepoNotifications(owner: model.owner, repo: model.repo)
|
|
}))
|
|
}
|
|
|
|
alert.add(action: AlertAction.cancel())
|
|
alert.popoverPresentationController?.barButtonItem = sender
|
|
present(alert, animated: trueUnlessReduceMotionEnabled)
|
|
}
|
|
|
|
func pushRepoNotifications(owner: String, repo: String) {
|
|
let controller = NotificationsViewController(
|
|
modelController: modelController,
|
|
inboxType: .repo(Repository(owner: owner, name: repo))
|
|
)
|
|
navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
|
|
}
|
|
|
|
func onViewAll() {
|
|
let controller = NotificationsViewController(
|
|
modelController: modelController,
|
|
inboxType: .all
|
|
)
|
|
navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
|
|
}
|
|
|
|
func resetRightBarItem(updatingState updateState: Bool = true) {
|
|
let item = UIBarButtonItem(
|
|
image: UIImage(named: "check"),
|
|
style: .plain,
|
|
target: self,
|
|
action: #selector(onMarkAll)
|
|
)
|
|
item.accessibilityLabel = NSLocalizedString("Mark notifications read", comment: "")
|
|
navigationItem.rightBarButtonItem = item
|
|
if updateState {
|
|
updateUnreadState()
|
|
}
|
|
}
|
|
|
|
@objc private func onMarkAll() {
|
|
let message: String
|
|
switch inboxType {
|
|
case .all, .unread:
|
|
message = NSLocalizedString("Mark all notifications as read?", comment: "")
|
|
case .repo(let repo):
|
|
let messageFormat = NSLocalizedString("Mark %@ notifications as read?", comment: "")
|
|
message = String(format: messageFormat, repo.name)
|
|
}
|
|
|
|
let alert = UIAlertController.configured(
|
|
title: NSLocalizedString("Notifications", comment: ""),
|
|
message: message,
|
|
preferredStyle: .alert
|
|
)
|
|
|
|
alert.addActions([
|
|
UIAlertAction(
|
|
title: Constants.Strings.markRead,
|
|
style: .destructive,
|
|
handler: { [weak self] _ in
|
|
self?.markRead()
|
|
}),
|
|
AlertAction.cancel()
|
|
])
|
|
|
|
present(alert, animated: trueUnlessReduceMotionEnabled)
|
|
}
|
|
|
|
private func markRead() {
|
|
self.setRightBarItemSpinning()
|
|
|
|
let block: (Bool) -> Void = { success in
|
|
let generator = UINotificationFeedbackGenerator()
|
|
if success {
|
|
generator.notificationOccurred(.success)
|
|
|
|
// clear all badges
|
|
BadgeNotifications.update(count: 0)
|
|
|
|
// change the spinner to the mark all item
|
|
// don't update state here; it is managed by `fetch`
|
|
self.resetRightBarItem(updatingState: false)
|
|
} else {
|
|
generator.notificationOccurred(.error)
|
|
}
|
|
self.fetch(page: nil)
|
|
|
|
// "mark all" is an engaging action, system prompt on it
|
|
RatingController.prompt(.system)
|
|
}
|
|
|
|
switch inboxType {
|
|
case .all, .unread: modelController.markAllNotifications(completion: block)
|
|
case .repo(let repo): modelController.markRepoNotifications(repo: repo, completion: block)
|
|
}
|
|
}
|
|
|
|
private func rebuildAndUpdate(
|
|
ids: [String],
|
|
append: Bool,
|
|
page: Int?,
|
|
animated: Bool
|
|
) {
|
|
if append {
|
|
notificationIDs += ids
|
|
} else {
|
|
notificationIDs = ids
|
|
}
|
|
update(page: page, animated: animated)
|
|
}
|
|
|
|
// MARK: BaseListViewController2DataSource
|
|
|
|
func models(adapter: ListSwiftAdapter) -> [ListSwiftPair] {
|
|
return notificationIDs.compactMap { id in
|
|
guard let model = modelController.githubClient.cache.get(id: id) as NotificationViewModel?
|
|
else { return nil }
|
|
return ListSwiftPair.pair(model) { [modelController] in
|
|
NotificationSectionController(modelController: modelController)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: BaseListViewController2EmptyDataSource
|
|
|
|
func emptyModel(for adapter: ListSwiftAdapter) -> ListSwiftPair {
|
|
let layoutInsets = view.safeAreaInsets
|
|
return ListSwiftPair.pair("empty-notification-value", {
|
|
return NoNewNotificationSectionController(layoutInsets: layoutInsets)
|
|
})
|
|
}
|
|
|
|
// MARK: ForegroundHandlerDelegate
|
|
|
|
func didForeground(handler: ForegroundHandler) {
|
|
feed.refreshHead()
|
|
}
|
|
|
|
// MARK: FlatCacheListener
|
|
|
|
func flatCacheDidUpdate(cache: FlatCache, update: FlatCache.Update) {
|
|
self.update(animated: trueUnlessReduceMotionEnabled)
|
|
updateUnreadState()
|
|
}
|
|
|
|
// MARK: TabNavRootViewControllerType
|
|
|
|
func didSingleTapTab() {
|
|
feed.collectionView.scrollToTop(animated: true)
|
|
}
|
|
|
|
func didDoubleTapTab() {
|
|
didSingleTapTab()
|
|
}
|
|
}
|