From 8bb6e9403d5d1f84b9ec7fdf510146648812aaad Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 14 Sep 2019 12:04:06 -0400 Subject: [PATCH] Add toggle to control whether Notifications tab shows all or just mentions Closes #45 --- Pachyderm/Client.swift | 6 +- Pachyderm/Model/Notification.swift | 2 +- Tusker.xcodeproj/project.pbxproj | 8 ++ .../Main/MainTabBarViewController.swift | 2 +- .../NotificationsPageViewController.swift | 40 ++++++++++ .../NotificationsTableViewController.swift | 18 ++--- .../SegmentedPageViewController.swift | 78 +++++++++++++++++++ 7 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 Tusker/Screens/Notifications/NotificationsPageViewController.swift create mode 100644 Tusker/Screens/Utilities/SegmentedPageViewController.swift diff --git a/Pachyderm/Client.swift b/Pachyderm/Client.swift index df4d6c7c..aa5f1c85 100644 --- a/Pachyderm/Client.swift +++ b/Pachyderm/Client.swift @@ -246,8 +246,10 @@ public class Client { } // MARK: - Notifications - public func getNotifications(range: RequestRange = .default) -> Request<[Notification]> { - var request = Request<[Notification]>(method: .get, path: "/api/v1/notifications") + public func getNotifications(excludeTypes: [Notification.Kind], range: RequestRange = .default) -> Request<[Notification]> { + var request = Request<[Notification]>(method: .get, path: "/api/v1/notifications", queryParameters: + "exclude_types" => excludeTypes.map { $0.rawValue } + ) request.range = range return request } diff --git a/Pachyderm/Model/Notification.swift b/Pachyderm/Model/Notification.swift index 6bcd99e2..89da70d5 100644 --- a/Pachyderm/Model/Notification.swift +++ b/Pachyderm/Model/Notification.swift @@ -31,7 +31,7 @@ public class Notification: Decodable { } extension Notification { - public enum Kind: String, Decodable { + public enum Kind: String, Decodable, CaseIterable { case mention case reblog case favourite diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index de797a67..f5a6e8b9 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -143,6 +143,7 @@ D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686329021ED8319008C716E /* GMGridViewController.m */; }; D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686329121ED8319008C716E /* GMImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686329321ED8319008C716E /* GMImagePicker.strings */; }; + D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; }; D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; }; D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; }; D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; }; @@ -165,6 +166,7 @@ D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; }; D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */; }; + D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */; }; D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; }; D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; }; @@ -389,6 +391,7 @@ D686329021ED8319008C716E /* GMGridViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewController.m; sourceTree = ""; }; D686329121ED8319008C716E /* GMImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMImagePickerController.h; sourceTree = ""; }; D686329421ED8319008C716E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = GMImagePicker.strings; sourceTree = ""; }; + D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = ""; }; D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSegment.swift; sourceTree = ""; }; D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGroup.swift; sourceTree = ""; }; D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = ""; }; @@ -410,6 +413,7 @@ D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = ""; }; D6BC874421961F73006163F1 /* Gifu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Gifu.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedTableViewController.swift; sourceTree = ""; }; + D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPageViewController.swift; sourceTree = ""; }; D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = ""; }; D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = ""; }; @@ -736,6 +740,7 @@ D641C786213DD852004B4513 /* Notifications */ = { isa = PBXGroup; children = ( + D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */, D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */, ); path = Notifications; @@ -1029,6 +1034,7 @@ D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */, D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */, D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */, + D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */, ); path = Utilities; sourceTree = ""; @@ -1491,10 +1497,12 @@ D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */, 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */, D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */, + D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */, D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */, D6D58DF922074B74009C8DD9 /* LinkLabel.swift in Sources */, 0454DDAF22B462EF00B8BB8E /* GalleryExpandAnimationController.swift in Sources */, D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */, + D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */, 0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */, D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */, D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */, diff --git a/Tusker/Screens/Main/MainTabBarViewController.swift b/Tusker/Screens/Main/MainTabBarViewController.swift index 0c02f864..dc36f2be 100644 --- a/Tusker/Screens/Main/MainTabBarViewController.swift +++ b/Tusker/Screens/Main/MainTabBarViewController.swift @@ -17,7 +17,7 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate { viewControllers = [ embedInNavigationController(TimelineTableViewController(for: .home)), - embedInNavigationController(NotificationsTableViewController()), + embedInNavigationController(NotificationsPageViewController()), ComposeViewController(), embedInNavigationController(TimelineTableViewController(for: .public(local: false))), embedInNavigationController(MyProfileTableViewController()), diff --git a/Tusker/Screens/Notifications/NotificationsPageViewController.swift b/Tusker/Screens/Notifications/NotificationsPageViewController.swift new file mode 100644 index 00000000..b78fa5c0 --- /dev/null +++ b/Tusker/Screens/Notifications/NotificationsPageViewController.swift @@ -0,0 +1,40 @@ +// +// NotificationsPageViewController.swift +// Tusker +// +// Created by Shadowfacts on 9/13/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm + +class NotificationsPageViewController: SegmentedPageViewController { + + private let notificationsTitle = NSLocalizedString("Notifications", comment: "notifications tab title") + private let mentionsTitle = NSLocalizedString("Mentions", comment: "mentions tab title") + + init() { + let notifications = NotificationsTableViewController(allowedTypes: Pachyderm.Notification.Kind.allCases) + notifications.title = notificationsTitle + + let mentions = NotificationsTableViewController(allowedTypes: [.mention]) + mentions.title = mentionsTitle + + super.init(titles: [ + notificationsTitle, + mentionsTitle + ], pageControllers: [ + notifications, + mentions + ]) + + title = notificationsTitle + tabBarItem.image = UIImage(systemName: "bell.fill") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift index 36cd5bb9..397c7ba1 100644 --- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift @@ -15,8 +15,9 @@ class NotificationsTableViewController: EnhancedTableViewController { private let actionGroupCell = "actionGroupCell" private let followGroupCell = "followGroupCell" + let excludedTypes: [Pachyderm.Notification.Kind] let groupTypes = [Notification.Kind.favourite, .reblog, .follow] - + var groups: [NotificationGroup] = [] { didSet { DispatchQueue.main.async { @@ -28,12 +29,11 @@ class NotificationsTableViewController: EnhancedTableViewController { var newer: RequestRange? var older: RequestRange? - init() { + init(allowedTypes: [Pachyderm.Notification.Kind]) { + self.excludedTypes = Array(Set(Pachyderm.Notification.Kind.allCases).subtracting(allowedTypes)) + super.init(style: .plain) - - title = "Notifications" - tabBarItem.image = UIImage(systemName: "bell.fill") - + self.refreshControl = UIRefreshControl() refreshControl!.addTarget(self, action: #selector(refreshNotifications(_:)), for: .valueChanged) } @@ -54,7 +54,7 @@ class NotificationsTableViewController: EnhancedTableViewController { tableView.prefetchDataSource = self - let request = MastodonController.client.getNotifications() + let request = MastodonController.client.getNotifications(excludeTypes: excludedTypes) MastodonController.client.run(request) { result in guard case let .success(notifications, pagination) = result else { fatalError() } @@ -116,7 +116,7 @@ class NotificationsTableViewController: EnhancedTableViewController { if indexPath.row == groups.count - 1 { guard let older = older else { return } - let request = MastodonController.client.getNotifications(range: older) + let request = MastodonController.client.getNotifications(excludeTypes: excludedTypes, range: older) MastodonController.client.run(request) { result in guard case let .success(newNotifications, pagination) = result else { fatalError() } @@ -148,7 +148,7 @@ class NotificationsTableViewController: EnhancedTableViewController { @objc func refreshNotifications(_ sender: Any) { guard let newer = newer else { return } - let request = MastodonController.client.getNotifications(range: newer) + let request = MastodonController.client.getNotifications(excludeTypes: excludedTypes, range: newer) MastodonController.client.run(request) { result in guard case let .success(newNotifications, pagination) = result else { fatalError() } diff --git a/Tusker/Screens/Utilities/SegmentedPageViewController.swift b/Tusker/Screens/Utilities/SegmentedPageViewController.swift new file mode 100644 index 00000000..c3235b47 --- /dev/null +++ b/Tusker/Screens/Utilities/SegmentedPageViewController.swift @@ -0,0 +1,78 @@ +// +// SegmentedPageViewController.swift +// Tusker +// +// Created by Shadowfacts on 9/13/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit + +class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate { + + let titles: [String] + let pageControllers: [UIViewController] + + private(set) var currentIndex: Int! + + var segmentedControl: UISegmentedControl! + + init(titles: [String], pageControllers: [UIViewController]) { + self.titles = titles + self.pageControllers = pageControllers + + super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) + + self.dataSource = self + self.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .systemBackground + + segmentedControl = UISegmentedControl(items: titles) + segmentedControl.addTarget(self, action: #selector(segmentedControlChanged), for: .valueChanged) + navigationItem.titleView = segmentedControl + + segmentedControl.selectedSegmentIndex = 0 + selectPage(at: 0, animated: false) + } + + func selectPage(at index: Int, animated: Bool) { + let direction: UIPageViewController.NavigationDirection = currentIndex == nil ? .forward : index - currentIndex > 0 ? .forward : .reverse + setViewControllers([pageControllers[index]], direction: direction, animated: animated) + navigationItem.title = pageControllers[index].title + currentIndex = index + } + + @objc func segmentedControlChanged() { + selectPage(at: segmentedControl.selectedSegmentIndex, animated: true) + } + + // MARK: - Page View Controller Data Source + func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { + guard let index = pageControllers.firstIndex(of: viewController), + index > 0 else { return nil } + return pageControllers[index - 1] + } + + func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { + guard let index = pageControllers.firstIndex(of: viewController), + index < pageControllers.count - 1 else { return nil } + return pageControllers[index + 1] + } + + // MARK: - Page View Controller Delegate + func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { + currentIndex = pageControllers.firstIndex(of: viewControllers!.first!)! + segmentedControl.selectedSegmentIndex = currentIndex + navigationItem.title = viewControllers!.first!.title + } + +}