Skip to content

Commit

Permalink
Merge pull request wordpress-mobile#21012 from wordpress-mobile/compl…
Browse files Browse the repository at this point in the history
…iance-pop-up

[EU/US Compliance] Wire up popover to the flow
  • Loading branch information
alpavanoglu authored Jul 11, 2023
2 parents 928de0f + 277c360 commit 0dd586b
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ end

def wordpress_kit
pod 'WordPressKit', '~> 8.5'
# pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: ''
# pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', tag: ''
# pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: ''
# pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: ''
# pod 'WordPressKit', path: '../WordPressKit-iOS'
end
Expand Down
2 changes: 1 addition & 1 deletion Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,6 @@ SPEC CHECKSUMS:
ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba
ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37

PODFILE CHECKSUM: 463ed7d39926c127d8197fe925fd7d05125f647b
PODFILE CHECKSUM: d1a2566033c325f4188d736422f6997b7551d2cb

COCOAPODS: 1.12.1
5 changes: 5 additions & 0 deletions WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum FeatureFlag: Int, CaseIterable {
case personalizeHomeTab
case commentModerationUpdate
case jetpackSocial
case compliancePopover

/// Returns a boolean indicating if the feature is enabled
var enabled: Bool {
Expand Down Expand Up @@ -131,6 +132,8 @@ enum FeatureFlag: Int, CaseIterable {
return false
case .jetpackSocial:
return AppConfiguration.isJetpack && BuildConfiguration.current == .localDeveloper
case .compliancePopover:
return true
}
}

Expand Down Expand Up @@ -231,6 +234,8 @@ extension FeatureFlag {
return "Comments Moderation Update"
case .jetpackSocial:
return "Jetpack Social"
case .compliancePopover:
return "Compliance Popover"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class GravatarButtonView: CircularImageView {
}
}


/// Touch animation
extension GravatarButtonView {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ final class BlogDashboardViewController: UIViewController {
BlogDashboardViewModel(viewController: self, blog: blog)
}()

private var complianceCoordinator: CompliancePopoverCoordinator?

lazy var collectionView: DynamicHeightCollectionView = {
let collectionView = DynamicHeightCollectionView(frame: .zero, collectionViewLayout: createLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
Expand Down Expand Up @@ -79,6 +81,9 @@ final class BlogDashboardViewController: UIViewController {
startAlertTimer()

WPAnalytics.track(.mySiteDashboardShown)

complianceCoordinator = CompliancePopoverCoordinator(viewController: self)
complianceCoordinator?.presentIfNeeded()
}

override func viewWillDisappear(_ animated: Bool) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import WordPressKit

final class ComplianceLocationService {
func getIPCountryCode(completion: @escaping (Result<String, Error>) -> Void) {
IPLocationRemote().fetchIPCountryCode(completion: completion)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
import SwiftUI

struct CompliancePopover: View {
@State
private var isAnalyticsOn = true
private enum Constants {
static let verticalScrollBuffer = Length.Padding.large
}

var goToSettingsAction: (() -> ())?
var saveAction: (() -> ())?
var shouldScroll: Bool = false
var screenHeight: CGFloat = 0

@StateObject
var viewModel: CompliancePopoverViewModel

var body: some View {
if shouldScroll {
GeometryReader { reader in
ScrollView(showsIndicators: false) {
contentVStack
// Fixes the issue of scroll view content size not sizing properly.
// Without this, on large dynamic fonts, the view is not properly scrollable.
Spacer().frame(height: reader.size.height - screenHeight + Constants.verticalScrollBuffer)
}
}
} else {
contentVStack
}
}

private var contentVStack: some View {
VStack(alignment: .leading, spacing: Length.Padding.double) {
titleText
subtitleText
Expand All @@ -13,6 +37,7 @@ struct CompliancePopover: View {
buttonsHStack
}
.padding(Length.Padding.small)
.fixedSize(horizontal: false, vertical: true)
}

private var titleText: some View {
Expand All @@ -27,14 +52,15 @@ struct CompliancePopover: View {
}

private var analyticsToggle: some View {
Toggle(Strings.toggleTitle, isOn: $isAnalyticsOn)
Toggle(Strings.toggleTitle, isOn: $viewModel.isAnalyticsEnabled)
.foregroundColor(Color.DS.Foreground.primary)
.toggleStyle(SwitchToggleStyle(tint: Color.DS.Background.brand))
.padding(.vertical, Length.Padding.single)
}

private var footnote: some View {
Text("")
Text(Strings.footnote)
.font(.body)
.foregroundColor(.secondary)
}

Expand All @@ -46,30 +72,32 @@ struct CompliancePopover: View {
}

private var settingsButton: some View {
ZStack {
RoundedRectangle(cornerRadius: Length.Padding.single)
.stroke(Color.DS.Border.divider, lineWidth: Length.Border.thin)
Button(action: {
print("Settings tapped")
}) {
Button(action: {
goToSettingsAction?()
}) {
ZStack {
RoundedRectangle(cornerRadius: Length.Padding.single)
.stroke(Color.DS.Border.divider, lineWidth: Length.Border.thin)
Text(Strings.settingsButtonTitle)
.font(.body)
}
.foregroundColor(Color.DS.Background.brand)
}
.foregroundColor(Color.DS.Background.brand)
.frame(height: Length.Hitbox.minTapDimension)
}

private var saveButton: some View {
ZStack {
RoundedRectangle(cornerRadius: Length.Radius.minHeightButton)
.fill(Color.DS.Background.brand)
Button(action: {
print("Save tapped")
}) {
Button(action: {
saveAction?()
}) {
ZStack {
RoundedRectangle(cornerRadius: Length.Radius.minHeightButton)
.fill(Color.DS.Background.brand)
Text(Strings.saveButtonTitle)
.font(.body)
}
.foregroundColor(.white)
}
.foregroundColor(.white)
.frame(height: Length.Hitbox.minTapDimension)
}
}
Expand All @@ -83,10 +111,7 @@ private enum Strings {

static let subtitle = NSLocalizedString(
"compliance.analytics.popover.subtitle",
value: """
We process your personal data to optimize our website and
marketing activities based on your consent and our legitimate interest.
""",
value: "We process your personal data to optimize our website and marketing activities based on your consent and our legitimate interest.",
comment: "Subtitle for the privacy compliance popover."
)

Expand All @@ -98,10 +123,7 @@ private enum Strings {

static let footnote = NSLocalizedString(
"compliance.analytics.popover.footnote",
value: """
These cookies allow us to optimize performance by collecting
information on how users interact with our websites.
""",
value: "These cookies allow us to optimize performance by collecting information on how users interact with our websites.",
comment: "Footnote for the privacy compliance popover."
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import UIKit

protocol CompliancePopoverCoordinatorProtocol {
func presentIfNeeded()
func navigateToSettings()
func dismiss()
}

final class CompliancePopoverCoordinator: CompliancePopoverCoordinatorProtocol {
private let viewController: UIViewController
private let complianceService = ComplianceLocationService()
private let defaults: UserDefaults

init(viewController: UIViewController, defaults: UserDefaults = UserDefaults.standard) {
self.viewController = viewController
self.defaults = defaults
}

func presentIfNeeded() {
guard FeatureFlag.compliancePopover.enabled else {
return
}
complianceService.getIPCountryCode { [weak self] result in
if case .success(let countryCode) = result {
guard let self, self.shouldShowPrivacyBanner(countryCode: countryCode) else {
return
}
DispatchQueue.main.async {
self.presentPopover()
}
}
}
}

func navigateToSettings() {
viewController.dismiss(animated: true) {
RootViewCoordinator.sharedPresenter.navigateToPrivacySettings()
}
}

func dismiss() {
viewController.dismiss(animated: true)
}

private func shouldShowPrivacyBanner(countryCode: String) -> Bool {
let isCountryInEU = Self.gdprCountryCodes.contains(countryCode)
return isCountryInEU && !defaults.didShowCompliancePopup
}

private func presentPopover() {
let complianceViewModel = CompliancePopoverViewModel(
defaults: defaults,
contextManager: ContextManager.shared
)
complianceViewModel.coordinator = self
let complianceViewController = CompliancePopoverViewController(viewModel: complianceViewModel)
let bottomSheetViewController = BottomSheetViewController(childViewController: complianceViewController, customHeaderSpacing: 0)

bottomSheetViewController.show(from: self.viewController)
}
}

private extension CompliancePopoverCoordinator {
static let gdprCountryCodes: Set<String> = [
"AT", "AUT", // Austria
"BE", "BEL", // Belgium
"BG", "BGR", // Bulgaria
"HR", "HRV", // Croatia
"CY", "CYP", // Cyprus
"CZ", "CZE", // Czech Republic
"DK", "DNK", // Denmark
"EE", "EST", // Estonia
"FI", "FIN", // Finland
"FR", "FRA", // France
"DE", "DEU", // Germany
"GR", "GRC", // Greece
"HU", "HUN", // Hungary
"IE", "IRL", // Ireland
"IT", "ITA", // Italy
"LV", "LVA", // Latvia
"LT", "LTU", // Lithuania
"LU", "LUX", // Luxembourg
"MT", "MLT", // Malta
"NL", "NLD", // Netherlands
"NO", "NOR", // Norway
"PL", "POL", // Poland
"PT", "PRT", // Portugal
"RO", "ROU", // Romania
"SK", "SVK", // Slovakia
"SI", "SVN", // Slovenia
"ES", "ESP", // Spain
"SE", "SWE", // Sweden
"CH", "CHE", // Switzerland
"IS",
"LI",
"GB",
// *Although the UK has departed from the EU as of January 2021,
// the GDPR was enacted before its withdrawal and is therefore considered a valid UK law.*
]
}

extension UserDefaults {
static let didShowCompliancePopupKey = "didShowCompliancePopup"

var didShowCompliancePopup: Bool {
get {
bool(forKey: Self.didShowCompliancePopupKey)
} set {
set(newValue, forKey: Self.didShowCompliancePopupKey)
}
}
}
Loading

0 comments on commit 0dd586b

Please sign in to comment.