Notifications (#275)

This commit is contained in:
ismailgulek
2022-11-21 19:37:13 +03:00
committed by GitHub
parent 9f3ed6ca7b
commit d389ce7ad7
74 changed files with 2459 additions and 324 deletions
@@ -31,7 +31,7 @@ struct ServiceLocator {
let userNotificationController: UserNotificationControllerProtocol
}
class AppCoordinator: CoordinatorProtocol {
class AppCoordinator: AppCoordinatorProtocol {
private let stateMachine: AppCoordinatorStateMachine
private let navigationController: NavigationController
private let userSessionStore: UserSessionStoreProtocol
@@ -40,6 +40,7 @@ class AppCoordinator: CoordinatorProtocol {
didSet {
deobserveUserSessionChanges()
if let userSession, !userSession.isSoftLogout {
configureNotificationManager()
observeUserSessionChanges()
}
}
@@ -52,7 +53,8 @@ class AppCoordinator: CoordinatorProtocol {
private let backgroundTaskService: BackgroundTaskServiceProtocol
private var cancellables = Set<AnyCancellable>()
private(set) var notificationManager: NotificationManagerProtocol?
init() {
navigationController = NavigationController()
stateMachine = AppCoordinatorStateMachine()
@@ -60,17 +62,12 @@ class AppCoordinator: CoordinatorProtocol {
bugReportService = BugReportService(withBaseURL: BuildSettings.bugReportServiceBaseURL, sentryURL: BuildSettings.bugReportSentryURL)
navigationController.setRootCoordinator(SplashScreenCoordinator())
ServiceLocator.serviceLocator = ServiceLocator(userNotificationController: UserNotificationController(rootCoordinator: navigationController))
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
fatalError("Should have a valid bundle identifier at this point")
}
backgroundTaskService = UIKitBackgroundTaskService(withApplication: UIApplication.shared)
userSessionStore = UserSessionStore(bundleIdentifier: bundleIdentifier,
backgroundTaskService: backgroundTaskService)
userSessionStore = UserSessionStore(backgroundTaskService: backgroundTaskService)
setupStateMachine()
@@ -97,6 +94,7 @@ class AppCoordinator: CoordinatorProtocol {
private func setupLogging() {
let loggerConfiguration = MXLogConfiguration()
loggerConfiguration.maxLogFilesCount = 10
#if DEBUG
// This exposes the full Rust side tracing subscriber filter for more flexibility.
@@ -252,6 +250,8 @@ class AppCoordinator: CoordinatorProtocol {
// regardless of the result, clear user data
userSessionStore.logout(userSession: userSession)
userSession = nil
notificationManager?.delegate = nil
notificationManager = nil
}
}
@@ -268,6 +268,38 @@ class AppCoordinator: CoordinatorProtocol {
startAuthentication()
}
}
private func configureNotificationManager() {
guard BuildSettings.enableNotifications else {
return
}
guard notificationManager == nil else {
return
}
let manager = NotificationManager(clientProxy: userSession.clientProxy)
if manager.isAvailable {
manager.delegate = self
notificationManager = manager
manager.start()
if let appDelegate = AppDelegate.shared {
appDelegate.callbacks
.receive(on: DispatchQueue.main)
.sink { [weak self] callback in
switch callback {
case .registeredNotifications(let deviceToken):
self?.notificationManager?.register(with: deviceToken)
case .failedToRegisteredNotifications(let error):
self?.notificationManager?.registrationFailed(with: error)
}
}
.store(in: &cancellables)
} else {
MXLog.debug("Couldn't register to AppDelegate callbacks")
}
}
}
private func observeUserSessionChanges() {
userSession.callbacks
@@ -321,3 +353,51 @@ extension AppCoordinator: AuthenticationCoordinatorDelegate {
stateMachine.processEvent(.succeededSigningIn)
}
}
// MARK: - NotificationManagerDelegate
extension AppCoordinator: NotificationManagerDelegate {
func authorizationStatusUpdated(_ service: NotificationManagerProtocol, granted: Bool) {
if granted {
UIApplication.shared.registerForRemoteNotifications()
}
}
func shouldDisplayInAppNotification(_ service: NotificationManagerProtocol, content: UNNotificationContent) -> Bool {
guard let roomId = content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String else {
return true
}
guard let userSessionFlowCoordinator else {
// there is not a user session yet
return false
}
return !userSessionFlowCoordinator.isDisplayingRoomScreen(withRoomId: roomId)
}
func notificationTapped(_ service: NotificationManagerProtocol, content: UNNotificationContent) async {
MXLog.debug("[AppCoordinator] tappedNotification")
guard let roomId = content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String else {
return
}
userSessionFlowCoordinator?.tryDisplayingRoomScreen(roomId: roomId)
}
func handleInlineReply(_ service: NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async {
MXLog.debug("[AppCoordinator] handle notification reply")
guard let roomId = content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String else {
return
}
let roomProxy = await userSession.clientProxy.roomForIdentifier(roomId)
switch await roomProxy?.sendMessage(replyText) {
case .success:
break
default:
// error or no room proxy
service.showLocalNotification(with: "⚠️ " + ElementL10n.dialogTitleError,
subtitle: ElementL10n.a11yErrorSomeMessageNotSent)
}
}
}
@@ -0,0 +1,21 @@
//
// Copyright 2022 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
protocol AppCoordinatorProtocol: CoordinatorProtocol {
var notificationManager: NotificationManagerProtocol? { get }
}
+21 -29
View File
@@ -14,38 +14,30 @@
// limitations under the License.
//
import SwiftUI
import Combine
import Foundation
import UIKit
@main
struct Application: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var applicationDelegate
private let applicationCoordinator: CoordinatorProtocol
init() {
if Tests.isRunningUITests {
applicationCoordinator = UITestsAppCoordinator()
} else {
applicationCoordinator = AppCoordinator()
}
}
var body: some Scene {
WindowGroup {
if Tests.isRunningUnitTests {
EmptyView()
} else {
applicationCoordinator.toPresentable()
.tint(.element.accent)
.task {
applicationCoordinator.start()
}
}
}
}
enum AppDelegateCallback {
case registeredNotifications(deviceToken: Data)
case failedToRegisteredNotifications(error: Error)
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
true
private(set) static var shared: AppDelegate!
let callbacks = PassthroughSubject<AppDelegateCallback, Never>()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
// worst singleton ever
Self.shared = self
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
callbacks.send(.registeredNotifications(deviceToken: deviceToken))
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
callbacks.send(.failedToRegisteredNotifications(error: error))
}
}
@@ -0,0 +1,45 @@
//
// Copyright 2022 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 SwiftUI
@main
struct Application: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var applicationDelegate
private let applicationCoordinator: AppCoordinatorProtocol
init() {
if Tests.isRunningUITests {
applicationCoordinator = UITestsAppCoordinator()
} else {
applicationCoordinator = AppCoordinator()
}
}
var body: some Scene {
WindowGroup {
if Tests.isRunningUnitTests {
EmptyView()
} else {
applicationCoordinator.toPresentable()
.tint(.element.accent)
.task {
applicationCoordinator.start()
}
}
}
}
}
@@ -17,12 +17,22 @@
import Foundation
final class BuildSettings {
// MARK: - Bundle Settings
static var pusherAppId: String {
#if DEBUG
InfoPlistReader.target.baseBundleIdentifier + ".ios.dev"
#else
InfoPlistReader.target.baseBundleIdentifier + ".ios.prod"
#endif
}
// MARK: - Servers
static let defaultHomeserverAddress = "matrix.org"
static let defaultSlidingSyncProxyBaseURLString = "https://slidingsync.lab.element.dev"
static let pushGatewayBaseURL = URL(staticString: "https://matrix.org/_matrix/push/v1/notify")
// MARK: - Bug report
static let bugReportServiceBaseURL = URL(staticString: "https://riot.im/bugreports")
@@ -38,14 +48,14 @@ final class BuildSettings {
#if DEBUG
/// The configuration to use for analytics during development. Set `isEnabled` to false to disable analytics in debug builds.
/// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations.
static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: ElementInfoPlist.cfBundleIdentifier.starts(with: "io.element.elementx"),
static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.target.bundleIdentifier.starts(with: "io.element.elementx"),
host: "https://posthog.element.dev",
apiKey: "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN",
termsURL: URL(staticString: "https://element.io/cookie-policy"))
#else
/// The configuration to use for analytics. Set `isEnabled` to false to disable analytics.
/// **Note:** Analytics are disabled by default for forks. If you are maintaining a fork, set custom configurations.
static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: ElementInfoPlist.cfBundleIdentifier.starts(with: "io.element.elementx"),
static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: InfoPlistReader.target.bundleIdentifier.starts(with: "io.element.elementx"),
host: "https://posthog.hss.element.io",
apiKey: "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
termsURL: URL(staticString: "https://element.io/cookie-policy"))
@@ -63,4 +73,8 @@ final class BuildSettings {
// MARK: - Other
static var permalinkBaseURL = URL(staticString: "https://matrix.to")
// MARK: - Notifications
static let enableNotifications = false
}