From 3e3d5c672913d43cce2e54e789ec722aa49622b4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 10 Sep 2019 19:34:40 +0200 Subject: [PATCH 1/9] AppDelegate: Post a notification with requests parameters on email validation. --- Riot/AppDelegate.h | 4 ++++ Riot/AppDelegate.m | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index d44a70a83..1ecb646da 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -38,6 +38,10 @@ extern NSString *const kAppDelegateDidTapStatusBarNotification; */ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification; +extern NSString *const AppDelegateDidValidateEmailNotification; +extern NSString *const AppDelegateDidValidateEmailNotificationSIDKey; +extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey; + @interface AppDelegate : UIResponder { BOOL isPushRegistered; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 7142875a1..d20dd38fa 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -84,6 +84,10 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapStatusBarNotification"; NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateNetworkStatusDidChangeNotification"; +NSString *const AppDelegateDidValidateEmailNotification = @"AppDelegateDidValidateEmailNotification"; +NSString *const AppDelegateDidValidateEmailNotificationSIDKey = @"AppDelegateDidValidateEmailNotificationSIDKey"; +NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDelegateDidValidateEmailNotificationClientSecretKey"; + @interface AppDelegate () { /** @@ -2136,7 +2140,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSMutableDictionary *queryParams; [self parseUniversalLinkFragment:webURL.absoluteString outPathParams:&pathParams outQueryParams:&queryParams]; - [identityService submit3PIDValidationToken:queryParams[@"token"] medium:kMX3PIDMediumEmail clientSecret:queryParams[@"client_secret"] sid:queryParams[@"sid"] success:^{ + NSString *clientSecret = queryParams[@"client_secret"]; + NSString *sid = queryParams[@"sid"]; + + [identityService submit3PIDValidationToken:queryParams[@"token"] medium:kMX3PIDMediumEmail clientSecret:clientSecret sid:sid success:^{ NSLog(@"[AppDelegate] handleUniversalLink. Email successfully validated."); @@ -2150,7 +2157,15 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN else { // No nextLink in Vector world means validation for binding a new email - NSLog(@"[AppDelegate] handleUniversalLink. TODO: Complete email binding"); + + // Post a notification about email validation to make a chance to SettingsDiscoveryThreePidDetailsViewModel to make it discoverable or not by the identity server. + if (clientSecret && sid) + { + NSDictionary *userInfo = @{ AppDelegateDidValidateEmailNotificationClientSecretKey : clientSecret, + AppDelegateDidValidateEmailNotificationSIDKey : sid }; + + [[NSNotificationCenter defaultCenter] postNotificationName:AppDelegateDidValidateEmailNotification object:nil userInfo:userInfo]; + } } } failure:^(NSError *error) { From 881886cd167d2f450f6aae20334e5e3b6f4cf85a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 10 Sep 2019 20:27:41 +0200 Subject: [PATCH 2/9] Settings discovery: Implement three pid details screen. --- Riot/Generated/Storyboards.swift | 5 + ...sDiscoveryThreePidDetailsCoordinator.swift | 56 ++++ ...PidDetailsCoordinatorBridgePresenter.swift | 63 +++++ ...coveryThreePidDetailsCoordinatorType.swift | 23 ++ ...gsDiscoveryThreePidDetailsViewAction.swift | 28 ++ ...ryThreePidDetailsViewController.storyboard | 152 +++++++++++ ...scoveryThreePidDetailsViewController.swift | 258 ++++++++++++++++++ ...ngsDiscoveryThreePidDetailsViewModel.swift | 240 ++++++++++++++++ ...iscoveryThreePidDetailsViewModelType.swift | 33 +++ ...ngsDiscoveryThreePidDetailsViewState.swift | 33 +++ Riot/SupportingFiles/Riot-Bridging-Header.h | 1 + 11 files changed, 892 insertions(+) create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinator.swift create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinatorType.swift create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewAction.swift create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.storyboard create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.swift create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModelType.swift create mode 100644 Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewState.swift diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 07cadf8f5..9b1096f00 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -97,6 +97,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: ServiceTermsModalScreenViewController.self) } + internal enum SettingsDiscoveryThreePidDetailsViewController: StoryboardType { + internal static let storyboardName = "SettingsDiscoveryThreePidDetailsViewController" + + internal static let initialScene = InitialSceneType(storyboard: SettingsDiscoveryThreePidDetailsViewController.self) + } internal enum SimpleScreenTemplateViewController: StoryboardType { internal static let storyboardName = "SimpleScreenTemplateViewController" diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinator.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinator.swift new file mode 100644 index 000000000..c693d276e --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinator.swift @@ -0,0 +1,56 @@ +// File created from ScreenTemplate +// $ createScreen.sh Details SettingsDiscoveryThreePidDetails +/* + Copyright 2019 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 UIKit + +final class SettingsDiscoveryThreePidDetailsCoordinator: SettingsDiscoveryThreePidDetailsCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var settingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePidDetailsViewModelType + private let settingsDiscoveryThreePidDetailsViewController: SettingsDiscoveryThreePidDetailsViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + // MARK: - Setup + + init(session: MXSession, threePid: MX3PID) { + self.session = session + + let settingsDiscoveryThreePidDetailsViewModel = SettingsDiscoveryThreePidDetailsViewModel(session: self.session, threePid: threePid) + let settingsDiscoveryThreePidDetailsViewController = SettingsDiscoveryThreePidDetailsViewController.instantiate(with: settingsDiscoveryThreePidDetailsViewModel) + self.settingsDiscoveryThreePidDetailsViewModel = settingsDiscoveryThreePidDetailsViewModel + self.settingsDiscoveryThreePidDetailsViewController = settingsDiscoveryThreePidDetailsViewController + } + + // MARK: - Public methods + + func start() { + } + + func toPresentable() -> UIViewController { + return self.settingsDiscoveryThreePidDetailsViewController + } +} diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift new file mode 100644 index 000000000..340db8255 --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift @@ -0,0 +1,63 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh Details SettingsDiscoveryThreePidDetails +/* + Copyright 2019 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 + +/// SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter enables to start SettingsDiscoveryThreePidDetailsCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let threePid: MX3PID + + private var coordinator: SettingsDiscoveryThreePidDetailsCoordinator? + private var router: NavigationRouter? + + // MARK: - Setup + + init(session: MXSession, medium: String, adress: String) { + self.session = session + self.threePid = MX3PID(medium: MX3PID.Medium(identifier: medium), address: adress) + super.init() + } + + // MARK: - Public + + func push(from navigationController: UINavigationController, animated: Bool, popCompletion: (() -> Void)?) { + + let router = NavigationRouter(navigationController: navigationController) + + let settingsDiscoveryThreePidDetailsCoordinator = SettingsDiscoveryThreePidDetailsCoordinator(session: self.session, threePid: self.threePid) + + router.push(settingsDiscoveryThreePidDetailsCoordinator, animated: animated) { [weak self] in + self?.coordinator = nil + self?.router = nil + popCompletion?() + } + + settingsDiscoveryThreePidDetailsCoordinator.start() + + self.coordinator = settingsDiscoveryThreePidDetailsCoordinator + self.router = router + } +} diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinatorType.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinatorType.swift new file mode 100644 index 000000000..4eac94c67 --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsCoordinatorType.swift @@ -0,0 +1,23 @@ +// File created from ScreenTemplate +// $ createScreen.sh Details SettingsDiscoveryThreePidDetails +/* + Copyright 2019 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 + +/// `SettingsDiscoveryThreePidDetailsCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol SettingsDiscoveryThreePidDetailsCoordinatorType: Coordinator, Presentable { +} diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewAction.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewAction.swift new file mode 100644 index 000000000..a6d366651 --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewAction.swift @@ -0,0 +1,28 @@ +// File created from ScreenTemplate +// $ createScreen.sh Details SettingsDiscoveryThreePidDetails +/* + Copyright 2019 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 + +/// SettingsDiscoveryThreePidDetailsViewController view actions exposed to view model +enum SettingsDiscoveryThreePidDetailsViewAction { + case load + case share + case revoke + case cancelEmailValidation + case enterSMSCode(_ code: String) +} diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.storyboard b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.storyboard new file mode 100644 index 000000000..e3dc15bda --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.storyboard @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.swift new file mode 100644 index 000000000..053d4ae55 --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.swift @@ -0,0 +1,258 @@ +// File created from ScreenTemplate +// $ createScreen.sh Details SettingsDiscoveryThreePidDetails +/* + Copyright 2019 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 UIKit + +final class SettingsDiscoveryThreePidDetailsViewController: UIViewController { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var scrollView: UIScrollView! + + @IBOutlet private weak var threePidBackgroundView: UIView! + @IBOutlet private weak var threePidTitleLabel: UILabel! + @IBOutlet private weak var threePidAdressLabel: UILabel! + @IBOutlet private weak var operationButton: UIButton! + @IBOutlet private weak var informationLabel: UILabel! + + // MARK: Private + + private var viewModel: SettingsDiscoveryThreePidDetailsViewModelType! + private var theme: Theme! + private var keyboardAvoider: KeyboardAvoider? + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + private var displayMode: SettingsDiscoveryThreePidDetailsDisplayMode? + + // MARK: - Setup + + class func instantiate(with viewModel: SettingsDiscoveryThreePidDetailsViewModelType) -> SettingsDiscoveryThreePidDetailsViewController { + let viewController = StoryboardScene.SettingsDiscoveryThreePidDetailsViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.keyboardAvoider = KeyboardAvoider(scrollViewContainerView: self.view, scrollView: self.scrollView) + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .load) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.keyboardAvoider?.startAvoiding() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.keyboardAvoider?.stopAvoiding() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.threePidBackgroundView.backgroundColor = theme.backgroundColor + self.threePidTitleLabel.textColor = theme.textPrimaryColor + self.threePidAdressLabel.textColor = theme.textSecondaryColor + + self.informationLabel.textColor = theme.textSecondaryColor + self.operationButton.backgroundColor = theme.backgroundColor + theme.applyStyle(onButton: self.operationButton) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + + self.scrollView.keyboardDismissMode = .interactive + + self.render(threePid: self.viewModel.threePid) + } + + private func render(threePid: MX3PID) { + + let title: String + let threePidTitle: String + let informationText: String + + switch threePid.medium { + case .email: + title = VectorL10n.settingsDiscoveryThreePidDetailsTitleEmail + threePidTitle = VectorL10n.settingsEmailAddress + informationText = VectorL10n.settingsDiscoveryThreePidDetailsInformationEmail + case .msisdn: + title = VectorL10n.settingsDiscoveryThreePidDetailsTitlePhoneNumber + threePidTitle = VectorL10n.settingsPhoneNumber + informationText = VectorL10n.settingsDiscoveryThreePidDetailsInformationPhoneNumber + default: + title = "" + threePidTitle = "" + informationText = "" + } + + self.title = title + self.threePidTitleLabel.text = threePidTitle + self.threePidAdressLabel.text = threePid.address + self.informationLabel.text = informationText + } + + private func render(viewState: SettingsDiscoveryThreePidDetailsViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(displayMode: let displayMode): + self.renderLoaded(displayMode: displayMode) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoaded(displayMode: SettingsDiscoveryThreePidDetailsDisplayMode) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + let operationButtonTitle: String + let operationButtonColor: UIColor + + switch displayMode { + case .share: + operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsShareAction + operationButtonColor = self.theme.tintColor + case .revoke: + operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsRevokeAction + operationButtonColor = self.theme.warningColor + case .cancelEmailValidation: + operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsCancelEmailValidationAction + operationButtonColor = self.theme.warningColor + case .enterSMSCode: + operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsEnterSmsCodeAction + operationButtonColor = self.theme.tintColor + } + + self.operationButton.setTitle(operationButtonTitle, for: .normal) + self.operationButton.setTitleColor(operationButtonColor, for: .normal) + self.operationButton.isEnabled = true + + self.displayMode = displayMode + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + self.operationButton.isEnabled = false + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + self.operationButton.isEnabled = true + } + + private func presentSMSActivationCodeAlert() { + + let alert = UIAlertController(title: VectorL10n.authMsisdnValidationTitle, + message: VectorL10n.authMsisdnValidationMessage, + preferredStyle: .alert) + + alert.addTextField { (textField) in + textField.placeholder = nil + textField.keyboardType = .phonePad + } + + alert.addAction(UIAlertAction(title: VectorL10n.authSubmit, style: .destructive, handler: { _ in + guard let textField = alert.textFields?.first, let smsCode = textField.text, smsCode.isEmpty == false else { + return + } + self.viewModel.process(viewAction: .enterSMSCode(smsCode)) + })) + + alert.addAction(UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: nil)) + + self.present(alert, animated: true, completion: nil) + } + + // MARK: - Actions + + @IBAction private func operationButtonAction(_ sender: Any) { + guard let displayMode = self.displayMode else { + return + } + + let viewAction: SettingsDiscoveryThreePidDetailsViewAction? + + switch displayMode { + case .share: + viewAction = .share + case .revoke: + viewAction = .revoke + case .cancelEmailValidation: + viewAction = .cancelEmailValidation + case .enterSMSCode: + viewAction = nil + self.presentSMSActivationCodeAlert() + } + + if let viewAction = viewAction { + self.viewModel.process(viewAction: viewAction) + } + } +} + +// MARK: - SettingsDiscoveryThreePidDetailsViewModelViewDelegate +extension SettingsDiscoveryThreePidDetailsViewController: SettingsDiscoveryThreePidDetailsViewModelViewDelegate { + + func settingsDiscoveryThreePidDetailsViewModel(_ viewModel: SettingsDiscoveryThreePidDetailsViewModelType, didUpdateViewState viewSate: SettingsDiscoveryThreePidDetailsViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift new file mode 100644 index 000000000..86a76f680 --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift @@ -0,0 +1,240 @@ +// File created from ScreenTemplate +// $ createScreen.sh Details SettingsDiscoveryThreePidDetails +/* + Copyright 2019 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 + +enum SettingsDiscoveryThreePidDetailsViewModelError: Error { + case unknown +} + +private struct ThreePidRequestTokenInfo { + let clientSecret: String + let sid: String + let bind: Bool +} + +final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePidDetailsViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var viewState: SettingsDiscoveryThreePidDetailsViewState? + private var currentThreePidRequestTokenInfo: ThreePidRequestTokenInfo? + private var doesHomeserverSupport3PidRebind: Bool = false + + // MARK: Public + + let threePid: MX3PID + + weak var viewDelegate: SettingsDiscoveryThreePidDetailsViewModelViewDelegate? + + // MARK: - Setup + + init(session: MXSession, threePid: MX3PID) { + self.session = session + self.threePid = threePid + } + + // MARK: - Public + + func process(viewAction: SettingsDiscoveryThreePidDetailsViewAction) { + switch viewAction { + case .load: + self.load() + case .share: + self.share() + case .revoke: + self.revoke() + case .cancelEmailValidation: + self.cancelEmailValidation() + case .enterSMSCode(let code): + self.validatePhoneNumber(with: code) + } + } + + // MARK: - Private + + private func load() { + self.update(viewState: .loading) + + self.checkThreePidDiscoverability() + } + + private func checkThreePidDiscoverability() { + self.isThreePidDiscoverable(self.threePid) { (response) in + switch response { + case .success(let isDiscoverable): + if isDiscoverable { + self.update(viewState: .loaded(displayMode: .revoke)) + } else { + self.update(viewState: .loaded(displayMode: .share)) + } + case .failure(let error): + self.update(viewState: .error(error)) + } + } + } + + private func share() { + self.requestBinding(for: self.threePid, bind: true) + } + + private func revoke() { + self.requestBinding(for: self.threePid, bind: false) + } + + private func requestBinding(for threePid: MX3PID, bind: Bool) { + guard let restClient = self.session.matrixRestClient, + let clientSecret = MXTools.generateSecret() else { + return + } + + self.update(viewState: .loading) + + if self.doesHomeserverSupport3PidRebind == false { + restClient.remove3PID(address: threePid.address, medium: threePid.medium.identifier) { (response) in + switch response { + case .success: + switch self.threePid.medium { + case .email: + restClient.requestToken(forEmail: threePid.address, isDuringRegistration: false, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid) in + + if let sid = sid { + self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind) + self.update(viewState: .loaded(displayMode: .cancelEmailValidation)) + } else { + self.update(viewState: .error( SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + } + + }, failure: { error in + self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + }) + case .msisdn: + let formattedPhoneNumber = self.formattedPhoneNumber(from: threePid.address) + restClient.requestToken(forPhoneNumber: formattedPhoneNumber, isDuringRegistration: false, countryCode: nil, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid, msisdn) in + + if let sid = sid { + self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind) + self.update(viewState: .loaded(displayMode: .enterSMSCode)) + } else { + self.update(viewState: .error( SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + } + + }, failure: { error in + self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + }) + default: + break + } + case .failure(let error): + self.update(viewState: .error(error)) + } + } + } else { + // TODO: Handle rebind API + } + } + + @discardableResult + private func isThreePidDiscoverable(_ threePid: MX3PID, completion: @escaping (_ response: MXResponse) -> Void) -> MXHTTPOperation? { + guard let identityService = self.session.identityService else { + completion(.failure(SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + return nil + } + + return identityService.lookup3PIDs([threePid]) { lookupResponse in + switch lookupResponse { + case .success(let threePids): + completion(.success(threePids.isEmpty == false)) + case .failure(let error): + completion(.failure(error)) + } + } + } + + private func update(viewState: SettingsDiscoveryThreePidDetailsViewState) { + self.viewDelegate?.settingsDiscoveryThreePidDetailsViewModel(self, didUpdateViewState: viewState) + } + + // MARK: Email + + private func cancelEmailValidation() { + self.currentThreePidRequestTokenInfo = nil + self.checkThreePidDiscoverability() + } + + private func registerEmailValidationNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(handleEmailValidationNotification(notification:)), name: .AppDelegateDidValidateEmail, object: nil) + } + + @objc private func handleEmailValidationNotification(notification: Notification) { + guard let userInfo = notification.userInfo, + let clientSecret = userInfo[AppDelegateDidValidateEmailNotificationClientSecretKey] as? String, + let sid = userInfo[AppDelegateDidValidateEmailNotificationSIDKey] as? String, + let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo, + threePidRequestTokenInfo.clientSecret == clientSecret, + threePidRequestTokenInfo.sid == sid else { + return + } + + self.validateThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo) + } + + private func validateThreePid(_ threePid: MX3PID, threePidRequestTokenInfo: ThreePidRequestTokenInfo) { + guard let restClient = self.session.matrixRestClient else { + return + } + + self.update(viewState: .loading) + + restClient.addThirdPartyIdentifier(threePidRequestTokenInfo.sid, clientSecret: threePidRequestTokenInfo.clientSecret, bind: threePidRequestTokenInfo.bind) { response in + switch response { + case .success: + self.checkThreePidDiscoverability() + case .failure(let error): + self.update(viewState: .error(error)) + } + } + } + + // MARK: Phone number + + private func formattedPhoneNumber(from phoneNumber: String) -> String { + guard phoneNumber.starts(with: "+") == false else { + return phoneNumber + } + return "+\(threePid.address)" + } + + private func validatePhoneNumber(with activationCode: String) { + guard let identityService = self.session.identityService, let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo else { + return + } + + identityService.submit3PIDValidationToken(activationCode, medium: MX3PID.Medium.msisdn.identifier, clientSecret: threePidRequestTokenInfo.clientSecret, sid: threePidRequestTokenInfo.sid) { (response) in + switch response { + case .success: + self.checkThreePidDiscoverability() + case .failure(let error): + self.update(viewState: .error(error)) + } + } + } +} diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModelType.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModelType.swift new file mode 100644 index 000000000..ce00918fd --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModelType.swift @@ -0,0 +1,33 @@ +// File created from ScreenTemplate +// $ createScreen.sh Details SettingsDiscoveryThreePidDetails +/* + Copyright 2019 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 SettingsDiscoveryThreePidDetailsViewModelViewDelegate: class { + func settingsDiscoveryThreePidDetailsViewModel(_ viewModel: SettingsDiscoveryThreePidDetailsViewModelType, didUpdateViewState viewSate: SettingsDiscoveryThreePidDetailsViewState) +} + +/// Protocol describing the view model used by `SettingsDiscoveryThreePidDetailsViewController` +protocol SettingsDiscoveryThreePidDetailsViewModelType { + + var threePid: MX3PID { get } + + var viewDelegate: SettingsDiscoveryThreePidDetailsViewModelViewDelegate? { get set } + + func process(viewAction: SettingsDiscoveryThreePidDetailsViewAction) +} diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewState.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewState.swift new file mode 100644 index 000000000..bc2182e52 --- /dev/null +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewState.swift @@ -0,0 +1,33 @@ +// File created from ScreenTemplate +// $ createScreen.sh Details SettingsDiscoveryThreePidDetails +/* + Copyright 2019 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 + +/// SettingsDiscoveryThreePidDetailsViewController view state +enum SettingsDiscoveryThreePidDetailsViewState { + case loading + case loaded(displayMode: SettingsDiscoveryThreePidDetailsDisplayMode) + case error(Error) +} + +enum SettingsDiscoveryThreePidDetailsDisplayMode { + case share + case revoke + case cancelEmailValidation + case enterSMSCode +} diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 92e6e483c..be5547efb 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -14,3 +14,4 @@ #import "EncryptionInfoView.h" #import "EventFormatter.h" #import "MediaPickerViewController.h" +#import "AppDelegate.h" From 04a62eb9daa5fd79dc2c0d5de3dc1f4f919f35f5 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 10 Sep 2019 20:33:27 +0200 Subject: [PATCH 3/9] Settings discovery: Implement settings table view discovery section. --- .../SettingsDiscoveryTableViewSection.swift | 245 ++++++++++++++++++ .../SettingsDiscoveryViewAction.swift | 25 ++ .../SettingsDiscoveryViewModel.swift | 200 ++++++++++++++ .../SettingsDiscoveryViewModelType.swift | 35 +++ .../SettingsDiscoveryViewState.swift | 31 +++ 5 files changed, 536 insertions(+) create mode 100644 Riot/Modules/Settings/Discovery/SettingsDiscoveryTableViewSection.swift create mode 100644 Riot/Modules/Settings/Discovery/SettingsDiscoveryViewAction.swift create mode 100644 Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModel.swift create mode 100644 Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModelType.swift create mode 100644 Riot/Modules/Settings/Discovery/SettingsDiscoveryViewState.swift diff --git a/Riot/Modules/Settings/Discovery/SettingsDiscoveryTableViewSection.swift b/Riot/Modules/Settings/Discovery/SettingsDiscoveryTableViewSection.swift new file mode 100644 index 000000000..202ee5309 --- /dev/null +++ b/Riot/Modules/Settings/Discovery/SettingsDiscoveryTableViewSection.swift @@ -0,0 +1,245 @@ +/* + Copyright 2019 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 + +@objc protocol SettingsDiscoveryTableViewSectionDelegate: class { + + func settingsDiscoveryTableViewSection(_ settingsDiscoveryTableViewSection: SettingsDiscoveryTableViewSection, tableViewCellClass: MXKTableViewCell.Type, forRow: Int) -> MXKTableViewCell + func settingsDiscoveryTableViewSectionDidUpdate(_ settingsDiscoveryTableViewSection: SettingsDiscoveryTableViewSection) +} + +private enum DiscoverySectionRows { + case info(text: String) + case attributedInfo(attributedText: NSAttributedString) + case button(title: String, action: () -> Void) + case threePid(threePid: MX3PID) +} + +@objc final class SettingsDiscoveryTableViewSection: NSObject, Themable { + + // MARK: - Constants + + private enum Constants { + static let defaultFont = UIFont.systemFont(ofSize: 17.0) + } + + // MARK: - Properties + + @objc weak var delegate: SettingsDiscoveryTableViewSectionDelegate? + + // MARK: Private + + private var theme: Theme! + private var viewModel: SettingsDiscoveryViewModel + + // Need to know the state to make `cellForRow` deliver cells accordingly + private var viewState: SettingsDiscoveryViewState = .loading { + didSet { + self.updateRows() + } + } + + private var discoveryRows: [DiscoverySectionRows] = [] + + // MARK: - Setup + + @objc init(viewModel: SettingsDiscoveryViewModel) { + self.theme = ThemeService.shared().theme + self.viewModel = viewModel + super.init() + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .load) + + self.registerThemeServiceDidChangeThemeNotification() + } + + // MARK: - Public + + @objc func numberOfRows() -> Int { + return self.discoveryRows.count + } + + @objc func cellForRow(atRow row: Int) -> UITableViewCell { + + let discoveryRow = self.discoveryRows[row] + + var cell: UITableViewCell? + + let enableInteraction: Bool + + if case .loading = self.viewState { + enableInteraction = false + } else { + enableInteraction = true + } + + switch discoveryRow { + case .info(let infoText): + if let infoCell: MXKTableViewCell = self.cellType(at: row) { + infoCell.textLabel?.numberOfLines = 0 + infoCell.textLabel?.text = infoText + infoCell.selectionStyle = .none + cell = infoCell + } + case .attributedInfo(attributedText: let infoText): + if let infoCell: MXKTableViewCell = self.cellType(at: row) { + infoCell.textLabel?.numberOfLines = 0 + infoCell.textLabel?.attributedText = infoText + infoCell.selectionStyle = .none + cell = infoCell + } + case .button(title: let title, action: let action): + if let buttonCell: MXKTableViewCellWithButton = self.cellType(at: row) { + buttonCell.mxkButton.setTitle(title, for: .normal) + buttonCell.mxkButton.setTitle(title, for: .highlighted) + buttonCell.mxkButton.vc_addAction(action: action) + buttonCell.mxkButton.isEnabled = enableInteraction + cell = buttonCell + } + case .threePid(let threePid): + if let detailCell: MXKTableViewCell = self.cellType(at: row) { + detailCell.accessoryType = .disclosureIndicator + detailCell.textLabel?.text = threePid.address + detailCell.isUserInteractionEnabled = enableInteraction + cell = detailCell + } + } + + return cell ?? UITableViewCell() + } + + @objc func reload() { + self.viewModel.process(viewAction: .load) + } + + @objc func selectRow(_ row: Int) { + let discoveryRow = self.discoveryRows[row] + + switch discoveryRow { + case .threePid(threePid: let threePid): + self.viewModel.process(viewAction: .select(threePid: threePid)) + case .attributedInfo(attributedText: _): + if case let .loaded(displayMode) = self.viewState { + switch displayMode { + case .noThreePidsAdded, .threePidsAdded: + self.viewModel.process(viewAction: .tapUserSettingsLink) + default: + break + } + } + default: + break + } + } + + func update(theme: Theme) { + self.theme = theme + + self.updateRows() + } + + // MARK: - Private + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func cellType(at row: Int) -> T? { + let klass: T.Type = T.self + let tableViewCell = delegate?.settingsDiscoveryTableViewSection(self, tableViewCellClass: klass, forRow: row) + return tableViewCell as? T + } + + private func updateRows() { + + let discoveryRows: [DiscoverySectionRows] + + switch self.viewState { + case .loading: + discoveryRows = self.discoveryRows + case .loaded(let displayMode): + switch displayMode { + case .noIdentityServer: + discoveryRows = [ + .info(text: VectorL10n.settingsDiscoveryNoIdentityServer) + ] + case .termsNotSigned(let host): + discoveryRows = [ + .info(text: VectorL10n.settingsDiscoveryTermsNotSigned(host)), + .button(title: VectorL10n.accept, action: { [weak self] in + self?.viewModel.process(viewAction: .acceptTerms) + }) + ] + case .noThreePidsAdded: + discoveryRows = [ + .attributedInfo(attributedText: self.threePidsManagementInfoAttributedString()) + ] + case .threePidsAdded(let emails, let phoneNumbers): + + let emailThreePids = emails.map { (email) -> DiscoverySectionRows in + return .threePid(threePid: email) + } + + let phoneNumbersThreePids = phoneNumbers.map { (phoneNumber) -> DiscoverySectionRows in + return .threePid(threePid: phoneNumber) + } + + var threePidsRows: [DiscoverySectionRows] = [] + threePidsRows.append(contentsOf: emailThreePids) + threePidsRows.append(contentsOf: phoneNumbersThreePids) + threePidsRows.append(.attributedInfo(attributedText: self.threePidsManagementInfoAttributedString())) + + discoveryRows = threePidsRows + } + case .error: + discoveryRows = [ + .info(text: VectorL10n.settingsDiscoveryErrorMessage), + .button(title: VectorL10n.retry, action: { [weak self] in + self?.viewModel.process(viewAction: .load) + }) + ] + } + + self.discoveryRows = discoveryRows + } + + private func threePidsManagementInfoAttributedString() -> NSAttributedString { + let attributedInfoString = NSMutableAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart1, + attributes: [.foregroundColor: self.theme.textPrimaryColor, .font: Constants.defaultFont]) + attributedInfoString.append(NSAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart2, + attributes: [.foregroundColor: self.theme.tintColor, .font: Constants.defaultFont])) + attributedInfoString.append(NSAttributedString(string: VectorL10n.settingsDiscoveryThreePidsManagementInformationPart3, + attributes: [.foregroundColor: self.theme.tintColor, .font: Constants.defaultFont])) + return attributedInfoString + } +} + +// MARK: - SettingsDiscoveryViewModelViewDelegate +extension SettingsDiscoveryTableViewSection: SettingsDiscoveryViewModelViewDelegate { + + func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModelType, didUpdateViewState viewState: SettingsDiscoveryViewState) { + self.viewState = viewState + + // The tableview datasource will call `self.cellForRow()` + self.delegate?.settingsDiscoveryTableViewSectionDidUpdate(self) + } +} diff --git a/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewAction.swift b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewAction.swift new file mode 100644 index 000000000..2122ee175 --- /dev/null +++ b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewAction.swift @@ -0,0 +1,25 @@ +/* + Copyright 2019 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 + +/// SettingsDiscoveryTableViewSection view actions exposed to view model +enum SettingsDiscoveryViewAction { + case load + case acceptTerms + case select(threePid: MX3PID) + case tapUserSettingsLink +} diff --git a/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModel.swift b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModel.swift new file mode 100644 index 000000000..229e024fc --- /dev/null +++ b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModel.swift @@ -0,0 +1,200 @@ +/* + Copyright 2019 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 + +@objc final class SettingsDiscoveryViewModel: NSObject, SettingsDiscoveryViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var identityService: MXIdentityService? + private var serviceTerms: MXServiceTerms? + private var viewState: SettingsDiscoveryViewState? + private var threePIDs: [MX3PID] = [] + + // MARK: Public + + weak var viewDelegate: SettingsDiscoveryViewModelViewDelegate? + @objc weak var coordinatorDelegate: SettingsDiscoveryViewModelCoordinatorDelegate? + + // MARK: - Setup + + @objc init(session: MXSession, thirdPartyIdentifiers: [MXThirdPartyIdentifier]) { + self.session = session + + let identityService = session.identityService + + if let identityService = identityService { + self.serviceTerms = MXServiceTerms(baseUrl: identityService.identityServer, serviceType: MXServiceTypeIdentityService, matrixSession: session, accessToken: nil) + } + + self.identityService = identityService + self.threePIDs = SettingsDiscoveryViewModel.threePids(from: thirdPartyIdentifiers) + super.init() + } + + // MARK: - Public + + func process(viewAction: SettingsDiscoveryViewAction) { + switch viewAction { + case .load: + self.checkTerms() + case .acceptTerms: + self.acceptTerms() + case .select(threePid: let threePid): + self.coordinatorDelegate?.settingsDiscoveryViewModel(self, didSelectThreePidWith: threePid.medium.identifier, and: threePid.address) + case .tapUserSettingsLink: + self.coordinatorDelegate?.settingsDiscoveryViewModelDidTapUserSettingsLink(self) + } + } + + @objc func update(thirdPartyIdentifiers: [MXThirdPartyIdentifier]) { + self.threePIDs = SettingsDiscoveryViewModel.threePids(from: thirdPartyIdentifiers) + + // Update view state only if three3pids was previously + guard let viewState = self.viewState, + case let .loaded(displayMode: displayMode) = viewState else { + return + } + + let canDisplayThreePids: Bool + + switch displayMode { + case .threePidsAdded, .noThreePidsAdded: + canDisplayThreePids = true + default: + canDisplayThreePids = false + } + + if canDisplayThreePids { + self.updateViewStateFromCurrentThreePids() + } + } + + // MARK: - Private + + private func checkTerms() { + guard let identityService = self.identityService, let serviceTerms = self.serviceTerms else { + self.update(viewState: .loaded(displayMode: .noIdentityServer)) + return + } + + guard self.canCheckTerms() else { + return + } + + self.update(viewState: .loading) + + serviceTerms.areAllTermsAgreed({ (areAllTermsAgreed) in + if areAllTermsAgreed { + // Display three pids if presents + self.updateViewStateFromCurrentThreePids() + } else { + let identityServer = identityService.identityServer + let identityServerHost = URL(string: identityServer)?.host ?? identityServer + + self.update(viewState: .loaded(displayMode: .termsNotSigned(host: identityServerHost))) + } + }, failure: { (error) in + self.update(viewState: .error(error)) + }) + } + + private func acceptTerms() { + guard let identityService = self.identityService else { + self.update(viewState: .loaded(displayMode: .noIdentityServer)) + return + } + + // Launch an identity server request to trigger terms modal apparition + identityService.account { (response) in + switch response { + case .success: + // Display three pids if presents + self.updateViewStateFromCurrentThreePids() + case .failure(let error): + if MXError(nsError: error)?.errcode == kMXErrCodeStringTermsNotSigned { + // Identity terms modal should appear + } else { + self.update(viewState: .error(error)) + } + } + } + } + + private func canCheckTerms() -> Bool { + guard let viewState = self.viewState else { + return true + } + + let canCheckTerms: Bool + + if case .loading = viewState { + canCheckTerms = false + } else { + canCheckTerms = true + } + + return canCheckTerms + } + + private func updateViewStateFromCurrentThreePids() { + self.updateViewState(with: self.threePIDs) + } + + private func updateViewState(with threePids: [MX3PID]) { + + let viewState: SettingsDiscoveryViewState + + if threePids.isEmpty { + viewState = .loaded(displayMode: .noThreePidsAdded) + } else { + let emails = threePids.compactMap { (threePid) -> MX3PID? in + if case .email = threePid.medium { + return threePid + } else { + return nil + } + } + + let phoneNumbers = threePids.compactMap { (threePid) -> MX3PID? in + if case .msisdn = threePid.medium { + return threePid + } else { + return nil + } + } + + viewState = .loaded(displayMode: .threePidsAdded(emails: emails, phoneNumbers: phoneNumbers)) + } + + self.update(viewState: viewState) + } + + private func update(viewState: SettingsDiscoveryViewState) { + self.viewState = viewState + self.viewDelegate?.settingsDiscoveryViewModel(self, didUpdateViewState: viewState) + } + + private class func threePids(from thirdPartyIdentifiers: [MXThirdPartyIdentifier]) -> [MX3PID] { + return thirdPartyIdentifiers.map({ (thirdPartyIdentifier) -> MX3PID in + return MX3PID(medium: MX3PID.Medium(identifier: thirdPartyIdentifier.medium), address: thirdPartyIdentifier.address) + }) + } +} diff --git a/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModelType.swift b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModelType.swift new file mode 100644 index 000000000..c3ebf4dab --- /dev/null +++ b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewModelType.swift @@ -0,0 +1,35 @@ +/* + Copyright 2019 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 SettingsDiscoveryViewModelViewDelegate: class { + func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModelType, didUpdateViewState viewState: SettingsDiscoveryViewState) +} + +@objc protocol SettingsDiscoveryViewModelCoordinatorDelegate: class { + func settingsDiscoveryViewModel(_ viewModel: SettingsDiscoveryViewModel, didSelectThreePidWith medium: String, and address: String) + func settingsDiscoveryViewModelDidTapUserSettingsLink(_ viewModel: SettingsDiscoveryViewModel) +} + +protocol SettingsDiscoveryViewModelType { + + var viewDelegate: SettingsDiscoveryViewModelViewDelegate? { get set } + + var coordinatorDelegate: SettingsDiscoveryViewModelCoordinatorDelegate? { get set } + + func process(viewAction: SettingsDiscoveryViewAction) +} diff --git a/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewState.swift b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewState.swift new file mode 100644 index 000000000..8afa3581e --- /dev/null +++ b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewState.swift @@ -0,0 +1,31 @@ +/* + Copyright 2019 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 + +/// SettingsDiscoveryTableViewSection view state +enum SettingsDiscoveryViewState { + case loading + case loaded(displayMode: SettingsDiscoveryDisplayMode) + case error(Error) +} + +enum SettingsDiscoveryDisplayMode { + case noIdentityServer + case termsNotSigned(host: String) + case noThreePidsAdded + case threePidsAdded(emails: [MX3PID], phoneNumbers: [MX3PID]) +} From 433d31c0096ab0b8000a27d921ab58219f77fc5b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 10 Sep 2019 20:36:45 +0200 Subject: [PATCH 4/9] Settings discovery: Handle new discovery section. --- ...TermsModalCoordinatorBridgePresenter.swift | 4 + .../Modules/Settings/SettingsViewController.m | 147 +++++++++++++++++- 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinatorBridgePresenter.swift b/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinatorBridgePresenter.swift index 5802b90eb..f3b05f288 100644 --- a/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinatorBridgePresenter.swift +++ b/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinatorBridgePresenter.swift @@ -69,6 +69,10 @@ final class ServiceTermsModalCoordinatorBridgePresenter: NSObject { viewController.present(serviceTermsModalCoordinator.toPresentable(), animated: animated, completion: nil) serviceTermsModalCoordinator.start() + if let coordinator = self.coordinator { + coordinator.toPresentable().dismiss(animated: false, completion: nil) + } + self.coordinator = serviceTermsModalCoordinator } diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 9d356a4d9..d3b7877ea 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -53,6 +53,7 @@ enum SETTINGS_SECTION_USER_SETTINGS_INDEX, SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX, SETTINGS_SECTION_CALLS_INDEX, + SETTINGS_SECTION_DISCOVERY_INDEX, SETTINGS_SECTION_USER_INTERFACE_INDEX, SETTINGS_SECTION_IGNORED_USERS_INDEX, SETTINGS_SECTION_CONTACTS_INDEX, @@ -146,7 +147,8 @@ MXKEncryptionInfoViewDelegate, KeyBackupSetupCoordinatorBridgePresenterDelegate, KeyBackupRecoverCoordinatorBridgePresenterDelegate, SignOutAlertPresenterDelegate, -SingleImagePickerPresenterDelegate> +SingleImagePickerPresenterDelegate, +SettingsDiscoveryTableViewSectionDelegate, SettingsDiscoveryViewModelCoordinatorDelegate> { // Current alert (if any). UIAlertController *currentAlert; @@ -193,6 +195,7 @@ SingleImagePickerPresenterDelegate> NSInteger userSettingsPhoneStartIndex; // The user can have several linked phone numbers. Hence, the dynamic section items count NSInteger userSettingsNewPhoneIndex; // This index also marks the end of the phone numbers list NSInteger userSettingsChangePasswordIndex; + NSInteger userSettingsThreePidsInformation; NSInteger userSettingsNightModeSepIndex; NSInteger userSettingsNightModeIndex; @@ -256,6 +259,10 @@ SingleImagePickerPresenterDelegate> @property (nonatomic, weak) UIButton *signOutButton; @property (nonatomic, strong) SingleImagePickerPresenter *imagePickerPresenter; +@property (nonatomic, strong) SettingsDiscoveryViewModel *settingsDiscoveryViewModel; +@property (nonatomic, strong) SettingsDiscoveryTableViewSection *settingsDiscoveryTableViewSection; +@property (nonatomic, strong) SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter; + @end @implementation SettingsViewController @@ -343,6 +350,8 @@ SingleImagePickerPresenterDelegate> keyBackupSection.delegate = self; } } + + [self setupDiscoverySection]; groupsDataSource = [[GroupsDataSource alloc] initWithMatrixSession:self.mainSession]; [groupsDataSource finalizeInitialization]; @@ -490,6 +499,13 @@ SingleImagePickerPresenterDelegate> newPhoneNumberCountryPicker = nil; } +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + [self.settingsDiscoveryTableViewSection reload]; +} + - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; @@ -962,6 +978,9 @@ SingleImagePickerPresenterDelegate> MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; [account load3PIDs:^{ + NSArray *thirdPartyIdentifiers = account.threePIDs ?: @[]; + [self.settingsDiscoveryViewModel updateWithThirdPartyIdentifiers:thirdPartyIdentifiers]; + // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). // Note: The use of 'reloadData' handles the case where the account has been logged out. [self refreshSettings]; @@ -1202,6 +1221,22 @@ SingleImagePickerPresenterDelegate> } } +- (void)setupDiscoverySection +{ + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + NSArray *thirdPartyIdentifiers = account.threePIDs ?: @[]; + + SettingsDiscoveryViewModel *viewModel = [[SettingsDiscoveryViewModel alloc] initWithSession:self.mainSession thirdPartyIdentifiers:thirdPartyIdentifiers]; + viewModel.coordinatorDelegate = self; + + SettingsDiscoveryTableViewSection *discoverySection = [[SettingsDiscoveryTableViewSection alloc] initWithViewModel:viewModel]; + discoverySection.delegate = self; + + self.settingsDiscoveryViewModel = viewModel; + self.settingsDiscoveryTableViewSection = discoverySection; +} + #pragma mark - Segues - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender @@ -1248,8 +1283,9 @@ SingleImagePickerPresenterDelegate> userSettingsNewEmailIndex = userSettingsEmailStartIndex + account.linkedEmails.count; userSettingsPhoneStartIndex = userSettingsNewEmailIndex + 1; userSettingsNewPhoneIndex = userSettingsPhoneStartIndex + account.linkedPhoneNumbers.count; - - count = userSettingsNewPhoneIndex + 1; + userSettingsThreePidsInformation = userSettingsNewPhoneIndex + 1; + + count = userSettingsThreePidsInformation + 1; } else if (section == SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX) { @@ -1264,6 +1300,10 @@ SingleImagePickerPresenterDelegate> count -= 2; } } + else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) + { + count = self.settingsDiscoveryTableViewSection.numberOfRows; + } else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) { count = USER_INTERFACE_COUNT; @@ -1717,6 +1757,21 @@ SingleImagePickerPresenterDelegate> cell = newPhoneCell; } } + else if (row == userSettingsThreePidsInformation) + { + MXKTableViewCell *threePidsInformationCell = [self getDefaultTableViewCell:self.tableView]; + + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part1", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.textPrimaryColor}]; + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part2", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.tintColor}]]; + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part3", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.textPrimaryColor}]]; + + threePidsInformationCell.textLabel.attributedText = attributedString; + threePidsInformationCell.textLabel.numberOfLines = 0; + + threePidsInformationCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = threePidsInformationCell; + } else if (row == userSettingsChangePasswordIndex) { MXKTableViewCellWithLabelAndTextField *passwordCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; @@ -1870,6 +1925,10 @@ SingleImagePickerPresenterDelegate> cell = globalInfoCell; } } + else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) + { + cell = [self.settingsDiscoveryTableViewSection cellForRowAtRow:row]; + } else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) { if (row == USER_INTERFACE_LANGUAGE_INDEX) @@ -2369,6 +2428,10 @@ SingleImagePickerPresenterDelegate> { return NSLocalizedStringFromTable(@"settings_calls_settings", @"Vector", nil); } + else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) + { + return NSLocalizedStringFromTable(@"settings_discovery_settings", @"Vector", nil); + } else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) { return NSLocalizedStringFromTable(@"settings_user_interface", @"Vector", nil); @@ -2602,6 +2665,15 @@ SingleImagePickerPresenterDelegate> [self showThemePicker]; } } + else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX && row == userSettingsThreePidsInformation) + { + NSIndexPath *discoveryIndexPath = [NSIndexPath indexPathForRow:0 inSection:SETTINGS_SECTION_DISCOVERY_INDEX]; + [tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; + } + else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) + { + [self.settingsDiscoveryTableViewSection selectRow:indexPath.row]; + } else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) { MXSession* session = [AppDelegate theDelegate].mxSessions[0]; @@ -4552,4 +4624,73 @@ SingleImagePickerPresenterDelegate> [self refreshSettings]; } +#pragma mark - SettingsDiscoveryTableViewSectionDelegate + +- (void)settingsDiscoveryTableViewSectionDidUpdate:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection +{ + [self.tableView reloadData]; +} + +- (MXKTableViewCell *)settingsDiscoveryTableViewSection:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection tableViewCellClass:(Class)tableViewCellClass forRow:(NSInteger)forRow +{ + MXKTableViewCell *tableViewCell; + + if ([tableViewCellClass isEqual:[MXKTableViewCell class]]) + { + tableViewCell = [self getDefaultTableViewCell:self.tableView]; + } + else if ([tableViewCellClass isEqual:[MXKTableViewCellWithTextView class]]) + { + tableViewCell = [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:forRow inSection:SETTINGS_SECTION_DISCOVERY_INDEX]]; + } + else if ([tableViewCellClass isEqual:[MXKTableViewCellWithButton class]]) + { + MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + + if (!cell) + { + cell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + cell.mxkButton.titleLabel.text = nil; + } + + cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + [cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + + tableViewCell = cell; + } + else if ([tableViewCellClass isEqual:[MXKTableViewCellWithLabelAndSwitch class]]) + { + tableViewCell = [self getLabelAndSwitchCell:self.tableView forIndexPath:[NSIndexPath indexPathForRow:forRow inSection:SETTINGS_SECTION_DISCOVERY_INDEX]]; + } + + return tableViewCell; +} + +#pragma mark - SettingsDiscoveryViewModelCoordinatorDelegate + +- (void)settingsDiscoveryViewModel:(SettingsDiscoveryViewModel *)viewModel didSelectThreePidWith:(NSString *)medium and:(NSString *)address +{ + SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter = [[SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession medium:medium adress:address]; + + MXWeakify(self); + + [discoveryThreePidDetailsPresenter pushFrom:self.navigationController animated:YES popCompletion:^{ + MXStrongifyAndReturnIfNil(self); + + self.discoveryThreePidDetailsPresenter = nil; + }]; + + self.discoveryThreePidDetailsPresenter = discoveryThreePidDetailsPresenter; +} + +- (void)settingsDiscoveryViewModelDidTapUserSettingsLink:(SettingsDiscoveryViewModel *)viewModel +{ + NSIndexPath *discoveryIndexPath = [NSIndexPath indexPathForRow:userSettingsNewEmailIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX]; + [self.tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; +} + @end From c44d6425d7d0539f4478879ddcc3d9085e16c3c8 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 10 Sep 2019 20:37:25 +0200 Subject: [PATCH 5/9] Update strings --- Riot/Assets/en.lproj/Vector.strings | 21 +++++++++ Riot/Generated/Strings.swift | 72 +++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 30c62bead..2faf8de15 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -360,6 +360,7 @@ "settings_user_settings" = "USER SETTINGS"; "settings_notifications_settings" = "NOTIFICATION SETTINGS"; "settings_calls_settings" = "CALLS"; +"settings_discovery_settings" = "DISCOVERY"; "settings_user_interface" = "USER INTERFACE"; "settings_ignored_users" = "IGNORED USERS"; "settings_contacts" = "LOCAL CONTACTS"; @@ -390,6 +391,9 @@ "settings_change_password" = "Change password"; "settings_night_mode" = "Night Mode"; "settings_fail_to_update_profile" = "Fail to update profile"; +"settings_three_pids_management_information_part1" = "Manage which email addresses or phone numbers you can use to log in or recover your account here. Control who can find you in "; +"settings_three_pids_management_information_part2" = "Discovery"; +"settings_three_pids_management_information_part3" = "."; "settings_enable_push_notif" = "Notifications on this device"; "settings_show_decrypted_content" = "Show decrypted content"; @@ -486,6 +490,23 @@ "settings_devices_description" = "A device's public name is visible to people you communicate with"; +"settings_discovery_no_identity_server" = "You are not currently using an identity server. To be discoverable by existing contacts you known, add one."; +"settings_discovery_terms_not_signed" = "Agree to the Identity Server (%@) Terms of Service to allow yourself to be discoverable by email address or phone number."; +"settings_discovery_three_pids_management_information_part1" = "Manage which email addresses or phone numbers other users can use to discover you and use to invite you to rooms. Add or remove email addresses or phone numbers from this list in "; +"settings_discovery_three_pids_management_information_part2" = "User Settings"; +"settings_discovery_three_pids_management_information_part3" = "."; +"settings_discovery_error_message" = "An error occured. Please retry."; + +"settings_discovery_three_pid_details_title_email" = "Manage email"; +"settings_discovery_three_pid_details_information_email" = "Manage preferences for this email address, which other users can use to discover you and use to invite you to rooms. Add or remove email addresses in Accounts."; +"settings_discovery_three_pid_details_title_phone_number" = "Manage phone number"; +"settings_discovery_three_pid_details_information_phone_number" = "Manage preferences for this phone number, which other users can use to discover you and use to invite you to rooms. Add or remove phone numbers in Accounts."; + +"settings_discovery_three_pid_details_share_action" = "Share"; +"settings_discovery_three_pid_details_revoke_action" = "Revoke"; +"settings_discovery_three_pid_details_cancel_email_validation_action" = "Cancel email validation"; +"settings_discovery_three_pid_details_enter_sms_code_action" = "Enter SMS activation code"; + // Room Details "room_details_title" = "Room Details"; "room_details_people" = "Members"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 9dd57688d..4d06611fb 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2514,6 +2514,66 @@ internal enum VectorL10n { internal static var settingsDevicesDescription: String { return VectorL10n.tr("Vector", "settings_devices_description") } + /// An error occured. Please retry. + internal static var settingsDiscoveryErrorMessage: String { + return VectorL10n.tr("Vector", "settings_discovery_error_message") + } + /// You are not currently using an identity server. To be discoverable by existing contacts you known, add one. + internal static var settingsDiscoveryNoIdentityServer: String { + return VectorL10n.tr("Vector", "settings_discovery_no_identity_server") + } + /// DISCOVERY + internal static var settingsDiscoverySettings: String { + return VectorL10n.tr("Vector", "settings_discovery_settings") + } + /// Agree to the Identity Server (%@) Terms of Service to allow yourself to be discoverable by email address or phone number. + internal static func settingsDiscoveryTermsNotSigned(_ p1: String) -> String { + return VectorL10n.tr("Vector", "settings_discovery_terms_not_signed", p1) + } + /// Cancel email validation + internal static var settingsDiscoveryThreePidDetailsCancelEmailValidationAction: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_cancel_email_validation_action") + } + /// Enter SMS activation code + internal static var settingsDiscoveryThreePidDetailsEnterSmsCodeAction: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_enter_sms_code_action") + } + /// Manage preferences for this email address, which other users can use to discover you and use to invite you to rooms. Add or remove email addresses in Accounts. + internal static var settingsDiscoveryThreePidDetailsInformationEmail: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_information_email") + } + /// Manage preferences for this phone number, which other users can use to discover you and use to invite you to rooms. Add or remove phone numbers in Accounts. + internal static var settingsDiscoveryThreePidDetailsInformationPhoneNumber: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_information_phone_number") + } + /// Revoke + internal static var settingsDiscoveryThreePidDetailsRevokeAction: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_revoke_action") + } + /// Share + internal static var settingsDiscoveryThreePidDetailsShareAction: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_share_action") + } + /// Manage email + internal static var settingsDiscoveryThreePidDetailsTitleEmail: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_title_email") + } + /// Manage phone number + internal static var settingsDiscoveryThreePidDetailsTitlePhoneNumber: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pid_details_title_phone_number") + } + /// Manage which email addresses or phone numbers other users can use to discover you and use to invite you to rooms. Add or remove email addresses or phone numbers from this list in + internal static var settingsDiscoveryThreePidsManagementInformationPart1: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pids_management_information_part1") + } + /// User Settings + internal static var settingsDiscoveryThreePidsManagementInformationPart2: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pids_management_information_part2") + } + /// . + internal static var settingsDiscoveryThreePidsManagementInformationPart3: String { + return VectorL10n.tr("Vector", "settings_discovery_three_pids_management_information_part3") + } /// Display Name internal static var settingsDisplayName: String { return VectorL10n.tr("Vector", "settings_display_name") @@ -2794,6 +2854,18 @@ internal enum VectorL10n { internal static var settingsThirdPartyNotices: String { return VectorL10n.tr("Vector", "settings_third_party_notices") } + /// Manage which email addresses or phone numbers you can use to log in or recover your account here. Control who can find you in + internal static var settingsThreePidsManagementInformationPart1: String { + return VectorL10n.tr("Vector", "settings_three_pids_management_information_part1") + } + /// Discovery + internal static var settingsThreePidsManagementInformationPart2: String { + return VectorL10n.tr("Vector", "settings_three_pids_management_information_part2") + } + /// . + internal static var settingsThreePidsManagementInformationPart3: String { + return VectorL10n.tr("Vector", "settings_three_pids_management_information_part3") + } /// Settings internal static var settingsTitle: String { return VectorL10n.tr("Vector", "settings_title") From e58b385822923a04a1f55466a2820a0d00fa3491 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 10 Sep 2019 20:37:33 +0200 Subject: [PATCH 6/9] Update pbxproj --- Riot.xcodeproj/project.pbxproj | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index fcd8e851b..6653d35eb 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -157,6 +157,11 @@ B110872521F098F0003554A5 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B110872121F098EF003554A5 /* ActivityIndicatorPresenter.swift */; }; B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B110872221F098F0003554A5 /* ActivityIndicatorView.swift */; }; B120863722EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */; }; + B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1A231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift */; }; + B125FE1D231D5DE400B72806 /* SettingsDiscoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1C231D5DE400B72806 /* SettingsDiscoveryViewModel.swift */; }; + B125FE1F231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE1E231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift */; }; + B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */; }; + B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */; }; B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */; }; B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */; }; B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */; }; @@ -178,6 +183,15 @@ B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142D22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift */; }; B152C73122DF561E0041315A /* EmojiServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B152C73022DF561E0041315A /* EmojiServiceTests.swift */; }; B152C73522DF599C0041315A /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = B152C73422DF599B0041315A /* apple_emojis_data.json */; }; + B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9723264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift */; }; + B157FAA023264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9823264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift */; }; + B157FAA123264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9923264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift */; }; + B157FAA223264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9A23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift */; }; + B157FAA323264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9B23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift */; }; + B157FAA423264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B157FA9C23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard */; }; + B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9D23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift */; }; + B157FAA623264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FA9E23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift */; }; + B157FAA823264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B157FAA723264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift */; }; B1664BC520F4E67600808783 /* FallbackViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1664BAD20F4E67500808783 /* FallbackViewController.xib */; }; B1664BC620F4E67600808783 /* FallbackViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1664BAE20F4E67500808783 /* FallbackViewController.m */; }; B1664BC720F4E67600808783 /* SharePresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1664BB220F4E67500808783 /* SharePresentingViewController.m */; }; @@ -787,6 +801,11 @@ B110872121F098EF003554A5 /* ActivityIndicatorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorPresenter.swift; sourceTree = ""; }; B110872221F098F0003554A5 /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = ""; }; B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryBridgeCoordinatorPresenter.swift; sourceTree = ""; }; + B125FE1A231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryTableViewSection.swift; sourceTree = ""; }; + B125FE1C231D5DE400B72806 /* SettingsDiscoveryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewModel.swift; sourceTree = ""; }; + B125FE1E231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewModelType.swift; sourceTree = ""; }; + B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewAction.swift; sourceTree = ""; }; + B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewState.swift; sourceTree = ""; }; B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModel.swift; sourceTree = ""; }; B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModelType.swift; sourceTree = ""; }; B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewAction.swift; sourceTree = ""; }; @@ -809,6 +828,15 @@ B152C72D22DF55D80041315A /* RiotTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RiotTests-Bridging-Header.h"; sourceTree = ""; }; B152C73022DF561E0041315A /* EmojiServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiServiceTests.swift; sourceTree = ""; }; B152C73422DF599B0041315A /* apple_emojis_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apple_emojis_data.json; sourceTree = ""; }; + B157FA9723264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsCoordinator.swift; sourceTree = ""; }; + B157FA9823264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewModelType.swift; sourceTree = ""; }; + B157FA9923264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsCoordinatorType.swift; sourceTree = ""; }; + B157FA9A23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewAction.swift; sourceTree = ""; }; + B157FA9B23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewState.swift; sourceTree = ""; }; + B157FA9C23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SettingsDiscoveryThreePidDetailsViewController.storyboard; sourceTree = ""; }; + B157FA9D23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewModel.swift; sourceTree = ""; }; + B157FA9E23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsViewController.swift; sourceTree = ""; }; + B157FAA723264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift; sourceTree = ""; }; B1664BAD20F4E67500808783 /* FallbackViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FallbackViewController.xib; sourceTree = ""; }; B1664BAE20F4E67500808783 /* FallbackViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FallbackViewController.m; sourceTree = ""; }; B1664BAF20F4E67500808783 /* FallbackViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FallbackViewController.h; sourceTree = ""; }; @@ -1903,6 +1931,19 @@ path = ActivityIndicator; sourceTree = ""; }; + B125FE19231D5B5600B72806 /* Discovery */ = { + isa = PBXGroup; + children = ( + B125FE1A231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift */, + B125FE1E231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift */, + B125FE1C231D5DE400B72806 /* SettingsDiscoveryViewModel.swift */, + B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */, + B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */, + B157FA9623264AE800EBFBD4 /* ThreePidDetails */, + ); + path = Discovery; + sourceTree = ""; + }; B14F142522144F6400FA0595 /* RecoveryKey */ = { isa = PBXGroup; children = ( @@ -1951,6 +1992,22 @@ path = Data; sourceTree = ""; }; + B157FA9623264AE800EBFBD4 /* ThreePidDetails */ = { + isa = PBXGroup; + children = ( + B157FAA723264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift */, + B157FA9923264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift */, + B157FA9723264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift */, + B157FA9823264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift */, + B157FA9D23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift */, + B157FA9A23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift */, + B157FA9B23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift */, + B157FA9E23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift */, + B157FA9C23264AE800EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard */, + ); + path = ThreePidDetails; + sourceTree = ""; + }; B1664BAB20F4E67500808783 /* Modules */ = { isa = PBXGroup; children = ( @@ -2272,6 +2329,7 @@ B1B5567E20EE6C4C00210D55 /* SettingsViewController.m */, B1B5578120EF564900210D55 /* Views */, 32BF994D21FA1C6300698084 /* KeyBackup */, + B125FE19231D5B5600B72806 /* Discovery */, B1B5567B20EE6C4C00210D55 /* Language */, B1B5567820EE6C4C00210D55 /* PhoneCountry */, B1B5568020EE6C4C00210D55 /* DeactivateAccount */, @@ -3858,6 +3916,7 @@ B1B5597020EFA85D00210D55 /* EncryptionInfoView.xib in Resources */, B1B558BC20EF768F00210D55 /* RoomMembershipBubbleCell.xib in Resources */, B1B5571F20EE6C4D00210D55 /* ContactDetailsViewController.xib in Resources */, + B157FAA423264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.storyboard in Resources */, B1B558E220EF768F00210D55 /* RoomIncomingTextMsgBubbleCell.xib in Resources */, B105778B221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard in Resources */, B1664DA220F4F95800808783 /* Localizable.strings in Resources */, @@ -4200,6 +4259,7 @@ 324A2053225FC571004FE8B0 /* DeviceVerificationIncomingViewModel.swift in Sources */, B1B557A120EF58AD00210D55 /* ContactTableViewCell.m in Sources */, F083BE021E7009ED00A9B29C /* AvatarGenerator.m in Sources */, + B157FAA023264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift in Sources */, B1B5573A20EE6C4D00210D55 /* GroupRoomsViewController.m in Sources */, B1B558F920EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.m in Sources */, B1FDF56021F5FE5500BA3834 /* KeyBackupSetupPassphraseViewAction.swift in Sources */, @@ -4218,6 +4278,7 @@ B1B5574B20EE6C4D00210D55 /* MediaAlbumContentViewController.m in Sources */, B1B5598820EFC3E000210D55 /* WidgetManager.m in Sources */, B1DB4F0E22316FFF0065DBFA /* UserNameColorGenerator.swift in Sources */, + B157FAA123264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift in Sources */, B1057789221304EC00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift in Sources */, B1DCC61922E5E17100625807 /* EmojiPickerCoordinatorType.swift in Sources */, B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */, @@ -4262,6 +4323,7 @@ B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */, B1B5572F20EE6C4D00210D55 /* ReadReceiptsViewController.m in Sources */, B1B558CB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */, + B157FAA823264BED00EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter.swift in Sources */, B169330B20F3CA3A00746532 /* Contact.m in Sources */, B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */, B1D4752A21EE52B10067973F /* KeyBackupSetupIntroViewController.swift in Sources */, @@ -4291,6 +4353,7 @@ B1B5571A20EE6C4D00210D55 /* SettingsViewController.m in Sources */, B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */, 32F6B9692270623100BBA352 /* DeviceVerificationDataLoadingCoordinator.swift in Sources */, + B125FE1D231D5DE400B72806 /* SettingsDiscoveryViewModel.swift in Sources */, B1B5594720EF7BD000210D55 /* RoomCollectionViewCell.m in Sources */, B10CFBC32268D99D00A5842E /* JitsiService.swift in Sources */, B1B558C120EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m in Sources */, @@ -4351,6 +4414,7 @@ B1B5582C20EF666100210D55 /* DirectoryRecentTableViewCell.m in Sources */, B1B558E420EF768F00210D55 /* RoomMembershipWithPaginationTitleBubbleCell.m in Sources */, B1B5573620EE6C4D00210D55 /* GroupsViewController.m in Sources */, + B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */, 3232ABB82257BE6500AD6A5C /* DeviceVerificationVerifyCoordinator.swift in Sources */, B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */, B1DCC62622E60CC600625807 /* EmojiItem.swift in Sources */, @@ -4362,7 +4426,9 @@ B1DCC63922E85E9A00625807 /* EmojiMartStore.swift in Sources */, B1B5590620EF768F00210D55 /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.m in Sources */, B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */, + B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift in Sources */, F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */, + B157FAA623264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift in Sources */, B1DCC62422E60CA900625807 /* EmojiPickerCategoryViewData.swift in Sources */, 324A2056225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift in Sources */, B16932F720F3C50E00746532 /* RecentsDataSource.m in Sources */, @@ -4401,6 +4467,8 @@ B1B557DE20EF5FBB00210D55 /* FilesSearchTableViewCell.m in Sources */, B1B5574020EE6C4D00210D55 /* SegmentedViewController.m in Sources */, B1B5599320EFC5E400210D55 /* DecryptionFailure.m in Sources */, + B125FE1F231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift in Sources */, + B157FAA323264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift in Sources */, B1098BF921ECFE65000DDA48 /* KeyBackupSetupCoordinator.swift in Sources */, B140B4A821F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift in Sources */, B1098BFE21ECFE65000DDA48 /* KeyBackupSetupPassphraseViewModelType.swift in Sources */, @@ -4475,6 +4543,7 @@ B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, B1B558F020EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */, 926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */, + B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */, B1B5593820EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.m in Sources */, B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */, 3232AB502256558300AD6A5C /* TemplateScreenViewState.swift in Sources */, @@ -4491,6 +4560,7 @@ B1098BFF21ECFE65000DDA48 /* PasswordStrengthView.swift in Sources */, B1B558D220EF768F00210D55 /* RoomEncryptedDataBubbleCell.m in Sources */, B1B558FA20EF768F00210D55 /* RoomMembershipBubbleCell.m in Sources */, + B157FAA223264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift in Sources */, 3232ABA1225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift in Sources */, B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */, B1C562E1228C7C8C0037F12A /* RoomContextualMenuToolbarView.swift in Sources */, @@ -4551,6 +4621,7 @@ 3232AB482256558300AD6A5C /* FlowTemplateCoordinatorType.swift in Sources */, B1B9DEF122EB396B0065E677 /* ReactionHistoryViewData.swift in Sources */, B1B558F820EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, + B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift in Sources */, 32242F0921E8B05F00725742 /* UIColor.swift in Sources */, B16932E720F3C37100746532 /* HomeMessagesSearchDataSource.m in Sources */, B1B558CE20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.m in Sources */, @@ -4564,6 +4635,7 @@ B1B9DEEE22EB34EF0065E677 /* ReactionHistoryViewAction.swift in Sources */, 32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */, B1B9DEEC22EB34EF0065E677 /* ReactionHistoryViewModelType.swift in Sources */, + B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */, B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */, B14F143022144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift in Sources */, B1E5368921FB1E20001F3AFF /* UIButton.swift in Sources */, From 12be54d89135f779755a73acedaeb2e8218099ed Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 10 Sep 2019 20:42:11 +0200 Subject: [PATCH 7/9] Update changes --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 251f90d5f..6c03f208c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -19,6 +19,7 @@ Improvements: * Privacy: Settings: Allow adding 3pids when no IS (#2659). * Privacy: Allow password reset when no IS (#2658). * Privacy: Allow email registration when no IS (#2657). + * Settings: Add a Discovery section (#2606). Changes in 0.9.2 (2019-08-08) =============================================== From 494036ef2144ddc6a74273cbf20e58263209adcf Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 11 Sep 2019 15:39:05 +0200 Subject: [PATCH 8/9] Update CHANGES.rst Co-Authored-By: manuroe --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6c03f208c..ebf9c7a1f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -19,7 +19,7 @@ Improvements: * Privacy: Settings: Allow adding 3pids when no IS (#2659). * Privacy: Allow password reset when no IS (#2658). * Privacy: Allow email registration when no IS (#2657). - * Settings: Add a Discovery section (#2606). + * Privacy: Settings: Add a Discovery section (#2606). Changes in 0.9.2 (2019-08-08) =============================================== From 982d225f9dd3687a1b5ec422567389a2c28db0b8 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 11 Sep 2019 16:27:32 +0200 Subject: [PATCH 9/9] Settings discovery: In three pid details screen, display a pending verification alert on email validation. Use a fallback API mechanism in view model for older homeservers that do no support 3pid rebind. --- .../ActivityIndicatorPresenter.swift | 4 + .../SettingsDiscoveryViewState.swift | 6 + ...gsDiscoveryThreePidDetailsViewAction.swift | 1 + ...scoveryThreePidDetailsViewController.swift | 61 +++++-- ...ngsDiscoveryThreePidDetailsViewModel.swift | 151 +++++++++++------- ...ngsDiscoveryThreePidDetailsViewState.swift | 2 +- 6 files changed, 156 insertions(+), 69 deletions(-) diff --git a/Riot/Modules/Common/ActivityIndicator/ActivityIndicatorPresenter.swift b/Riot/Modules/Common/ActivityIndicator/ActivityIndicatorPresenter.swift index 901d52bc6..f33509dcc 100755 --- a/Riot/Modules/Common/ActivityIndicator/ActivityIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityIndicator/ActivityIndicatorPresenter.swift @@ -34,6 +34,10 @@ final class ActivityIndicatorPresenter: ActivityIndicatorPresenterType { private weak var activityIndicatorView: ActivityIndicatorView? private weak var presentingView: UIView? + var isPresenting: Bool { + return self.activityIndicatorView != nil + } + // MARK: - Public func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)? = nil) { diff --git a/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewState.swift b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewState.swift index 8afa3581e..2c765280c 100644 --- a/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewState.swift +++ b/Riot/Modules/Settings/Discovery/SettingsDiscoveryViewState.swift @@ -23,6 +23,12 @@ enum SettingsDiscoveryViewState { case error(Error) } +/// SettingsDiscoveryTableViewSection `loaded` view state dipslay modes +/// +/// - noIdentityServer: No identity server configured. +/// - termsNotSigned: Identity server terms are not signed. +/// - noThreePidsAdded: No three pids added to the user HS account. +/// - threePidsAdded: Three pids added to the user HS account. enum SettingsDiscoveryDisplayMode { case noIdentityServer case termsNotSigned(host: String) diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewAction.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewAction.swift index a6d366651..81af3c303 100644 --- a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewAction.swift +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewAction.swift @@ -24,5 +24,6 @@ enum SettingsDiscoveryThreePidDetailsViewAction { case share case revoke case cancelEmailValidation + case confirmEmailValidation case enterSMSCode(_ code: String) } diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.swift index 053d4ae55..5ba5ccb78 100644 --- a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.swift +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewController.swift @@ -39,6 +39,7 @@ final class SettingsDiscoveryThreePidDetailsViewController: UIViewController { private var keyboardAvoider: KeyboardAvoider? private var errorPresenter: MXKErrorPresentation! private var activityPresenter: ActivityIndicatorPresenter! + private weak var presentedAlertController: UIAlertController? private var displayMode: SettingsDiscoveryThreePidDetailsDisplayMode? @@ -163,33 +164,50 @@ final class SettingsDiscoveryThreePidDetailsViewController: UIViewController { private func renderLoaded(displayMode: SettingsDiscoveryThreePidDetailsDisplayMode) { self.activityPresenter.removeCurrentActivityIndicator(animated: true) - let operationButtonTitle: String - let operationButtonColor: UIColor + let operationButtonTitle: String? + let operationButtonColor: UIColor? + let operationButtonEnabled: Bool + + self.presentedAlertController?.dismiss(animated: false, completion: nil) switch displayMode { case .share: operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsShareAction operationButtonColor = self.theme.tintColor + operationButtonEnabled = true case .revoke: operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsRevokeAction operationButtonColor = self.theme.warningColor - case .cancelEmailValidation: - operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsCancelEmailValidationAction - operationButtonColor = self.theme.warningColor + operationButtonEnabled = true + case .pendingEmailVerification: + self.presentPendingEmailVerificationAlert() + + operationButtonTitle = nil + operationButtonColor = nil + operationButtonEnabled = false case .enterSMSCode: operationButtonTitle = VectorL10n.settingsDiscoveryThreePidDetailsEnterSmsCodeAction operationButtonColor = self.theme.tintColor + operationButtonEnabled = true } - self.operationButton.setTitle(operationButtonTitle, for: .normal) - self.operationButton.setTitleColor(operationButtonColor, for: .normal) - self.operationButton.isEnabled = true + if let operationButtonTitle = operationButtonTitle { + self.operationButton.setTitle(operationButtonTitle, for: .normal) + } + + if let operationButtonColor = operationButtonColor { + self.operationButton.setTitleColor(operationButtonColor, for: .normal) + } + + self.operationButton.isEnabled = operationButtonEnabled self.displayMode = displayMode } private func renderLoading() { - self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + if self.activityPresenter.isPresenting == false { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } self.operationButton.isEnabled = false } @@ -199,6 +217,24 @@ final class SettingsDiscoveryThreePidDetailsViewController: UIViewController { self.operationButton.isEnabled = true } + private func presentPendingEmailVerificationAlert() { + + let alert = UIAlertController(title: Bundle.mxk_localizedString(forKey: "account_email_validation_title"), + message: Bundle.mxk_localizedString(forKey: "account_email_validation_message"), + preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: VectorL10n.continue, style: .default, handler: { _ in + self.viewModel.process(viewAction: .confirmEmailValidation) + })) + + alert.addAction(UIAlertAction(title: Bundle.mxk_localizedString(forKey: "abort"), style: .cancel, handler: { _ in + self.viewModel.process(viewAction: .cancelEmailValidation) + })) + + self.present(alert, animated: true, completion: nil) + self.presentedAlertController = alert + } + private func presentSMSActivationCodeAlert() { let alert = UIAlertController(title: VectorL10n.authMsisdnValidationTitle, @@ -210,7 +246,7 @@ final class SettingsDiscoveryThreePidDetailsViewController: UIViewController { textField.keyboardType = .phonePad } - alert.addAction(UIAlertAction(title: VectorL10n.authSubmit, style: .destructive, handler: { _ in + alert.addAction(UIAlertAction(title: VectorL10n.authSubmit, style: .default, handler: { _ in guard let textField = alert.textFields?.first, let smsCode = textField.text, smsCode.isEmpty == false else { return } @@ -220,6 +256,7 @@ final class SettingsDiscoveryThreePidDetailsViewController: UIViewController { alert.addAction(UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: nil)) self.present(alert, animated: true, completion: nil) + self.presentedAlertController = alert } // MARK: - Actions @@ -236,11 +273,11 @@ final class SettingsDiscoveryThreePidDetailsViewController: UIViewController { viewAction = .share case .revoke: viewAction = .revoke - case .cancelEmailValidation: - viewAction = .cancelEmailValidation case .enterSMSCode: viewAction = nil self.presentSMSActivationCodeAlert() + default: + viewAction = nil } if let viewAction = viewAction { diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift index 86a76f680..2dd108384 100644 --- a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift @@ -37,7 +37,6 @@ final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePid private let session: MXSession private var viewState: SettingsDiscoveryThreePidDetailsViewState? private var currentThreePidRequestTokenInfo: ThreePidRequestTokenInfo? - private var doesHomeserverSupport3PidRebind: Bool = false // MARK: Public @@ -64,6 +63,8 @@ final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePid self.revoke() case .cancelEmailValidation: self.cancelEmailValidation() + case .confirmEmailValidation: + self.confirmEmailValidation() case .enterSMSCode(let code): self.validatePhoneNumber(with: code) } @@ -93,62 +94,75 @@ final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePid } private func share() { - self.requestBinding(for: self.threePid, bind: true) + self.requestToken(for: self.threePid, bind: true) } private func revoke() { - self.requestBinding(for: self.threePid, bind: false) + self.requestToken(for: self.threePid, bind: false) } - private func requestBinding(for threePid: MX3PID, bind: Bool) { + private func requestToken(for threePid: MX3PID, bind: Bool, useOlderHomeserver: Bool = false) { guard let restClient = self.session.matrixRestClient, let clientSecret = MXTools.generateSecret() else { return } + let requestThreePidToken: (() -> Void) = { + switch self.threePid.medium { + case .email: + restClient.requestToken(forEmail: threePid.address, isDuringRegistration: false, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid) in + + if let sid = sid { + self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind) + self.update(viewState: .loaded(displayMode: .pendingEmailVerification)) + self.registerEmailValidationNotification() + } else { + self.update(viewState: .error(SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + } + + }, failure: { error in + if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDInUse, useOlderHomeserver == false { + self.requestToken(for: threePid, bind: bind, useOlderHomeserver: true) + } else { + self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + } + }) + case .msisdn: + let formattedPhoneNumber = self.formattedPhoneNumber(from: threePid.address) + restClient.requestToken(forPhoneNumber: formattedPhoneNumber, isDuringRegistration: false, countryCode: nil, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid, msisdn) in + + if let sid = sid { + self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind) + self.update(viewState: .loaded(displayMode: .enterSMSCode)) + } else { + self.update(viewState: .error(SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + } + + }, failure: { error in + if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDInUse, useOlderHomeserver == false { + self.requestToken(for: threePid, bind: bind, useOlderHomeserver: true) + } else { + self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown)) + } + }) + default: + break + } + } + self.update(viewState: .loading) - if self.doesHomeserverSupport3PidRebind == false { + if useOlderHomeserver { restClient.remove3PID(address: threePid.address, medium: threePid.medium.identifier) { (response) in switch response { case .success: - switch self.threePid.medium { - case .email: - restClient.requestToken(forEmail: threePid.address, isDuringRegistration: false, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid) in - - if let sid = sid { - self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind) - self.update(viewState: .loaded(displayMode: .cancelEmailValidation)) - } else { - self.update(viewState: .error( SettingsDiscoveryThreePidDetailsViewModelError.unknown)) - } - - }, failure: { error in - self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown)) - }) - case .msisdn: - let formattedPhoneNumber = self.formattedPhoneNumber(from: threePid.address) - restClient.requestToken(forPhoneNumber: formattedPhoneNumber, isDuringRegistration: false, countryCode: nil, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid, msisdn) in - - if let sid = sid { - self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind) - self.update(viewState: .loaded(displayMode: .enterSMSCode)) - } else { - self.update(viewState: .error( SettingsDiscoveryThreePidDetailsViewModelError.unknown)) - } - - }, failure: { error in - self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown)) - }) - default: - break - } + requestThreePidToken() case .failure(let error): self.update(viewState: .error(error)) } } } else { - // TODO: Handle rebind API + requestThreePidToken() } } @@ -173,17 +187,59 @@ final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePid self.viewDelegate?.settingsDiscoveryThreePidDetailsViewModel(self, didUpdateViewState: viewState) } + private func bindThreePid(_ threePid: MX3PID, threePidRequestTokenInfo: ThreePidRequestTokenInfo) { + guard let restClient = self.session.matrixRestClient else { + return + } + + self.update(viewState: .loading) + + restClient.addThirdPartyIdentifier(threePidRequestTokenInfo.sid, clientSecret: threePidRequestTokenInfo.clientSecret, bind: threePidRequestTokenInfo.bind) { response in + switch response { + case .success: + + if case .email = threePid.medium { + self.unregisterEmailValidationNotification() + } + + self.checkThreePidDiscoverability() + case .failure(let error): + if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDAuthFailed { + self.update(viewState: .loaded(displayMode: .pendingEmailVerification)) + } else { + if case .email = threePid.medium { + self.unregisterEmailValidationNotification() + } + + self.update(viewState: .error(error)) + } + } + } + } + // MARK: Email private func cancelEmailValidation() { + self.unregisterEmailValidationNotification() self.currentThreePidRequestTokenInfo = nil self.checkThreePidDiscoverability() } + private func confirmEmailValidation() { + guard let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo else { + return + } + self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo) + } + private func registerEmailValidationNotification() { NotificationCenter.default.addObserver(self, selector: #selector(handleEmailValidationNotification(notification:)), name: .AppDelegateDidValidateEmail, object: nil) } + private func unregisterEmailValidationNotification() { + NotificationCenter.default.removeObserver(self, name: .AppDelegateDidValidateEmail, object: nil) + } + @objc private func handleEmailValidationNotification(notification: Notification) { guard let userInfo = notification.userInfo, let clientSecret = userInfo[AppDelegateDidValidateEmailNotificationClientSecretKey] as? String, @@ -194,24 +250,7 @@ final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePid return } - self.validateThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo) - } - - private func validateThreePid(_ threePid: MX3PID, threePidRequestTokenInfo: ThreePidRequestTokenInfo) { - guard let restClient = self.session.matrixRestClient else { - return - } - - self.update(viewState: .loading) - - restClient.addThirdPartyIdentifier(threePidRequestTokenInfo.sid, clientSecret: threePidRequestTokenInfo.clientSecret, bind: threePidRequestTokenInfo.bind) { response in - switch response { - case .success: - self.checkThreePidDiscoverability() - case .failure(let error): - self.update(viewState: .error(error)) - } - } + self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo) } // MARK: Phone number @@ -231,7 +270,7 @@ final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePid identityService.submit3PIDValidationToken(activationCode, medium: MX3PID.Medium.msisdn.identifier, clientSecret: threePidRequestTokenInfo.clientSecret, sid: threePidRequestTokenInfo.sid) { (response) in switch response { case .success: - self.checkThreePidDiscoverability() + self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo) case .failure(let error): self.update(viewState: .error(error)) } diff --git a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewState.swift b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewState.swift index bc2182e52..71687c7b8 100644 --- a/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewState.swift +++ b/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewState.swift @@ -28,6 +28,6 @@ enum SettingsDiscoveryThreePidDetailsViewState { enum SettingsDiscoveryThreePidDetailsDisplayMode { case share case revoke - case cancelEmailValidation + case pendingEmailVerification case enterSMSCode }