Move complete room notification settings module to RiotSwiftUI and fix naming/directory structure.

This commit is contained in:
David Langley
2021-09-02 14:09:29 +01:00
parent a70d424a73
commit 007e0a8b6f
13 changed files with 19 additions and 19 deletions
@@ -0,0 +1,101 @@
//
// Copyright 2021 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
@available(iOS 14.0, *)
@objc protocol NotificationSettingsCoordinatorBridgePresenterDelegate {
func notificationSettingsCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: NotificationSettingsCoordinatorBridgePresenter)
}
/// NotificationSettingsCoordinatorBridgePresenter enables to start NotificationSettingsCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
/// It breaks the Coordinator abstraction and it has been introduced for Objective-C compatibility (mainly for integration in legacy view controllers).
/// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator.
@available(iOS 14.0, *)
@objcMembers
final class NotificationSettingsCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var coordinator: NotificationSettingsCoordinator?
private var router: NavigationRouter?
// MARK: Public
weak var delegate: NotificationSettingsCoordinatorBridgePresenterDelegate?
// MARK: - Setup
init(session: MXSession) {
self.session = session
super.init()
}
// MARK: - Public
func push(from navigationController: UINavigationController, animated: Bool, screen: NotificationSettingsScreen, popCompletion: (() -> Void)?) {
let router = NavigationRouter(navigationController: navigationController)
let notificationSettingsCoordinator = NotificationSettingsCoordinator(session: session, screen: screen)
router.push(notificationSettingsCoordinator, animated: animated) { [weak self] in
self?.coordinator = nil
self?.router = nil
popCompletion?()
}
notificationSettingsCoordinator.start()
self.coordinator = notificationSettingsCoordinator
self.router = router
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let coordinator = self.coordinator else {
return
}
coordinator.toPresentable().dismiss(animated: animated) {
self.coordinator = nil
if let completion = completion {
completion()
}
}
}
}
// MARK: - NotificationSettingsCoordinatorDelegate
@available(iOS 14.0, *)
extension NotificationSettingsCoordinatorBridgePresenter: NotificationSettingsCoordinatorDelegate {
func notificationSettingsCoordinatorDidComplete(_ coordinator: NotificationSettingsCoordinatorType) {
self.delegate?.notificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self)
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
@available(iOS 14.0, *)
extension NotificationSettingsCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate {
func notificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) {
self.delegate?.notificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self)
}
}
@@ -0,0 +1,76 @@
// File created from ScreenTemplate
// $ createScreen.sh Settings/Notifications NotificationSettings
/*
Copyright 2021 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
import SwiftUI
@available(iOS 14.0, *)
final class NotificationSettingsCoordinator: NotificationSettingsCoordinatorType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var notificationSettingsViewModel: NotificationSettingsViewModelType
private let notificationSettingsViewController: UIViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: NotificationSettingsCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, screen: NotificationSettingsScreen) {
self.session = session
let notificationSettingsService = MXNotificationSettingsService(session: session)
let viewModel = NotificationSettingsViewModel(notificationSettingsService: notificationSettingsService, ruleIds: screen.pushRules)
let viewController: UIViewController
switch screen {
case .defaultNotifications:
viewController = VectorHostingController(rootView: DefaultNotificationSettings(viewModel: viewModel))
case .mentionsAndKeywords:
viewController = VectorHostingController(rootView: MentionsAndKeywordNotificationSettings(viewModel: viewModel))
case .other:
viewController = VectorHostingController(rootView: OtherNotificationSettings(viewModel: viewModel))
}
self.notificationSettingsViewModel = viewModel
self.notificationSettingsViewController = viewController
}
// MARK: - Public methods
func start() {
self.notificationSettingsViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.notificationSettingsViewController
}
}
// MARK: - NotificationSettingsViewModelCoordinatorDelegate
@available(iOS 14.0, *)
extension NotificationSettingsCoordinator: NotificationSettingsViewModelCoordinatorDelegate {
func notificationSettingsViewModelDidComplete(_ viewModel: NotificationSettingsViewModelType) {
self.delegate?.notificationSettingsCoordinatorDidComplete(self)
}
}
@@ -0,0 +1,28 @@
// File created from ScreenTemplate
// $ createScreen.sh Settings/Notifications NotificationSettings
/*
Copyright 2021 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 NotificationSettingsCoordinatorDelegate: AnyObject {
func notificationSettingsCoordinatorDidComplete(_ coordinator: NotificationSettingsCoordinatorType)
}
/// `NotificationSettingsCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol NotificationSettingsCoordinatorType: Coordinator, Presentable {
var delegate: NotificationSettingsCoordinatorDelegate? { get }
}
@@ -0,0 +1,86 @@
//
// Copyright 2021 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
import DesignKit
/**
Conformance of MXPushRule to the abstraction `NotificationPushRule` for use in `NotificationSettingsViewModel`.
*/
extension MXPushRule: NotificationPushRuleType {
/*
Given a rule, check it match the actions in the static definition.
*/
func matches(standardActions: NotificationStandardActions?) -> Bool {
guard let standardActions = standardActions else {
return false
}
if !enabled && standardActions == .disabled {
return true
}
if enabled,
let actions = standardActions.actions,
highlight == actions.highlight,
sound == actions.sound,
notify == actions.notify,
dontNotify == !actions.notify {
return true
}
return false
}
private func getAction(actionType: MXPushRuleActionType, tweakType: String? = nil) -> MXPushRuleAction? {
guard let actions = actions as? [MXPushRuleAction] else {
return nil
}
return actions.first { action in
var match = action.actionType == actionType
if let tweakType = tweakType,
let actionTweak = action.parameters?["set_tweak"] as? String {
match = match && (tweakType == actionTweak)
}
return match
}
}
var highlight: Bool {
guard let action = getAction(actionType: MXPushRuleActionTypeSetTweak, tweakType: "highlight") else {
return false
}
if let highlight = action.parameters["value"] as? Bool {
return highlight
}
return true
}
var sound: String? {
guard let action = getAction(actionType: MXPushRuleActionTypeSetTweak, tweakType: "sound") else {
return nil
}
return action.parameters["value"] as? String
}
var notify: Bool {
return getAction(actionType: MXPushRuleActionTypeNotify) != nil
}
var dontNotify: Bool {
return getAction(actionType: MXPushRuleActionTypeDontNotify) != nil
}
}
@@ -16,7 +16,7 @@
import Foundation
struct MockNotificationPushRule: NotificationPushRule {
struct MockNotificationPushRule: NotificationPushRuleType {
var ruleId: String!
var enabled: Bool
func matches(standardActions: NotificationStandardActions?) -> Bool {
@@ -16,7 +16,7 @@
import Foundation
protocol NotificationPushRule {
protocol NotificationPushRuleType {
var ruleId: String! { get }
var enabled: Bool { get }
func matches(standardActions: NotificationStandardActions?) -> Bool
@@ -0,0 +1,88 @@
//
// Copyright 2021 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
import Combine
@available(iOS 14.0, *)
class MXNotificationSettingsService: NotificationSettingsServiceType {
private let session: MXSession
private var cancellables = Set<AnyCancellable>()
@Published private var contentRules = [MXPushRule]()
@Published private var rules = [MXPushRule]()
var rulesPublisher: AnyPublisher<[NotificationPushRuleType], Never> {
$rules.map({ $0.map({ $0 as NotificationPushRuleType }) }).eraseToAnyPublisher()
}
var contentRulesPublisher: AnyPublisher<[NotificationPushRuleType], Never> {
$contentRules.map({ $0.map({ $0 as NotificationPushRuleType }) }).eraseToAnyPublisher()
}
init(session: MXSession) {
self.session = session
// Publisher of all rule updates
let rulesUpdated = NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: kMXNotificationCenterDidUpdateRules))
// Set initial value of the content rules
if let contentRules = session.notificationCenter.rules.global.content as? [MXPushRule] {
self.contentRules = contentRules
}
// Observe future updates to content rules
rulesUpdated
.compactMap({ _ in self.session.notificationCenter.rules.global.content as? [MXPushRule] })
.assign(to: &$contentRules)
// Set initial value of rules
if let flatRules = session.notificationCenter.flatRules as? [MXPushRule] {
rules = flatRules
}
// Observe future updates to rules
rulesUpdated
.compactMap({ _ in self.session.notificationCenter.flatRules as? [MXPushRule] })
.assign(to: &$rules)
}
func add(keyword: String, enabled: Bool) {
let index = NotificationIndex.index(when: enabled)
guard let actions = NotificationPushRuleId.keywords.standardActions(for: index)?.actions
else {
return
}
session.notificationCenter.addContentRuleWithRuleId(matchingPattern: keyword, notify: actions.notify, sound: actions.sound, highlight: actions.highlight)
}
func remove(keyword: String) {
guard let rule = session.notificationCenter.rule(byId: keyword) else { return }
session.notificationCenter.removeRule(rule)
}
func updatePushRuleActions(for ruleId: String, enabled: Bool, actions: NotificationActions?) {
guard let rule = session.notificationCenter.rule(byId: ruleId) else { return }
session.notificationCenter.enableRule(rule, isEnabled: enabled)
if let actions = actions {
session.notificationCenter.updatePushRuleActions(ruleId,
kind: rule.kind,
notify: actions.notify,
soundName: actions.sound,
highlight: actions.highlight)
}
}
}
@@ -22,10 +22,10 @@ class MockNotificationSettingsService: NotificationSettingsServiceType, Observab
static let example = MockNotificationSettingsService()
@Published var keywords = Set<String>()
@Published var rules = [NotificationPushRule]()
@Published var contentRules = [NotificationPushRule]()
@Published var rules = [NotificationPushRuleType]()
@Published var contentRules = [NotificationPushRuleType]()
var contentRulesPublisher: AnyPublisher<[NotificationPushRule], Never> {
var contentRulesPublisher: AnyPublisher<[NotificationPushRuleType], Never> {
$contentRules.eraseToAnyPublisher()
}
@@ -33,7 +33,7 @@ class MockNotificationSettingsService: NotificationSettingsServiceType, Observab
$keywords.eraseToAnyPublisher()
}
var rulesPublisher: AnyPublisher<[NotificationPushRule], Never> {
var rulesPublisher: AnyPublisher<[NotificationPushRuleType], Never> {
$rules.eraseToAnyPublisher()
}
@@ -25,11 +25,11 @@ protocol NotificationSettingsServiceType {
/**
Publisher of all push rules.
*/
var rulesPublisher: AnyPublisher<[NotificationPushRule], Never> { get }
var rulesPublisher: AnyPublisher<[NotificationPushRuleType], Never> { get }
/**
Publisher of content rules.
*/
var contentRulesPublisher: AnyPublisher<[NotificationPushRule], Never> { get }
var contentRulesPublisher: AnyPublisher<[NotificationPushRuleType], Never> { get }
/**
Adds a keyword.
@@ -155,7 +155,7 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob
}
// MARK: - Private
private func rulesUpdated(newRules: [NotificationPushRule]) {
private func rulesUpdated(newRules: [NotificationPushRuleType]) {
for rule in newRules {
guard let ruleId = NotificationPushRuleId(rawValue: rule.ruleId),
ruleIds.contains(ruleId) else { continue }
@@ -174,7 +174,7 @@ final class NotificationSettingsViewModel: NotificationSettingsViewModelType, Ob
Matcing is done by comparing the rule against the static definitions for that rule.
The same logic is used on android.
*/
private func isChecked(rule: NotificationPushRule) -> Bool {
private func isChecked(rule: NotificationPushRuleType) -> Bool {
guard let ruleId = NotificationPushRuleId(rawValue: rule.ruleId) else { return false }
let firstIndex = NotificationIndex.allCases.first { nextIndex in