mirror of
https://github.com/ProtonMail/protoncore_ios.git
synced 2026-01-16 23:00:24 +00:00
task: CP-8314 [iOS] Change T&C URL for Wallet in sign-up
Refs: https://gitlab.protontech.ch/apple/shared/protoncore/-/merge_requests/1856 Changelog: changed
This commit is contained in:
commit
aa90f83548
14 changed files with 128 additions and 51 deletions
|
|
@ -542,10 +542,10 @@
|
|||
</objects>
|
||||
<point key="canvasLocation" x="2473.913043478261" y="-618.08035714285711"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<!--External Link View Controller-->
|
||||
<scene sceneID="dI1-bc-aG0">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="TC" id="mgS-ty-qip" customClass="TCViewController" customModule="ProtonCoreLoginUI" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="ExternalLink" id="mgS-ty-qip" customClass="ExternalLinkViewController" customModule="ProtonCoreLoginUI" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="6g4-vM-jwm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ final class Container {
|
|||
}
|
||||
|
||||
func makePasswordViewModel() -> PasswordViewModel {
|
||||
return PasswordViewModel()
|
||||
return PasswordViewModel(clientApp: clientApp)
|
||||
}
|
||||
|
||||
func makeRecoveryViewModel(initialCountryCode: Int) -> RecoveryViewModel {
|
||||
|
|
|
|||
|
|
@ -283,11 +283,27 @@ final class SignupCoordinator {
|
|||
}
|
||||
|
||||
private func showTermsAndConditionsViewController() {
|
||||
let tcViewController = UIStoryboard.instantiateInSignup(TCViewController.self, inAppTheme: customization.inAppTheme)
|
||||
tcViewController.termsAndConditionsURL = externalLinks.termsAndConditions
|
||||
tcViewController.delegate = self
|
||||
let elViewController = UIStoryboard.instantiateInSignup(ExternalLinkViewController.self, inAppTheme: customization.inAppTheme)
|
||||
elViewController.configuration = .init(
|
||||
title: LUITranslation.terms_conditions_view_title.l10n,
|
||||
url: externalLinks.termsAndConditions
|
||||
)
|
||||
elViewController.delegate = self
|
||||
|
||||
let navigationVC = LoginNavigationViewController(rootViewController: tcViewController)
|
||||
let navigationVC = LoginNavigationViewController(rootViewController: elViewController)
|
||||
navigationVC.modalPresentationStyle = .pageSheet
|
||||
navigationController?.present(navigationVC, animated: true)
|
||||
}
|
||||
|
||||
private func showPrivacyPolicyViewController() {
|
||||
let elViewController = UIStoryboard.instantiateInSignup(ExternalLinkViewController.self, inAppTheme: customization.inAppTheme)
|
||||
elViewController.configuration = .init(
|
||||
title: LUITranslation.privacy_policy_view_title.l10n,
|
||||
url: externalLinks.privacyPolicy
|
||||
)
|
||||
elViewController.delegate = self
|
||||
|
||||
let navigationVC = LoginNavigationViewController(rootViewController: elViewController)
|
||||
navigationVC.modalPresentationStyle = .pageSheet
|
||||
navigationController?.present(navigationVC, animated: true)
|
||||
}
|
||||
|
|
@ -523,6 +539,10 @@ extension SignupCoordinator: PasswordViewControllerDelegate {
|
|||
func termsAndConditionsLinkPressed() {
|
||||
showTermsAndConditionsViewController()
|
||||
}
|
||||
|
||||
func privacyPolicyLinkPressed() {
|
||||
showPrivacyPolicyViewController()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: RecoveryViewControllerDelegate
|
||||
|
|
@ -669,8 +689,8 @@ extension SignupCoordinator: CompleteViewControllerDelegate {
|
|||
|
||||
// MARK: TCViewControllerDelegate
|
||||
|
||||
extension SignupCoordinator: TCViewControllerDelegate {
|
||||
func termsAndConditionsClose() {
|
||||
extension SignupCoordinator: ExternalLinkViewControllerDelegate {
|
||||
func externalLinkViewControllerClose() {
|
||||
navigationController?.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,16 @@ final class ExternalLinks {
|
|||
}
|
||||
|
||||
var termsAndConditions: URL {
|
||||
return URL(string: "https://proton.me/legal/terms-ios")!
|
||||
switch clientApp {
|
||||
case .wallet:
|
||||
return URL(string: "https://proton.me/leqal/wallet/terms")!
|
||||
default:
|
||||
return URL(string: "https://proton.me/legal/terms-ios")!
|
||||
}
|
||||
}
|
||||
|
||||
var privacyPolicy: URL {
|
||||
return URL(string: "https://proton.me/wallet/privacy-policy")!
|
||||
}
|
||||
|
||||
var support: URL {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ public enum LUITranslation: TranslationsExposing {
|
|||
case password_view_title
|
||||
case password_field_minimum_length_hint
|
||||
case repeat_password_field_title
|
||||
case password_t_c_desc
|
||||
case password_t_c_link
|
||||
case password_t_c_wallet_desc
|
||||
case password_p_p_link
|
||||
case domains_sheet_title
|
||||
case recovery_view_title
|
||||
case recovery_view_title_optional
|
||||
|
|
@ -111,8 +115,6 @@ public enum LUITranslation: TranslationsExposing {
|
|||
case recovery_seg_phone
|
||||
case recovery_email_field_title
|
||||
case recovery_phone_field_title
|
||||
case recovery_t_c_desc
|
||||
case recovery_t_c_link
|
||||
case skip_button
|
||||
case recovery_skip_title
|
||||
case recovery_skip_desc
|
||||
|
|
@ -129,6 +131,7 @@ public enum LUITranslation: TranslationsExposing {
|
|||
case email_verification_code_desc
|
||||
case did_not_receive_code_button
|
||||
case terms_conditions_view_title
|
||||
case privacy_policy_view_title
|
||||
case error_invalid_token_request
|
||||
case error_invalid_token
|
||||
case error_create_user_failed
|
||||
|
|
@ -290,6 +293,14 @@ public enum LUITranslation: TranslationsExposing {
|
|||
return localized(key: "Password must contain at least 8 characters", comment: "Password field hint about minimum length")
|
||||
case .repeat_password_field_title:
|
||||
return localized(key: "Repeat password", comment: "Repeat password field title")
|
||||
case .password_t_c_desc:
|
||||
return localized(key: "By clicking Next, you agree with Proton's Terms and Conditions", comment: "Password terms and conditions description")
|
||||
case .password_t_c_link:
|
||||
return localized(key: "Terms and Conditions", comment: "Password terms and conditions link")
|
||||
case .password_t_c_wallet_desc:
|
||||
return localized(key: "By clicking Next, you agree with Proton's Terms and Conditions and Privacy Policy", comment: "Password terms and conditions description")
|
||||
case .password_p_p_link:
|
||||
return localized(key: "Privacy Policy", comment: "Password privacy policy link")
|
||||
case .domains_sheet_title:
|
||||
return localized(key: "Domain", comment: "Title of domains bottom action sheet")
|
||||
case .recovery_view_title:
|
||||
|
|
@ -306,10 +317,6 @@ public enum LUITranslation: TranslationsExposing {
|
|||
return localized(key: "Recovery email", comment: "Recovery email field title")
|
||||
case .recovery_phone_field_title:
|
||||
return localized(key: "Recovery phone number", comment: "Recovery phone field title")
|
||||
case .recovery_t_c_desc:
|
||||
return localized(key: "By clicking Next, you agree with Proton's Terms and Conditions", comment: "Recovery terms and conditions description")
|
||||
case .recovery_t_c_link:
|
||||
return localized(key: "Terms and Conditions", comment: "Recovery terms and conditions link")
|
||||
case .skip_button:
|
||||
return localized(key: "Skip", comment: "Skip button")
|
||||
case .recovery_skip_title:
|
||||
|
|
@ -342,6 +349,8 @@ public enum LUITranslation: TranslationsExposing {
|
|||
return localized(key: "Did not receive a code?", comment: "Did not receive code button")
|
||||
case .terms_conditions_view_title:
|
||||
return localized(key: "Terms and Conditions", comment: "Terms and conditions view title")
|
||||
case .privacy_policy_view_title:
|
||||
return localized(key: "Privacy Policy", comment: "Privacy policy view title")
|
||||
case .error_invalid_token_request:
|
||||
return localized(key: "Invalid token request", comment: "Invalid token request error")
|
||||
case .error_invalid_token:
|
||||
|
|
|
|||
|
|
@ -144,8 +144,12 @@
|
|||
|
||||
"By clicking Next, you agree with Proton's Terms and Conditions" = "By clicking Next, you agree with Proton's Terms and Conditions";
|
||||
|
||||
"By clicking Next, you agree with Proton's Terms and Conditions and Privacy Policy" = "By clicking Next, you agree with Proton's Terms and Conditions and Privacy Policy";
|
||||
|
||||
"Terms and Conditions" = "Terms and Conditions";
|
||||
|
||||
"Privacy Policy" = "Privacy Policy";
|
||||
|
||||
"Skip" = "Skip";
|
||||
|
||||
"Skip recovery method?" = "Skip recovery method?";
|
||||
|
|
@ -178,6 +182,8 @@
|
|||
|
||||
"Terms and Conditions" = "Terms and Conditions";
|
||||
|
||||
"Privacy Policy" = "Privacy Policy";
|
||||
|
||||
"Invalid token request" = "Invalid token request";
|
||||
|
||||
"Invalid token error" = "Invalid token error";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// TCViewController.swift
|
||||
// ExternalLinkViewController.swift
|
||||
// ProtonCore-Login - Created on 11/03/2021.
|
||||
//
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
|
|
@ -26,14 +26,19 @@ import WebKit
|
|||
import ProtonCoreFoundations
|
||||
import ProtonCoreUIFoundations
|
||||
|
||||
protocol TCViewControllerDelegate: AnyObject {
|
||||
func termsAndConditionsClose()
|
||||
protocol ExternalLinkViewControllerDelegate: AnyObject {
|
||||
func externalLinkViewControllerClose()
|
||||
}
|
||||
|
||||
class TCViewController: UIViewController, AccessibleView {
|
||||
struct ExternalLinkConfiguration {
|
||||
let title: String
|
||||
let url: URL
|
||||
}
|
||||
|
||||
weak var delegate: TCViewControllerDelegate?
|
||||
var termsAndConditionsURL: URL?
|
||||
class ExternalLinkViewController: UIViewController, AccessibleView {
|
||||
|
||||
weak var delegate: ExternalLinkViewControllerDelegate?
|
||||
var configuration: ExternalLinkConfiguration?
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle { darkModeAwarePreferredStatusBarStyle() }
|
||||
|
||||
|
|
@ -46,9 +51,9 @@ class TCViewController: UIViewController, AccessibleView {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = ColorProvider.BackgroundNorm
|
||||
navigationItem.title = LUITranslation.terms_conditions_view_title.l10n
|
||||
navigationItem.title = configuration?.title
|
||||
navigationController?.navigationBar.tintColor = ColorProvider.IconNorm
|
||||
setUpCloseButton(showCloseButton: true, action: #selector(TCViewController.onCloseButtonTap(_:)))
|
||||
setUpCloseButton(showCloseButton: true, action: #selector(ExternalLinkViewController.onCloseButtonTap(_:)))
|
||||
setupWebView()
|
||||
generateAccessibilityIdentifiers()
|
||||
updateTitleAttributes()
|
||||
|
|
@ -57,20 +62,20 @@ class TCViewController: UIViewController, AccessibleView {
|
|||
// MARK: Actions
|
||||
|
||||
@objc func onCloseButtonTap(_ sender: UIButton) {
|
||||
delegate?.termsAndConditionsClose()
|
||||
delegate?.externalLinkViewControllerClose()
|
||||
}
|
||||
|
||||
// MARK: Private methods
|
||||
|
||||
func setupWebView() {
|
||||
webView.navigationDelegate = self
|
||||
guard let url = self.termsAndConditionsURL else { return }
|
||||
guard let url = configuration?.url else { return }
|
||||
let request = URLRequest(url: url, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 20.0)
|
||||
webView.load(request)
|
||||
}
|
||||
}
|
||||
|
||||
extension TCViewController: WKNavigationDelegate {
|
||||
extension ExternalLinkViewController: WKNavigationDelegate {
|
||||
func webView(_ webView: WKWebView,
|
||||
decidePolicyFor navigationAction: WKNavigationAction,
|
||||
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
|
|
@ -80,7 +85,7 @@ extension TCViewController: WKNavigationDelegate {
|
|||
}
|
||||
|
||||
// promise webview won't navigate to other link
|
||||
if url == termsAndConditionsURL?.absoluteString {
|
||||
if url == configuration?.url.absoluteString {
|
||||
decisionHandler(.allow)
|
||||
} else {
|
||||
decisionHandler(.cancel)
|
||||
|
|
@ -26,12 +26,14 @@ import ProtonCoreFoundations
|
|||
import ProtonCoreUIFoundations
|
||||
import ProtonCoreObservability
|
||||
import ProtonCoreTelemetry
|
||||
import ProtonCoreUtilities
|
||||
|
||||
protocol PasswordViewControllerDelegate: AnyObject {
|
||||
func passwordIsShown()
|
||||
func validatedPassword(password: String, completionHandler: (() -> Void)?)
|
||||
func passwordBackButtonPressed()
|
||||
func termsAndConditionsLinkPressed()
|
||||
func privacyPolicyLinkPressed()
|
||||
}
|
||||
|
||||
class PasswordViewController: UIViewController, AccessibleView, Focusable, ProductMetricsMeasurable {
|
||||
|
|
@ -284,8 +286,20 @@ extension PasswordViewController: SignUpErrorCapable, LoginErrorCapable {
|
|||
|
||||
extension PasswordViewController: UITextViewDelegate {
|
||||
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||
delegate?.termsAndConditionsLinkPressed()
|
||||
measureOnViewClicked(item: "terms")
|
||||
guard let stringRange = Range(characterRange, in: textView.text) else {
|
||||
return false
|
||||
}
|
||||
switch textView.text[stringRange] {
|
||||
case LUITranslation.password_t_c_link.l10n:
|
||||
delegate?.termsAndConditionsLinkPressed()
|
||||
measureOnViewClicked(item: "terms")
|
||||
case LUITranslation.password_p_p_link.l10n:
|
||||
delegate?.privacyPolicyLinkPressed()
|
||||
measureOnViewClicked(item: "privacy_policy")
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,15 @@
|
|||
|
||||
import Foundation
|
||||
import ProtonCoreLogin
|
||||
import ProtonCoreDataModel
|
||||
import UIKit
|
||||
|
||||
class PasswordViewModel {
|
||||
let clientApp: ClientApp
|
||||
|
||||
init(clientApp: ClientApp) {
|
||||
self.clientApp = clientApp
|
||||
}
|
||||
|
||||
func passwordValidationResult(for restrictions: SignupPasswordRestrictions,
|
||||
password: String,
|
||||
|
|
@ -57,18 +63,25 @@ class PasswordViewModel {
|
|||
}
|
||||
|
||||
func termsAttributedString(textView: UITextView) -> NSAttributedString {
|
||||
/// Fix me poissble bug: if _login_recovery_t_c_desc translated string doesnt match in _login_recovery_t_c_link translated string. the hyper link could be failed when clicking.
|
||||
var text = LUITranslation.recovery_t_c_desc.l10n
|
||||
let linkText = LUITranslation.recovery_t_c_link.l10n
|
||||
if ProcessInfo.processInfo.arguments.contains("RunningInUITests") {
|
||||
// Workaround for UI test automation to detect link in separated line
|
||||
let texts = text.components(separatedBy: linkText)
|
||||
if texts.count >= 2 {
|
||||
text = texts[0] + "\n" + linkText + texts[1]
|
||||
switch clientApp {
|
||||
case .wallet:
|
||||
let text = NSMutableAttributedString(string: LUITranslation.password_t_c_wallet_desc.l10n)
|
||||
text.addHyperLink(subString: LUITranslation.password_t_c_link.l10n, link: "", font: textView.font)
|
||||
text.addHyperLink(subString: LUITranslation.password_p_p_link.l10n, link: "", font: textView.font)
|
||||
return text
|
||||
default:
|
||||
var text = LUITranslation.password_t_c_desc.l10n
|
||||
let linkText = LUITranslation.password_t_c_link.l10n
|
||||
if ProcessInfo.processInfo.arguments.contains("RunningInUITests") {
|
||||
// Workaround for UI test automation to detect link in separated line
|
||||
let texts = text.components(separatedBy: linkText)
|
||||
if texts.count >= 2 {
|
||||
text = texts[0] + "\n" + linkText + texts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .hyperlink(in: text, as: linkText, path: "", subfont: textView.font)
|
||||
return .hyperlink(in: text, as: linkText, path: "", subfont: textView.font)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class SignupScreenLoadObservabilityTests: SnapshotTestCase {
|
|||
let passwordViewController = UIStoryboard.instantiate(storyboardName: "PMSignup",
|
||||
controllerType: PasswordViewController.self,
|
||||
inAppTheme: { .default })
|
||||
passwordViewController.viewModel = PasswordViewModel()
|
||||
passwordViewController.viewModel = PasswordViewModel(clientApp: .other(named: "core-unit-tests"))
|
||||
_ = passwordViewController.view
|
||||
XCTAssertTrue(stub.reportStub.wasCalledExactlyOnce)
|
||||
XCTAssertTrue(stub.reportStub.lastArguments!.value.isSameAs(event: .screenLoadCountTotal(screenName: .passwordCreation)))
|
||||
|
|
|
|||
|
|
@ -34,8 +34,9 @@ import ProtonCoreUIFoundations
|
|||
class TCSnapshotTests: SnapshotTestCase {
|
||||
|
||||
func testTCViewControllerScreen() {
|
||||
let tcViewController = UIStoryboard.instantiate(storyboardName: "PMSignup", controllerType: TCViewController.self, inAppTheme: { .default })
|
||||
let navigationViewController = LoginNavigationViewController(rootViewController: tcViewController)
|
||||
let elViewController = UIStoryboard.instantiate(storyboardName: "PMSignup", controllerType: ExternalLinkViewController.self, inAppTheme: { .default })
|
||||
elViewController.configuration = .init(title: LUITranslation.terms_conditions_view_title.l10n, url: URL(string: "https://proton.me")!)
|
||||
let navigationViewController = LoginNavigationViewController(rootViewController: elViewController)
|
||||
checkSnapshots(controller: navigationViewController, perceptualPrecision: 0.98)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import XCTest
|
|||
class PasswordViewModelTests: XCTestCase {
|
||||
|
||||
func testPasswordOK1() throws {
|
||||
let viewModel = PasswordViewModel()
|
||||
let viewModel = PasswordViewModel(clientApp: .other(named: "core-unit-tests"))
|
||||
let result = viewModel.passwordValidationResult(for: .notEmpty, password: "a", repeatParrword: "a")
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
@ -41,7 +41,7 @@ class PasswordViewModelTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testPasswordOK2() throws {
|
||||
let viewModel = PasswordViewModel()
|
||||
let viewModel = PasswordViewModel(clientApp: .other(named: "core-unit-tests"))
|
||||
let result = viewModel.passwordValidationResult(for: .notEmpty, password: "fhhjdhjdhjdhjhdjhddssaww@#$", repeatParrword: "fhhjdhjdhjdhjhdjhddssaww@#$")
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
@ -52,7 +52,7 @@ class PasswordViewModelTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testPasswordEmpty() throws {
|
||||
let viewModel = PasswordViewModel()
|
||||
let viewModel = PasswordViewModel(clientApp: .other(named: "core-unit-tests"))
|
||||
let result = viewModel.passwordValidationResult(for: .notEmpty, password: "", repeatParrword: "")
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
@ -63,7 +63,7 @@ class PasswordViewModelTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testPasswordNotEqual1() throws {
|
||||
let viewModel = PasswordViewModel()
|
||||
let viewModel = PasswordViewModel(clientApp: .other(named: "core-unit-tests"))
|
||||
let result = viewModel.passwordValidationResult(for: .notEmpty, password: "aa", repeatParrword: "bb")
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
@ -74,7 +74,7 @@ class PasswordViewModelTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testPasswordNotEqual2() throws {
|
||||
let viewModel = PasswordViewModel()
|
||||
let viewModel = PasswordViewModel(clientApp: .other(named: "core-unit-tests"))
|
||||
let result = viewModel.passwordValidationResult(for: .notEmpty, password: "", repeatParrword: "bb")
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
@ -85,7 +85,7 @@ class PasswordViewModelTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testPasswordNotEqual3() throws {
|
||||
let viewModel = PasswordViewModel()
|
||||
let viewModel = PasswordViewModel(clientApp: .other(named: "core-unit-tests"))
|
||||
let result = viewModel.passwordValidationResult(for: .notEmpty, password: "c", repeatParrword: "")
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ private let recoveryDialogTitleName = LUITranslation.recovery_skip_title.l10n
|
|||
private let recoveryDialogMessageName = LUITranslation.recovery_skip_desc.l10n
|
||||
private let recoveryDialogSkipButtonAccessibilityId = "DialogSkipButton"
|
||||
private let recoveryDialogRecoveryButtonAccessibilityId = "DialogRecoveryMethodButton"
|
||||
private let linkString = LUITranslation.recovery_t_c_link.l10n
|
||||
private let linkString = LUITranslation.password_t_c_link.l10n
|
||||
private let errorBannerHVRequired = "Human verification required"
|
||||
private let errorBannerInvalidNumber = "Phone number failed validation"
|
||||
private let errorBannerButton = LUITranslation._core_ok_button.l10n
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ extension String {
|
|||
|
||||
}
|
||||
|
||||
extension String {
|
||||
public extension String {
|
||||
|
||||
subscript(value: Int) -> Character {
|
||||
self[index(at: value)]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue