mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 00:01:00 +00:00
Mentions tab
This commit is contained in:
parent
32a019f8d7
commit
195f2d6a29
11 changed files with 156 additions and 31 deletions
|
@ -575,10 +575,12 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func notificationsPublisher() -> AnyPublisher<[CollectionSection], Error> {
|
||||
func notificationsPublisher(
|
||||
excludeTypes: Set<MastodonNotification.NotificationType>) -> AnyPublisher<[CollectionSection], Error> {
|
||||
ValueObservation.tracking(
|
||||
NotificationInfo.request(
|
||||
NotificationRecord.order(NotificationRecord.Columns.id.desc)).fetchAll)
|
||||
NotificationRecord.order(NotificationRecord.Columns.id.desc)
|
||||
.filter(!excludeTypes.map(\.rawValue).contains(NotificationRecord.Columns.type))).fetchAll)
|
||||
.removeDuplicates()
|
||||
.publisher(in: databaseWriter)
|
||||
.map { [.init(items: $0.map {
|
||||
|
|
|
@ -92,6 +92,8 @@
|
|||
"main-navigation.explore" = "Explore";
|
||||
"main-navigation.notifications" = "Notifications";
|
||||
"main-navigation.conversations" = "Messages";
|
||||
"notifications.all" = "All";
|
||||
"notifications.mentions" = "Mentions";
|
||||
"ok" = "OK";
|
||||
"pending.pending-confirmation" = "Your account is pending confirmation";
|
||||
"post" = "Post";
|
||||
|
|
|
@ -26,6 +26,7 @@ public extension MastodonNotification {
|
|||
case favourite
|
||||
case poll
|
||||
case followRequest = "follow_request"
|
||||
case status
|
||||
case unknown
|
||||
|
||||
public static var unknownCase: Self { .unknown }
|
||||
|
|
|
@ -5,7 +5,7 @@ import HTTP
|
|||
import Mastodon
|
||||
|
||||
public enum NotificationsEndpoint {
|
||||
case notifications
|
||||
case notifications(excludeTypes: Set<MastodonNotification.NotificationType>)
|
||||
}
|
||||
|
||||
extension NotificationsEndpoint: Endpoint {
|
||||
|
@ -15,6 +15,13 @@ extension NotificationsEndpoint: Endpoint {
|
|||
["notifications"]
|
||||
}
|
||||
|
||||
public var queryParameters: [URLQueryItem] {
|
||||
switch self {
|
||||
case let .notifications(excludeTypes):
|
||||
return Array(excludeTypes).map { URLQueryItem(name: "exclude_types[]", value: $0.rawValue) }
|
||||
}
|
||||
}
|
||||
|
||||
public var method: HTTPMethod {
|
||||
switch self {
|
||||
case .notifications:
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52ED257D757100FA2C5F /* CompositionView.swift */; };
|
||||
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA59472522B8B600804347 /* ViewConstants.swift */; };
|
||||
D097F41B25BE3E1A00859F2C /* SearchScope+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D097F41A25BE3E1A00859F2C /* SearchScope+Extensions.swift */; };
|
||||
D097F4C125BFA04C00859F2C /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D097F4C025BFA04C00859F2C /* NotificationsViewController.swift */; };
|
||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */; };
|
||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
||||
|
@ -270,6 +271,7 @@
|
|||
D08E52D1257C811200FA2C5F /* ShareExtensionError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShareExtensionError+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D08E52ED257D757100FA2C5F /* CompositionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionView.swift; sourceTree = "<group>"; };
|
||||
D097F41A25BE3E1A00859F2C /* SearchScope+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchScope+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D097F4C025BFA04C00859F2C /* NotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = "<group>"; };
|
||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
|
||||
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
||||
|
@ -570,6 +572,7 @@
|
|||
D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */,
|
||||
D035F86825B7F2ED00DC75ED /* MainNavigationViewController.swift */,
|
||||
D0FCC104259C4E61000B67DF /* NewStatusViewController.swift */,
|
||||
D097F4C025BFA04C00859F2C /* NotificationsViewController.swift */,
|
||||
D06BC5E525202AD90079541D /* ProfileViewController.swift */,
|
||||
D0F0B12D251A97E400942152 /* TableViewController.swift */,
|
||||
D035F87C25B7F61600DC75ED /* TimelinesViewController.swift */,
|
||||
|
@ -940,6 +943,7 @@
|
|||
D0D2AC6725BD0484003D5DF2 /* LineChartView.swift in Sources */,
|
||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */,
|
||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||
D097F4C125BFA04C00859F2C /* NotificationsViewController.swift in Sources */,
|
||||
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
||||
D035F88725B8016000DC75ED /* NavigationViewModel+Extensions.swift in Sources */,
|
||||
|
|
|
@ -253,8 +253,10 @@ public extension IdentityService {
|
|||
ExploreService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func notificationsService() -> NotificationsService {
|
||||
NotificationsService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
func notificationsService(excludeTypes: Set<MastodonNotification.NotificationType>) -> NotificationsService {
|
||||
NotificationsService(excludeTypes: excludeTypes,
|
||||
mastodonAPIClient: mastodonAPIClient,
|
||||
contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func conversationsService() -> ConversationsService {
|
||||
|
|
|
@ -11,18 +11,22 @@ public struct NotificationsService {
|
|||
public let nextPageMaxId: AnyPublisher<String, Never>
|
||||
public let navigationService: NavigationService
|
||||
|
||||
private let excludeTypes: Set<MastodonNotification.NotificationType>
|
||||
private let mastodonAPIClient: MastodonAPIClient
|
||||
private let contentDatabase: ContentDatabase
|
||||
private let nextPageMaxIdSubject: CurrentValueSubject<String, Never>
|
||||
|
||||
init(mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||
init(excludeTypes: Set<MastodonNotification.NotificationType>,
|
||||
mastodonAPIClient: MastodonAPIClient,
|
||||
contentDatabase: ContentDatabase) {
|
||||
self.excludeTypes = excludeTypes
|
||||
self.mastodonAPIClient = mastodonAPIClient
|
||||
self.contentDatabase = contentDatabase
|
||||
|
||||
let nextPageMaxIdSubject = CurrentValueSubject<String, Never>(String(Int.max))
|
||||
|
||||
self.nextPageMaxIdSubject = nextPageMaxIdSubject
|
||||
sections = contentDatabase.notificationsPublisher()
|
||||
sections = contentDatabase.notificationsPublisher(excludeTypes: excludeTypes)
|
||||
.handleEvents(receiveOutput: {
|
||||
guard case let .notification(notification, _) = $0.last?.items.last,
|
||||
notification.id < nextPageMaxIdSubject.value
|
||||
|
@ -37,10 +41,12 @@ public struct NotificationsService {
|
|||
}
|
||||
|
||||
extension NotificationsService: CollectionService {
|
||||
public var markerTimeline: Marker.Timeline? { .notifications }
|
||||
public var markerTimeline: Marker.Timeline? { excludeTypes.isEmpty ? .notifications : nil }
|
||||
|
||||
public func request(maxId: String?, minId: String?, search: Search?) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.pagedRequest(NotificationsEndpoint.notifications, maxId: maxId, minId: minId)
|
||||
mastodonAPIClient.pagedRequest(NotificationsEndpoint.notifications(excludeTypes: excludeTypes),
|
||||
maxId: maxId,
|
||||
minId: minId)
|
||||
.handleEvents(receiveOutput: {
|
||||
guard let maxId = $0.info.maxId, maxId < nextPageMaxIdSubject.value else { return }
|
||||
|
||||
|
|
|
@ -63,14 +63,8 @@ private extension MainNavigationViewController {
|
|||
rootViewModel: rootViewModel)
|
||||
]
|
||||
|
||||
if let notificationsViewModel = viewModel.notificationsViewModel {
|
||||
let notificationsViewController = TableViewController(
|
||||
viewModel: notificationsViewModel,
|
||||
rootViewModel: rootViewModel)
|
||||
|
||||
notificationsViewController.tabBarItem = NavigationViewModel.Tab.notifications.tabBarItem
|
||||
|
||||
controllers.append(notificationsViewController)
|
||||
if viewModel.identityContext.identity.authenticated {
|
||||
controllers.append(NotificationsViewController(viewModel: viewModel, rootViewModel: rootViewModel))
|
||||
}
|
||||
|
||||
if let conversationsViewModel = viewModel.conversationsViewModel {
|
||||
|
|
109
View Controllers/NotificationsViewController.swift
Normal file
109
View Controllers/NotificationsViewController.swift
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import Mastodon
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class NotificationsViewController: UIPageViewController {
|
||||
private let segmentedControl = UISegmentedControl(items: [
|
||||
NSLocalizedString("notifications.all", comment: ""),
|
||||
NSLocalizedString("notifications.mentions", comment: "")
|
||||
])
|
||||
private let notificationViewControllers: [TableViewController]
|
||||
private let viewModel: NavigationViewModel
|
||||
private let rootViewModel: RootViewModel
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(viewModel: NavigationViewModel, rootViewModel: RootViewModel) {
|
||||
self.viewModel = viewModel
|
||||
self.rootViewModel = rootViewModel
|
||||
|
||||
var excludingAllExceptMentions = Set(MastodonNotification.NotificationType.allCasesExceptUnknown)
|
||||
|
||||
excludingAllExceptMentions.remove(.mention)
|
||||
|
||||
notificationViewControllers = [
|
||||
TableViewController(viewModel: viewModel.notificationsViewModel(excludeTypes: []),
|
||||
rootViewModel: rootViewModel),
|
||||
TableViewController(viewModel: viewModel.notificationsViewModel(excludeTypes: excludingAllExceptMentions),
|
||||
rootViewModel: rootViewModel)
|
||||
]
|
||||
|
||||
super.init(transitionStyle: .scroll,
|
||||
navigationOrientation: .horizontal,
|
||||
options: [.interPageSpacing: CGFloat.defaultSpacing])
|
||||
|
||||
if let firstViewController = notificationViewControllers.first {
|
||||
setViewControllers([firstViewController], direction: .forward, animated: false)
|
||||
}
|
||||
|
||||
tabBarItem = NavigationViewModel.Tab.notifications.tabBarItem
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
dataSource = self
|
||||
delegate = self
|
||||
|
||||
navigationItem.titleView = segmentedControl
|
||||
segmentedControl.selectedSegmentIndex = 0
|
||||
segmentedControl.addAction(
|
||||
UIAction { [weak self] _ in
|
||||
guard let self = self,
|
||||
let currentViewController = self.viewControllers?.first as? TableViewController,
|
||||
let currentIndex = self.notificationViewControllers.firstIndex(of: currentViewController),
|
||||
self.segmentedControl.selectedSegmentIndex != currentIndex
|
||||
else { return }
|
||||
|
||||
self.setViewControllers(
|
||||
[self.notificationViewControllers[self.segmentedControl.selectedSegmentIndex]],
|
||||
direction: self.segmentedControl.selectedSegmentIndex > currentIndex ? .forward : .reverse,
|
||||
animated: !UIAccessibility.isReduceMotionEnabled)
|
||||
},
|
||||
for: .valueChanged)
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationsViewController: UIPageViewControllerDataSource {
|
||||
func pageViewController(_ pageViewController: UIPageViewController,
|
||||
viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||
guard
|
||||
let viewController = viewController as? TableViewController,
|
||||
let index = notificationViewControllers.firstIndex(of: viewController),
|
||||
index + 1 < notificationViewControllers.count
|
||||
else { return nil }
|
||||
|
||||
return notificationViewControllers[index + 1]
|
||||
}
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController,
|
||||
viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||
guard
|
||||
let viewController = viewController as? TableViewController,
|
||||
let index = notificationViewControllers.firstIndex(of: viewController),
|
||||
index > 0
|
||||
else { return nil }
|
||||
|
||||
return notificationViewControllers[index - 1]
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationsViewController: UIPageViewControllerDelegate {
|
||||
func pageViewController(_ pageViewController: UIPageViewController,
|
||||
didFinishAnimating finished: Bool,
|
||||
previousViewControllers: [UIViewController],
|
||||
transitionCompleted completed: Bool) {
|
||||
guard let viewController = viewControllers?.first as? TableViewController,
|
||||
let index = notificationViewControllers.firstIndex(of: viewController)
|
||||
else { return }
|
||||
|
||||
segmentedControl.selectedSegmentIndex = index
|
||||
}
|
||||
}
|
|
@ -23,20 +23,6 @@ public final class NavigationViewModel: ObservableObject {
|
|||
return exploreViewModel
|
||||
}()
|
||||
|
||||
public lazy var notificationsViewModel: CollectionViewModel? = {
|
||||
if identityContext.identity.authenticated {
|
||||
let notificationsViewModel = CollectionItemsViewModel(
|
||||
collectionService: identityContext.service.notificationsService(),
|
||||
identityContext: identityContext)
|
||||
|
||||
notificationsViewModel.request(maxId: nil, minId: nil, search: nil)
|
||||
|
||||
return notificationsViewModel
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
public lazy var conversationsViewModel: CollectionViewModel? = {
|
||||
if identityContext.identity.authenticated {
|
||||
let conversationsViewModel = CollectionItemsViewModel(
|
||||
|
@ -140,4 +126,14 @@ public extension NavigationViewModel {
|
|||
collectionService: identityContext.service.service(timeline: timeline),
|
||||
identityContext: identityContext)
|
||||
}
|
||||
|
||||
func notificationsViewModel(excludeTypes: Set<MastodonNotification.NotificationType>) -> CollectionItemsViewModel {
|
||||
let viewModel = CollectionItemsViewModel(
|
||||
collectionService: identityContext.service.notificationsService(excludeTypes: excludeTypes),
|
||||
identityContext: identityContext)
|
||||
|
||||
viewModel.request(maxId: nil, minId: nil, search: nil)
|
||||
|
||||
return viewModel
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,6 +223,8 @@ extension MastodonNotification.NotificationType {
|
|||
return "star.fill"
|
||||
case .poll:
|
||||
return "chart.bar.doc.horizontal"
|
||||
case .status:
|
||||
return "house"
|
||||
case .mention, .unknown:
|
||||
return "at"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue