diff --git a/BrowserKit/Sources/Shared/SupportUtils.swift b/BrowserKit/Sources/Shared/SupportUtils.swift index 147567c6531e..f759797de33e 100644 --- a/BrowserKit/Sources/Shared/SupportUtils.swift +++ b/BrowserKit/Sources/Shared/SupportUtils.swift @@ -36,6 +36,14 @@ public struct SupportUtils { return URL(string: "https://www.mozilla.org/privacy/firefox/") } + public static var URLForUpdatedPrivacyNotice: URL? { + return URL(string: "https://www.mozilla.org/privacy/firefox/next/") + } + + public static var URLForUpdatedPrivacyNoticeDiff: URL? { + return URL(string: "https://www.mozilla.org/privacy/firefox/update/") + } + public static var URLForRelayAccountManagement: URL? { return URL(string: "https://relay.firefox.com/accounts/profile") } diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 10c24ebbc441..f80ee7633616 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -1545,6 +1545,7 @@ C4F3B29A1CFCF93A00966259 /* ButtonToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F3B2991CFCF93A00966259 /* ButtonToast.swift */; }; C706CBEF2C3F0FDE00DC65F1 /* CreditCardSettingsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C706CBEE2C3F0FDE00DC65F1 /* CreditCardSettingsViewControllerTests.swift */; }; C710FDB72DCBDBEF0046475F /* TopSiteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C710FDB62DCBDBEF0046475F /* TopSiteCell.swift */; }; + C71158CD2EEA0B19009766B8 /* PrivacyNoticeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71158CC2EEA0B0A009766B8 /* PrivacyNoticeCell.swift */; }; C7149E002EB14E2400C1FF00 /* StoriesWebviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7149DFF2EB14E2400C1FF00 /* StoriesWebviewViewController.swift */; }; C714D27E2E4539BE00FC02E6 /* ShortcutsLibraryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C714D27D2E4539BE00FC02E6 /* ShortcutsLibraryState.swift */; }; C714D2822E4549BB00FC02E6 /* ShortcutsLibraryAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C714D2812E4549BB00FC02E6 /* ShortcutsLibraryAction.swift */; }; @@ -9948,6 +9949,7 @@ C6C94C1CB6286845DEAF8EDD /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; }; C706CBEE2C3F0FDE00DC65F1 /* CreditCardSettingsViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardSettingsViewControllerTests.swift; sourceTree = ""; }; C710FDB62DCBDBEF0046475F /* TopSiteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSiteCell.swift; sourceTree = ""; }; + C71158CC2EEA0B0A009766B8 /* PrivacyNoticeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyNoticeCell.swift; sourceTree = ""; }; C7149DFF2EB14E2400C1FF00 /* StoriesWebviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoriesWebviewViewController.swift; sourceTree = ""; }; C714D27D2E4539BE00FC02E6 /* ShortcutsLibraryState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsLibraryState.swift; sourceTree = ""; }; C714D2812E4549BB00FC02E6 /* ShortcutsLibraryAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsLibraryAction.swift; sourceTree = ""; }; @@ -13447,6 +13449,7 @@ 8AF347DC2CADD0E100624036 /* Redux */, DFACBF82277B9278003D5F41 /* Wallpapers */, 8AB5958628412B620090F4AE /* CustomizeHome */, + C71158CB2EEA0ADF009766B8 /* PrivacyNotice */, ); path = Homepage; sourceTree = ""; @@ -14436,6 +14439,14 @@ path = Service; sourceTree = ""; }; + C71158CB2EEA0ADF009766B8 /* PrivacyNotice */ = { + isa = PBXGroup; + children = ( + C71158CC2EEA0B0A009766B8 /* PrivacyNoticeCell.swift */, + ); + path = PrivacyNotice; + sourceTree = ""; + }; C714D27C2E45388D00FC02E6 /* ShortcutsLibrary */ = { isa = PBXGroup; children = ( @@ -18469,6 +18480,7 @@ 8AEAD9F52C3D7BA9001A2C5A /* FeatureFlagsDebugViewController.swift in Sources */, 810CD9C12BB346D800E290C2 /* OnboardingCardViewController.swift in Sources */, 8AD3AFC32D143EF600CFC887 /* HomepageDimensionImplementation.swift in Sources */, + C71158CD2EEA0B19009766B8 /* PrivacyNoticeCell.swift in Sources */, 8A3EF8172A2FD2B900796E3A /* AdvancedAccountSettings.swift in Sources */, C2886E082EC5E025007AE6C3 /* StaticFileRoute.swift in Sources */, EBB89509219398E500EB91A0 /* TabContentBlocker+ContentScript.swift in Sources */, diff --git a/firefox-ios/Client/Frontend/Home/Homepage/PrivacyNotice/PrivacyNoticeCell.swift b/firefox-ios/Client/Frontend/Home/Homepage/PrivacyNotice/PrivacyNoticeCell.swift new file mode 100644 index 000000000000..5fe2392456ed --- /dev/null +++ b/firefox-ios/Client/Frontend/Home/Homepage/PrivacyNotice/PrivacyNoticeCell.swift @@ -0,0 +1,120 @@ +// 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 + +/// The privacy notice cell used in the homepage collection view +final class PrivacyNoticeCell: UICollectionViewCell, + ReusableCell, + ThemeApplicable { + struct UX { + static let cellCornerRadius: CGFloat = 16 + static let cellBorderWidth: CGFloat = 1 + static let bodyLabelVerticalInset: CGFloat = 10 + static let contentHorizontalInset: CGFloat = 16 + static let contentSpacing: CGFloat = 4 + static let closeButtonSize = CGSize(width: 20, height: 20) + static let closeButtonImageSize = CGSize(width: 20, height: 20) + } + + // MARK: - UI Elements + + private let bodyTextView: UITextView = .build { textView in + textView.backgroundColor = .clear + textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = true + textView.dataDetectorTypes = [] + textView.adjustsFontForContentSizeCategory = true + + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0 + } + + private let closeButton: UIButton = .build { button in + var config = UIButton.Configuration.plain() + + let image = UIImage(named: (StandardImageIdentifiers.Medium.cross)) + let scaledAndTemplatedImage = image?.createScaled(UX.closeButtonImageSize).withRenderingMode(.alwaysTemplate) + + config.image = scaledAndTemplatedImage + button.configuration = config + } + + override init(frame: CGRect) { + super.init(frame: .zero) + + setupLayout() + setupBodyTextViewAttributedString() + } + + override func layoutSubviews() { + contentView.layer.cornerRadius = UX.cellCornerRadius + contentView.layer.borderWidth = UX.cellBorderWidth + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func configure(theme: Theme) { + applyTheme(theme: theme) + } + + private func setupLayout() { + contentView.addSubview(bodyTextView) + contentView.addSubview(closeButton) + + NSLayoutConstraint.activate([ + bodyTextView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: UX.bodyLabelVerticalInset), + bodyTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: UX.contentHorizontalInset), + bodyTextView.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -UX.contentSpacing), + bodyTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -UX.bodyLabelVerticalInset), + + closeButton.centerYAnchor.constraint(equalTo: bodyTextView.centerYAnchor), + closeButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, + constant: -UX.contentHorizontalInset), + closeButton.widthAnchor.constraint(equalToConstant: UX.closeButtonSize.width), + closeButton.heightAnchor.constraint(equalToConstant: UX.closeButtonSize.height), + ]) + } + + private func setupBodyTextViewAttributedString() { + let bodyString = String.FirefoxHomepage.PrivacyNotice.Body + let privacyNoticeString = String.FirefoxHomepage.PrivacyNotice.PrivacyNoticeLink + let learnMoreString = String.FirefoxHomepage.PrivacyNotice.LearnMoreLink + let fullText = String(format: bodyString, privacyNoticeString, AppName.shortName.rawValue, learnMoreString) + let attributedString = NSMutableAttributedString(string: fullText) + + attributedString.addAttributes([ + .font: FXFontStyles.Regular.footnote.scaledFont(), + ], range: NSRange(location: 0, length: attributedString.length)) + + if let updatedPrivacyNoticeUrl = SupportUtils.URLForUpdatedPrivacyNotice, + let updatedPrivacyNoticeDiffUrl = SupportUtils.URLForUpdatedPrivacyNoticeDiff { + let privacyNoticeLinkRange = (fullText as NSString).range(of: .FirefoxHomepage.PrivacyNotice.PrivacyNoticeLink) + attributedString.addAttribute(.link, value: updatedPrivacyNoticeUrl, range: privacyNoticeLinkRange) + + let learnMoreLinkRange = (fullText as NSString).range(of: .FirefoxHomepage.PrivacyNotice.LearnMoreLink) + attributedString.addAttribute(.link, value: updatedPrivacyNoticeDiffUrl, range: learnMoreLinkRange) + } + + bodyTextView.attributedText = attributedString + } + + // MARK: - ThemeApplicable + func applyTheme(theme: Theme) { + contentView.backgroundColor = theme.colors.layer2 + contentView.layer.borderColor = theme.colors.borderPrimary.cgColor + bodyTextView.textColor = theme.colors.textPrimary + bodyTextView.linkTextAttributes = [ + .foregroundColor: theme.colors.actionPrimary, + .underlineStyle: NSUnderlineStyle.single.rawValue + ] + var config = closeButton.configuration + config?.baseForegroundColor = theme.colors.iconPrimary + closeButton.configuration = config + } +}