mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Removing Delivered Notifications (#1305)
* completed * changelog * tests * more tests * better naming * fix * a bit more waiting time * generated mocks * xcodegen
This commit is contained in:
@@ -197,6 +197,25 @@ class BugReportServiceMock: BugReportServiceProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
class NotificationCenterMock: NotificationCenterProtocol {
|
||||
|
||||
//MARK: - post
|
||||
|
||||
var postNameObjectCallsCount = 0
|
||||
var postNameObjectCalled: Bool {
|
||||
return postNameObjectCallsCount > 0
|
||||
}
|
||||
var postNameObjectReceivedArguments: (aName: NSNotification.Name, anObject: Any?)?
|
||||
var postNameObjectReceivedInvocations: [(aName: NSNotification.Name, anObject: Any?)] = []
|
||||
var postNameObjectClosure: ((NSNotification.Name, Any?) -> Void)?
|
||||
|
||||
func post(name aName: NSNotification.Name, object anObject: Any?) {
|
||||
postNameObjectCallsCount += 1
|
||||
postNameObjectReceivedArguments = (aName: aName, anObject: anObject)
|
||||
postNameObjectReceivedInvocations.append((aName: aName, anObject: anObject))
|
||||
postNameObjectClosure?(aName, anObject)
|
||||
}
|
||||
}
|
||||
class NotificationManagerMock: NotificationManagerProtocol {
|
||||
var delegate: NotificationManagerDelegate?
|
||||
|
||||
@@ -1365,6 +1384,39 @@ class UserNotificationCenterMock: UserNotificationCenterProtocol {
|
||||
return requestAuthorizationOptionsReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - deliveredNotifications
|
||||
|
||||
var deliveredNotificationsCallsCount = 0
|
||||
var deliveredNotificationsCalled: Bool {
|
||||
return deliveredNotificationsCallsCount > 0
|
||||
}
|
||||
var deliveredNotificationsReturnValue: [UNNotification]!
|
||||
var deliveredNotificationsClosure: (() async -> [UNNotification])?
|
||||
|
||||
func deliveredNotifications() async -> [UNNotification] {
|
||||
deliveredNotificationsCallsCount += 1
|
||||
if let deliveredNotificationsClosure = deliveredNotificationsClosure {
|
||||
return await deliveredNotificationsClosure()
|
||||
} else {
|
||||
return deliveredNotificationsReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - removeDeliveredNotifications
|
||||
|
||||
var removeDeliveredNotificationsWithIdentifiersCallsCount = 0
|
||||
var removeDeliveredNotificationsWithIdentifiersCalled: Bool {
|
||||
return removeDeliveredNotificationsWithIdentifiersCallsCount > 0
|
||||
}
|
||||
var removeDeliveredNotificationsWithIdentifiersReceivedIdentifiers: [String]?
|
||||
var removeDeliveredNotificationsWithIdentifiersReceivedInvocations: [[String]] = []
|
||||
var removeDeliveredNotificationsWithIdentifiersClosure: (([String]) -> Void)?
|
||||
|
||||
func removeDeliveredNotifications(withIdentifiers identifiers: [String]) {
|
||||
removeDeliveredNotificationsWithIdentifiersCallsCount += 1
|
||||
removeDeliveredNotificationsWithIdentifiersReceivedIdentifiers = identifiers
|
||||
removeDeliveredNotificationsWithIdentifiersReceivedInvocations.append(identifiers)
|
||||
removeDeliveredNotificationsWithIdentifiersClosure?(identifiers)
|
||||
}
|
||||
//MARK: - setNotificationCategories
|
||||
|
||||
var setNotificationCategoriesCallsCount = 0
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Notification.Name {
|
||||
static let roomMarkedAsRead = Notification.Name("roomMarkedAsRead")
|
||||
static let invitesScreenAppeared = Notification.Name("invitesScreenAppeared")
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol NotificationCenterProtocol: AnyObject {
|
||||
func post(name aName: NSNotification.Name, object anObject: Any?)
|
||||
}
|
||||
|
||||
extension NotificationCenter: NotificationCenterProtocol { }
|
||||
@@ -48,4 +48,5 @@ struct InvitesScreenRoomDetails: Identifiable {
|
||||
enum InvitesScreenViewAction {
|
||||
case accept(InvitesScreenRoomDetails)
|
||||
case decline(InvitesScreenRoomDetails)
|
||||
case appeared
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel
|
||||
private let appSettings: AppSettings
|
||||
private let analytics: AnalyticsService
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let notificationCenterProtocol: NotificationCenterProtocol
|
||||
|
||||
private let previouslySeenInvites: Set<String>
|
||||
private let actionsSubject: PassthroughSubject<InvitesScreenViewModelAction, Never> = .init()
|
||||
@@ -36,11 +37,13 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel
|
||||
init(userSession: UserSessionProtocol,
|
||||
appSettings: AppSettings,
|
||||
analytics: AnalyticsService,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
notificationCenterProtocol: NotificationCenterProtocol = NotificationCenter.default) {
|
||||
self.userSession = userSession
|
||||
self.appSettings = appSettings
|
||||
self.analytics = analytics
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.notificationCenterProtocol = notificationCenterProtocol
|
||||
|
||||
previouslySeenInvites = appSettings.seenInvites
|
||||
super.init(initialViewState: InvitesScreenViewState(), imageProvider: userSession.mediaProvider)
|
||||
@@ -55,6 +58,8 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel
|
||||
accept(invite: invite)
|
||||
case .decline(let invite):
|
||||
startDeclineFlow(invite: invite)
|
||||
case .appeared:
|
||||
notificationCenterProtocol.post(name: .invitesScreenAppeared, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,11 @@ struct InvitesScreen: View {
|
||||
noInvitesContent
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
context.send(viewAction: .appeared)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var noInvitesContent: some View {
|
||||
Text(L10n.screenInvitesEmptyList)
|
||||
.font(.compound.bodyLG)
|
||||
|
||||
@@ -33,18 +33,21 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let analytics: AnalyticsService
|
||||
private unowned let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let notificationCenterProtocol: NotificationCenterProtocol
|
||||
|
||||
init(timelineController: RoomTimelineControllerProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
roomProxy: RoomProxyProtocol,
|
||||
appSettings: AppSettings,
|
||||
analytics: AnalyticsService,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
notificationCenterProtocol: NotificationCenterProtocol = NotificationCenter.default) {
|
||||
self.roomProxy = roomProxy
|
||||
self.timelineController = timelineController
|
||||
self.appSettings = appSettings
|
||||
self.analytics = analytics
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.notificationCenterProtocol = notificationCenterProtocol
|
||||
|
||||
super.init(initialViewState: RoomScreenViewState(roomId: timelineController.roomID,
|
||||
roomTitle: roomProxy.roomTitle,
|
||||
@@ -214,6 +217,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
|
||||
private func markRoomAsRead() async {
|
||||
notificationCenterProtocol.post(name: .roomMarkedAsRead, object: roomProxy.id)
|
||||
_ = await timelineController.markRoomAsRead()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import Foundation
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
class NotificationManager: NSObject, NotificationManagerProtocol {
|
||||
final class NotificationManager: NSObject, NotificationManagerProtocol {
|
||||
private let notificationCenter: UserNotificationCenterProtocol
|
||||
private let appSettings: AppSettings
|
||||
|
||||
@@ -31,6 +31,8 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
|
||||
appSettings: AppSettings) {
|
||||
self.notificationCenter = notificationCenter
|
||||
self.appSettings = appSettings
|
||||
super.init()
|
||||
addObservers()
|
||||
}
|
||||
|
||||
// MARK: NotificationManagerProtocol
|
||||
@@ -169,6 +171,47 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
|
||||
delegate?.unregisterForRemoteNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
private func addObservers() {
|
||||
NotificationCenter
|
||||
.default
|
||||
.publisher(for: .roomMarkedAsRead)
|
||||
.sink { [weak self] notification in
|
||||
guard let roomID = notification.object as? String else {
|
||||
return
|
||||
}
|
||||
self?.removeMessageNotifications(for: roomID)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
NotificationCenter
|
||||
.default
|
||||
.publisher(for: .invitesScreenAppeared)
|
||||
.sink { [weak self] _ in
|
||||
self?.removeInviteNotifications()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func removeMessageNotifications(for roomID: String) {
|
||||
Task {
|
||||
let notificationsIdentifiers = await notificationCenter
|
||||
.deliveredNotifications()
|
||||
.filter { $0.request.content.roomID == roomID }
|
||||
.map(\.request.identifier)
|
||||
notificationCenter.removeDeliveredNotifications(withIdentifiers: notificationsIdentifiers)
|
||||
}
|
||||
}
|
||||
|
||||
private func removeInviteNotifications() {
|
||||
Task {
|
||||
let notificationsIdentifiers = await notificationCenter
|
||||
.deliveredNotifications()
|
||||
.filter { $0.request.content.categoryIdentifier == NotificationConstants.Category.invite }
|
||||
.map(\.request.identifier)
|
||||
notificationCenter.removeDeliveredNotifications(withIdentifiers: notificationsIdentifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UNUserNotificationCenterDelegate
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
// Sadly we can't mock this because the delegate needs to be weak
|
||||
// Use UserNotificationCenterSpy
|
||||
protocol UserNotificationCenterProtocol: AnyObject {
|
||||
var delegate: UNUserNotificationCenterDelegate? { get set }
|
||||
func add(_ request: UNNotificationRequest) async throws
|
||||
func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool
|
||||
func deliveredNotifications() async -> [UNNotification]
|
||||
func removeDeliveredNotifications(withIdentifiers identifiers: [String])
|
||||
func setNotificationCategories(_ categories: Set<UNNotificationCategory>)
|
||||
func authorizationStatus() async -> UNAuthorizationStatus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user