Skip to content

Commit

Permalink
Add FXIOS-10606 [ToS] Firefox iOS: ToS Card in Onboarding - UI Creati…
Browse files Browse the repository at this point in the history
…on Only (#23275)

* FXIOS-10606 Firefox iOS: ToS Card in Onboarding - UI Creation Only

* Fixed a SwiftLint warning

* Refactored some code
Fixed the theme change issue

* Removed notificationCenter from init

* Updating the theme via Themeable instead of Notifiable

* Refactored some code
  • Loading branch information
dicarobinho authored Nov 22, 2024
1 parent 6e0d6ac commit 3c37c34
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class PrimaryRoundedButton: ResizableButton, ThemeApplicable {

configuration = UIButton.Configuration.filled()
titleLabel?.adjustsFontForContentSizeCategory = true

// Fix for https://openradar.appspot.com/FB12472792
titleLabel?.textAlignment = .center
}

required init?(coder aDecoder: NSCoder) {
Expand Down
4 changes: 4 additions & 0 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
047F9B3224E1FE1F00CD7DF7 /* WidgetKitExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 047F9B2724E1FE1C00CD7DF7 /* WidgetKitExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
047F9B3E24E1FF4000CD7DF7 /* SearchQuickLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047F9B3A24E1FF4000CD7DF7 /* SearchQuickLinks.swift */; };
047F9B4224E1FF4000CD7DF7 /* ImageButtonWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047F9B3C24E1FF4000CD7DF7 /* ImageButtonWithLabel.swift */; };
0A3F57F92CEDD4CA0051B001 /* TermsOfServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3F57F82CEDD4CA0051B001 /* TermsOfServiceViewController.swift */; };
0A49784A2C53E63200B1E82A /* TrackingProtectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4978492C53E63200B1E82A /* TrackingProtectionViewController.swift */; };
0A686B3C2CDB70DC0090E146 /* MainMenuTelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A686B3B2CDB70DC0090E146 /* MainMenuTelemetryTests.swift */; };
0A6875152C91886A00606F53 /* CertificatesHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6875142C91886A00606F53 /* CertificatesHeaderView.swift */; };
Expand Down Expand Up @@ -2286,6 +2287,7 @@
09D94B5E8CF4C06397168F78 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = "my.lproj/Default Browser.strings"; sourceTree = "<group>"; };
09F14D989587092C60A0078F /* ga */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ga; path = ga.lproj/Shared.strings; sourceTree = "<group>"; };
0A3A4A9EB784F7903FB13B7A /* ms */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ms; path = ms.lproj/LoginManager.strings; sourceTree = "<group>"; };
0A3F57F82CEDD4CA0051B001 /* TermsOfServiceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServiceViewController.swift; sourceTree = "<group>"; };
0A4978492C53E63200B1E82A /* TrackingProtectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingProtectionViewController.swift; sourceTree = "<group>"; };
0A574D09BF8D9E37D6C9C654 /* bn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bn; path = bn.lproj/ClearHistoryConfirm.strings; sourceTree = "<group>"; };
0A5B4CE9B0996AE804491134 /* an */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = an; path = an.lproj/Shared.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -10965,6 +10967,7 @@
74F80D332A0A52D700013C3D /* PrivacyPolicyViewController.swift */,
742BD99D2A13AC9000BA6B15 /* OnboardingInstructionPopupViewController.swift */,
81020C912BB5AFA2007B8481 /* OnboardingMultipleChoiceButtonView.swift */,
0A3F57F82CEDD4CA0051B001 /* TermsOfServiceViewController.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -16426,6 +16429,7 @@
E18CE8DA2BDA3F6B00EE2BCD /* NavigationToolbarContainer.swift in Sources */,
C8E2E80E23D20FD2005AACE6 /* FxAWebViewController.swift in Sources */,
E14F7DF2288F3F9F00E3722C /* ThemedTableSectionHeaderFooterView.swift in Sources */,
0A3F57F92CEDD4CA0051B001 /* TermsOfServiceViewController.swift in Sources */,
8A3EF7F22A2FCF4000796E3A /* DeleteExportedDataSetting.swift in Sources */,
8ADC2A142A33762900543DAA /* ReferringPage.swift in Sources */,
0E6C1E212C909AD7001A43BB /* PasswordGeneratorAction.swift in Sources */,
Expand Down
9 changes: 9 additions & 0 deletions firefox-ios/Client/Application/AccessibilityIdentifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,15 @@ public struct AccessibilityIdentifiers {
}
}

struct TermsOfService {
static let logo = "TermsOfService.Logo"
static let title = "TermsOfService.Title"
static let termsOfServiceAgreement = "TermsOfService.TermsOfServiceAgreement"
static let privacyNoticeAgreement = "TermsOfService.PrivacyNoticeAgreement"
static let manageDataCollectionAgreement = "TermsOfService.ManageDataCollectionAgreement"
static let agreeAndContinueButton = "TermsOfService.AgreeAndContinueButton"
}

struct Upgrade {
static let backgroundImage = "Upgrade.BackgroundImage"
static let upgrade = "upgrade."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import Common
import Shared
import UIKit
import ComponentLibrary

class TermsOfServiceViewController: UIViewController,
Themeable {
struct UX {
static let horizontalMargin: CGFloat = 24
static let logoIconSize: CGFloat = 160
static let margin: CGFloat = 20
static let agreementContentSpacing: CGFloat = 15
static let distanceBetweenViews = 2 * margin
}

// MARK: - Properties
var windowUUID: WindowUUID
var themeManager: ThemeManager
var themeObserver: (any NSObjectProtocol)?
var currentWindowUUID: UUID? { windowUUID }
var notificationCenter: NotificationProtocol

// MARK: - UI elements
private lazy var contentScrollView: UIScrollView = .build()

private lazy var contentView: UIView = .build()

private lazy var titleLabel: UILabel = .build { label in
label.text = String(format: .Onboarding.TermsOfService.Title, AppName.shortName.rawValue)
label.font = FXFontStyles.Regular.title1.scaledFont()
label.textAlignment = .center
label.numberOfLines = 0
label.adjustsFontForContentSizeCategory = true
label.accessibilityIdentifier = AccessibilityIdentifiers.TermsOfService.title
}

private lazy var logoImage: UIImageView = .build { logoImage in
logoImage.image = UIImage(named: ImageIdentifiers.logo)
logoImage.accessibilityIdentifier = AccessibilityIdentifiers.TermsOfService.logo
}

private lazy var confirmationButton: PrimaryRoundedButton = .build { [weak self] button in
let viewModel = PrimaryRoundedButtonViewModel(
title: .Onboarding.TermsOfService.AgreementButtonTitle,
a11yIdentifier: AccessibilityIdentifiers.TermsOfService.agreeAndContinueButton)
button.configure(viewModel: viewModel)
}

private lazy var agreementContent: UIStackView = .build { stackView in
stackView.axis = .vertical
stackView.spacing = UX.agreementContentSpacing
}

// MARK: - Initializers
init(
windowUUID: WindowUUID,
themeManager: ThemeManager = AppContainer.shared.resolve(),
notificationCenter: NotificationProtocol = NotificationCenter.default
) {
self.windowUUID = windowUUID
self.themeManager = themeManager
self.notificationCenter = notificationCenter
super.init(nibName: nil, bundle: nil)

setupLayout()
applyTheme()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - View cycles
override func viewDidLoad() {
super.viewDidLoad()
listenForThemeChange(view)
}

// MARK: - View setup
private func configure() {
agreementContent.removeAllArrangedViews()
let termsOfServiceLink = String(format: .Onboarding.TermsOfService.TermsOfServiceLink, AppName.shortName.rawValue)
let termsOfServiceAgreement = String(format: .Onboarding.TermsOfService.TermsOfServiceAgreement, termsOfServiceLink)
setupAgreementTextView(with: termsOfServiceAgreement,
linkTitle: termsOfServiceLink,
and: AccessibilityIdentifiers.TermsOfService.termsOfServiceAgreement)

let privacyNoticeLink = String.Onboarding.TermsOfService.PrivacyNoticeLink
let privacyNoticeText = String.Onboarding.TermsOfService.PrivacyNoticeAgreement
let privacyAgreement = String(format: privacyNoticeText, AppName.shortName.rawValue, privacyNoticeLink)
setupAgreementTextView(with: privacyAgreement,
linkTitle: privacyNoticeLink,
and: AccessibilityIdentifiers.TermsOfService.privacyNoticeAgreement)

let manageLink = String.Onboarding.TermsOfService.ManageLink
let manageText = String.Onboarding.TermsOfService.ManagePreferenceAgreement
let manageAgreement = String(format: manageText, AppName.shortName.rawValue, manageLink)
setupAgreementTextView(with: manageAgreement,
linkTitle: manageLink,
and: AccessibilityIdentifiers.TermsOfService.manageDataCollectionAgreement)
}

private func setupLayout() {
view.addSubview(contentScrollView)
contentScrollView.addSubview(contentView)
contentView.addSubview(titleLabel)
contentView.addSubview(logoImage)
contentView.addSubview(confirmationButton)
contentView.addSubview(agreementContent)

let topMargin = view.frame.size.height / 3 - UX.logoIconSize - UX.margin

NSLayoutConstraint.activate([
contentScrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
contentScrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
contentScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),

contentView.topAnchor.constraint(equalTo: contentScrollView.topAnchor),
contentView.bottomAnchor.constraint(equalTo: contentScrollView.bottomAnchor),
contentView.leadingAnchor.constraint(equalTo: contentScrollView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: contentScrollView.trailingAnchor),
contentView.widthAnchor.constraint(equalTo: contentScrollView.frameLayoutGuide.widthAnchor),
contentView.heightAnchor.constraint(equalTo: contentScrollView.heightAnchor).priority(.defaultLow),

logoImage.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
logoImage.heightAnchor.constraint(equalToConstant: UX.logoIconSize),
logoImage.widthAnchor.constraint(equalToConstant: UX.logoIconSize),
logoImage.topAnchor.constraint(equalTo: contentView.topAnchor, constant: topMargin),

titleLabel.topAnchor.constraint(equalTo: logoImage.bottomAnchor, constant: UX.margin),
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: UX.horizontalMargin),
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -UX.horizontalMargin),

agreementContent.topAnchor.constraint(
greaterThanOrEqualTo: titleLabel.bottomAnchor,
constant: UX.distanceBetweenViews
),
agreementContent.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: UX.horizontalMargin),
agreementContent.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -UX.horizontalMargin),

confirmationButton.topAnchor.constraint(
equalTo: agreementContent.bottomAnchor,
constant: UX.distanceBetweenViews
),
confirmationButton.bottomAnchor.constraint(
equalTo: contentView.bottomAnchor,
constant: -UX.distanceBetweenViews
),
confirmationButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: UX.horizontalMargin),
confirmationButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -UX.horizontalMargin)
])
}

// TODO: FXIOS-10347 Firefox iOS: Manage Privacy Preferences during Onboarding
private func setupAgreementTextView(with title: String, linkTitle: String, and a11yId: String) {
let agreementLabel: UILabel = .build()
agreementLabel.accessibilityIdentifier = a11yId
agreementLabel.numberOfLines = 0
agreementLabel.textAlignment = .center
agreementLabel.adjustsFontForContentSizeCategory = true

let linkedAgreementDescription = NSMutableAttributedString(string: title)
let linkedText = (title as NSString).range(of: linkTitle)
linkedAgreementDescription.addAttribute(.font,
value: FXFontStyles.Regular.caption1.scaledFont(),
range: NSRange(location: 0, length: title.count))
linkedAgreementDescription.addAttribute(.foregroundColor,
value: themeManager.getCurrentTheme(for: windowUUID).colors.textSecondary,
range: NSRange(location: 0, length: title.count))
linkedAgreementDescription.addAttribute(.foregroundColor,
value: themeManager.getCurrentTheme(for: windowUUID).colors.textAccent,
range: linkedText)

agreementLabel.attributedText = linkedAgreementDescription
agreementContent.addArrangedSubview(agreementLabel)
}

// MARK: - Themable
func applyTheme() {
let theme = themeManager.getCurrentTheme(for: windowUUID)
view.backgroundColor = theme.colors.layer2
titleLabel.textColor = theme.colors.textPrimary
confirmationButton.applyTheme(theme: theme)
configure()
}
}
43 changes: 43 additions & 0 deletions firefox-ios/Client/Frontend/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,49 @@ extension String {
value: "Skip",
comment: "Describes an action on some of the Onboarding screen, including the wallpaper onboarding screen. This string will be on a button so user can skip that onboarding page.")

public struct TermsOfService {
public static let Title = MZLocalizedString(
key: "", // Onboarding.TermsOfService.Title.v135
tableName: "Onboarding",
value: "Welcome to %@",
comment: "Title for the Terms of Service screen in the onboarding process. Placeholder is for app name.")
public static let AgreementButtonTitle = MZLocalizedString(
key: "", // Onboarding.TermsOfService.AgreementButtonTitle.v135
tableName: "Onboarding",
value: "Agree and continue",
comment: "Title for the confirmation button for Terms of Service agreement, in the Terms of Service screen.")
public static let TermsOfServiceAgreement = MZLocalizedString(
key: "", // Onboarding.TermsOfService.TermsOfServiceAgreement.v135
tableName: "Onboarding",
value: "By continuing, you agree to the %@",
comment: "Agreement text for Terms of Service in the Terms of Service screen. Placeholder is for the Terms of Service link button that redirect the user to the Terms of Service page")
public static let PrivacyNoticeAgreement = MZLocalizedString(
key: "", // Onboarding.TermsOfService.PrivacyNoticeAgreement.v135
tableName: "Onboarding",
value: "%@ cares about your privacy. Read more in our %@",
comment: "Agreement text for Privacy Notice in the Terms of Service screen. First placeholder is for the app name. The second placeholder is for the Privacy Notice link button that redirect the user to the Privacy Notice page")
public static let ManagePreferenceAgreement = MZLocalizedString(
key: "", // Onboarding.TermsOfService.ManagePreferenceAgreement.v135
tableName: "Onboarding",
value: "To help improve the browser, %@ sends diagnostic and interaction data to Mozilla. %@",
comment: "Agreement text for sends diagnostic and interaction data to Mozilla in the Terms of Service screen. First placeholder is for the app name. The last placeholder is for for the Manage link button which redirect the user to another screen in order to manage the data collection preferences.")
public static let TermsOfServiceLink = MZLocalizedString(
key: "", // Onboarding.TermsOfService.TermsOfServiceLink.v135
tableName: "Onboarding",
value: "%@ Terms of Service.",
comment: "Title for the Terms of Service button link, in the Terms of Service screen for redirecting the user to the Terms of Service page. Placeholder is for the app name.")
public static let PrivacyNoticeLink = MZLocalizedString(
key: "", // Onboarding.TermsOfService.PrivacyNoticeLink.v135
tableName: "Onboarding",
value: "Privacy Notice.",
comment: "Title for the Privacy Notice button link, in the Terms of Service screen for redirecting the user to the Privacy Notice page.")
public static let ManageLink = MZLocalizedString(
key: "", // Onboarding.TermsOfService.ManageLink.v135
tableName: "Onboarding",
value: "Manage",
comment: "Title for the Manage button link, in the Terms of Service screen for redirecting the user to the Manage data collection preferences screen.")
}

public struct Intro {
public static let DescriptionPart1 = MZLocalizedString(
key: "Onboarding.IntroDescriptionPart1.v114",
Expand Down

0 comments on commit 3c37c34

Please sign in to comment.