mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 00:01:00 +00:00
Notification navigation
This commit is contained in:
parent
980c5f0099
commit
f924062814
8 changed files with 153 additions and 47 deletions
|
@ -145,7 +145,7 @@
|
||||||
"main-navigation.notifications" = "Notifications";
|
"main-navigation.notifications" = "Notifications";
|
||||||
"main-navigation.conversations" = "Messages";
|
"main-navigation.conversations" = "Messages";
|
||||||
"metatext" = "Metatext";
|
"metatext" = "Metatext";
|
||||||
"notification.signed-in-as-%@" = "Signed in as %@";
|
"notification.signed-in-as-%@" = "Logged in as %@";
|
||||||
"notifications.all" = "All";
|
"notifications.all" = "All";
|
||||||
"notifications.mentions" = "Mentions";
|
"notifications.mentions" = "Mentions";
|
||||||
"ok" = "OK";
|
"ok" = "OK";
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import HTTP
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
public enum NotificationEndpoint {
|
||||||
|
case notification(id: MastodonNotification.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationEndpoint: Endpoint {
|
||||||
|
public typealias ResultType = MastodonNotification
|
||||||
|
|
||||||
|
public var context: [String] {
|
||||||
|
defaultContext + ["notifications"]
|
||||||
|
}
|
||||||
|
|
||||||
|
public var pathComponentsInContext: [String] {
|
||||||
|
switch self {
|
||||||
|
case let .notification(id):
|
||||||
|
return [id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var method: HTTPMethod {
|
||||||
|
switch self {
|
||||||
|
case .notification:
|
||||||
|
return .get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -247,6 +247,21 @@ public extension IdentityService {
|
||||||
mastodonAPIClient.request(StatusEndpoint.post(statusComponents)).map(\.id).eraseToAnyPublisher()
|
mastodonAPIClient.request(StatusEndpoint.post(statusComponents)).map(\.id).eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func notificationService(pushNotification: PushNotification) -> AnyPublisher<NotificationService, Error> {
|
||||||
|
mastodonAPIClient.request(NotificationEndpoint.notification(id: .init(pushNotification.notificationId)))
|
||||||
|
.flatMap { notification in
|
||||||
|
contentDatabase.insert(notifications: [notification])
|
||||||
|
.collect()
|
||||||
|
.map { _ in
|
||||||
|
NotificationService(
|
||||||
|
notification: notification,
|
||||||
|
mastodonAPIClient: mastodonAPIClient,
|
||||||
|
contentDatabase: contentDatabase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
func service(accountList: AccountsEndpoint, titleComponents: [String]? = nil) -> AccountListService {
|
func service(accountList: AccountsEndpoint, titleComponents: [String]? = nil) -> AccountListService {
|
||||||
AccountListService(
|
AccountListService(
|
||||||
endpoint: accountList,
|
endpoint: accountList,
|
||||||
|
|
|
@ -10,6 +10,7 @@ public enum Navigation {
|
||||||
case url(URL)
|
case url(URL)
|
||||||
case collection(CollectionService)
|
case collection(CollectionService)
|
||||||
case profile(ProfileService)
|
case profile(ProfileService)
|
||||||
|
case notification(NotificationService)
|
||||||
case searchScope(SearchScope)
|
case searchScope(SearchScope)
|
||||||
case webfingerStart
|
case webfingerStart
|
||||||
case webfingerEnd
|
case webfingerEnd
|
||||||
|
|
|
@ -33,7 +33,7 @@ final class MainNavigationViewController: UITabBarController {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
viewModel.$presentingSecondaryNavigation.sink { [weak self] in
|
viewModel.$presentingSecondaryNavigation.removeDuplicates().sink { [weak self] in
|
||||||
if $0 {
|
if $0 {
|
||||||
self?.presentSecondaryNavigation()
|
self?.presentSecondaryNavigation()
|
||||||
} else {
|
} else {
|
||||||
|
@ -182,27 +182,40 @@ private extension MainNavigationViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle(navigation: Navigation) {
|
func handle(navigation: Navigation) {
|
||||||
let vc: UIViewController
|
|
||||||
|
|
||||||
switch navigation {
|
switch navigation {
|
||||||
case let .collection(collectionService):
|
case let .collection(collectionService):
|
||||||
vc = TableViewController(
|
let vc = TableViewController(
|
||||||
viewModel: CollectionItemsViewModel(
|
viewModel: CollectionItemsViewModel(
|
||||||
collectionService: collectionService,
|
collectionService: collectionService,
|
||||||
identityContext: viewModel.identityContext),
|
identityContext: viewModel.identityContext),
|
||||||
rootViewModel: rootViewModel)
|
rootViewModel: rootViewModel)
|
||||||
|
|
||||||
|
selectedViewController?.show(vc, sender: self)
|
||||||
case let .profile(profileService):
|
case let .profile(profileService):
|
||||||
vc = ProfileViewController(
|
let vc = ProfileViewController(
|
||||||
viewModel: ProfileViewModel(
|
viewModel: ProfileViewModel(
|
||||||
profileService: profileService,
|
profileService: profileService,
|
||||||
identityContext: viewModel.identityContext),
|
identityContext: viewModel.identityContext),
|
||||||
rootViewModel: rootViewModel,
|
rootViewModel: rootViewModel,
|
||||||
identityContext: viewModel.identityContext,
|
identityContext: viewModel.identityContext,
|
||||||
parentNavigationController: nil)
|
parentNavigationController: nil)
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedViewController?.show(vc, sender: self)
|
selectedViewController?.show(vc, sender: self)
|
||||||
|
case .notification:
|
||||||
|
let index = NavigationViewModel.Tab.notifications.rawValue
|
||||||
|
|
||||||
|
guard let viewControllers = viewControllers,
|
||||||
|
viewControllers.count > index,
|
||||||
|
let notificationsNavigationController = viewControllers[index] as? UINavigationController,
|
||||||
|
let notificationsViewController =
|
||||||
|
notificationsNavigationController.viewControllers.first as? NotificationsViewController
|
||||||
|
else { break }
|
||||||
|
|
||||||
|
selectedIndex = index
|
||||||
|
notificationsNavigationController.popToRootViewController(animated: false)
|
||||||
|
notificationsViewController.handle(navigation: navigation)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,21 @@ final class NotificationsViewController: UIPageViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NotificationsViewController {
|
||||||
|
func handle(navigation: Navigation) {
|
||||||
|
switch navigation {
|
||||||
|
case .notification:
|
||||||
|
guard let firstViewController = notificationViewControllers.first else { return }
|
||||||
|
|
||||||
|
segmentedControl.selectedSegmentIndex = 0
|
||||||
|
setViewControllers([firstViewController], direction: .reverse, animated: false)
|
||||||
|
firstViewController.handle(navigation: navigation)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension NotificationsViewController: UIPageViewControllerDataSource {
|
extension NotificationsViewController: UIPageViewControllerDataSource {
|
||||||
func pageViewController(_ pageViewController: UIPageViewController,
|
func pageViewController(_ pageViewController: UIPageViewController,
|
||||||
viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||||
|
|
|
@ -195,6 +195,48 @@ extension TableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handle(navigation: Navigation) {
|
||||||
|
switch navigation {
|
||||||
|
case let .collection(collectionService):
|
||||||
|
let vc = TableViewController(
|
||||||
|
viewModel: CollectionItemsViewModel(
|
||||||
|
collectionService: collectionService,
|
||||||
|
identityContext: viewModel.identityContext),
|
||||||
|
rootViewModel: rootViewModel,
|
||||||
|
parentNavigationController: parentNavigationController)
|
||||||
|
|
||||||
|
if let parentNavigationController = parentNavigationController {
|
||||||
|
parentNavigationController.pushViewController(vc, animated: true)
|
||||||
|
} else {
|
||||||
|
show(vc, sender: self)
|
||||||
|
}
|
||||||
|
case let .profile(profileService):
|
||||||
|
let vc = ProfileViewController(
|
||||||
|
viewModel: ProfileViewModel(
|
||||||
|
profileService: profileService,
|
||||||
|
identityContext: viewModel.identityContext),
|
||||||
|
rootViewModel: rootViewModel,
|
||||||
|
identityContext: viewModel.identityContext,
|
||||||
|
parentNavigationController: parentNavigationController)
|
||||||
|
|
||||||
|
if let parentNavigationController = parentNavigationController {
|
||||||
|
parentNavigationController.pushViewController(vc, animated: true)
|
||||||
|
} else {
|
||||||
|
show(vc, sender: self)
|
||||||
|
}
|
||||||
|
case let .notification(notificationService):
|
||||||
|
navigate(toNotification: notificationService.notification)
|
||||||
|
case let .url(url):
|
||||||
|
present(SFSafariViewController(url: url), animated: true)
|
||||||
|
case .searchScope:
|
||||||
|
break
|
||||||
|
case .webfingerStart:
|
||||||
|
webfingerIndicatorView.startAnimating()
|
||||||
|
case .webfingerEnd:
|
||||||
|
webfingerIndicatorView.stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TableViewController: UITableViewDataSourcePrefetching {
|
extension TableViewController: UITableViewDataSourcePrefetching {
|
||||||
|
@ -374,44 +416,18 @@ private extension TableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle(navigation: Navigation) {
|
func navigate(toNotification: MastodonNotification) {
|
||||||
switch navigation {
|
guard let item = dataSource.snapshot().itemIdentifiers.first(where: {
|
||||||
case let .collection(collectionService):
|
guard case let .notification(notification, _) = $0 else { return false }
|
||||||
let vc = TableViewController(
|
|
||||||
viewModel: CollectionItemsViewModel(
|
|
||||||
collectionService: collectionService,
|
|
||||||
identityContext: viewModel.identityContext),
|
|
||||||
rootViewModel: rootViewModel,
|
|
||||||
parentNavigationController: parentNavigationController)
|
|
||||||
|
|
||||||
if let parentNavigationController = parentNavigationController {
|
return notification.id == toNotification.id
|
||||||
parentNavigationController.pushViewController(vc, animated: true)
|
}),
|
||||||
} else {
|
let indexPath = dataSource.indexPath(for: item)
|
||||||
show(vc, sender: self)
|
else { return }
|
||||||
}
|
|
||||||
case let .profile(profileService):
|
|
||||||
let vc = ProfileViewController(
|
|
||||||
viewModel: ProfileViewModel(
|
|
||||||
profileService: profileService,
|
|
||||||
identityContext: viewModel.identityContext),
|
|
||||||
rootViewModel: rootViewModel,
|
|
||||||
identityContext: viewModel.identityContext,
|
|
||||||
parentNavigationController: parentNavigationController)
|
|
||||||
|
|
||||||
if let parentNavigationController = parentNavigationController {
|
tableView.scrollToRow(at: indexPath, at: .none, animated: !UIAccessibility.isReduceMotionEnabled)
|
||||||
parentNavigationController.pushViewController(vc, animated: true)
|
|
||||||
} else {
|
viewModel.select(indexPath: indexPath)
|
||||||
show(vc, sender: self)
|
|
||||||
}
|
|
||||||
case let .url(url):
|
|
||||||
present(SFSafariViewController(url: url), animated: true)
|
|
||||||
case .searchScope:
|
|
||||||
break
|
|
||||||
case .webfingerStart:
|
|
||||||
webfingerIndicatorView.startAnimating()
|
|
||||||
case .webfingerEnd:
|
|
||||||
webfingerIndicatorView.stopAnimating()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func present(attachmentViewModel: AttachmentViewModel, statusViewModel: StatusViewModel) {
|
func present(attachmentViewModel: AttachmentViewModel, statusViewModel: StatusViewModel) {
|
||||||
|
|
|
@ -95,24 +95,39 @@ public extension NavigationViewModel {
|
||||||
|
|
||||||
func navigateToProfile(id: Account.Id) {
|
func navigateToProfile(id: Account.Id) {
|
||||||
presentingSecondaryNavigation = false
|
presentingSecondaryNavigation = false
|
||||||
|
presentedNewStatusViewModel = nil
|
||||||
navigationsSubject.send(.profile(identityContext.service.navigationService.profileService(id: id)))
|
navigationsSubject.send(.profile(identityContext.service.navigationService.profileService(id: id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func navigate(timeline: Timeline) {
|
func navigate(timeline: Timeline) {
|
||||||
presentingSecondaryNavigation = false
|
presentingSecondaryNavigation = false
|
||||||
|
presentedNewStatusViewModel = nil
|
||||||
navigationsSubject.send(
|
navigationsSubject.send(
|
||||||
.collection(identityContext.service.navigationService.timelineService(timeline: timeline)))
|
.collection(identityContext.service.navigationService.timelineService(timeline: timeline)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func navigateToFollowerRequests() {
|
func navigateToFollowerRequests() {
|
||||||
presentingSecondaryNavigation = false
|
presentingSecondaryNavigation = false
|
||||||
|
presentedNewStatusViewModel = nil
|
||||||
navigationsSubject.send(.collection(identityContext.service.service(
|
navigationsSubject.send(.collection(identityContext.service.service(
|
||||||
accountList: .followRequests,
|
accountList: .followRequests,
|
||||||
titleComponents: ["follow-requests"])))
|
titleComponents: ["follow-requests"])))
|
||||||
}
|
}
|
||||||
|
|
||||||
func navigate(pushNotification: PushNotification) {
|
func navigate(pushNotification: PushNotification) {
|
||||||
// TODO
|
switch pushNotification.notificationType {
|
||||||
|
case .followRequest:
|
||||||
|
navigateToFollowerRequests()
|
||||||
|
default:
|
||||||
|
identityContext.service.notificationService(pushNotification: pushNotification)
|
||||||
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
|
.sink { [weak self] in
|
||||||
|
self?.presentingSecondaryNavigation = false
|
||||||
|
self?.presentedNewStatusViewModel = nil
|
||||||
|
self?.navigationsSubject.send(.notification($0))
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewModel(timeline: Timeline) -> CollectionItemsViewModel {
|
func viewModel(timeline: Timeline) -> CollectionItemsViewModel {
|
||||||
|
|
Loading…
Reference in a new issue