mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Notifications (#275)
This commit is contained in:
@@ -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 }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user