Skip to content

Commit

Permalink
Alessandro/refresh dax dialog and ntp logo (#3129)
Browse files Browse the repository at this point in the history
Co-authored-by: Alessandro Boron <[email protected]>
  • Loading branch information
SabrinaTardio and alessandroboron committed Aug 5, 2024
1 parent 2fa6587 commit fa9ef11
Show file tree
Hide file tree
Showing 13 changed files with 529 additions and 38 deletions.
2 changes: 2 additions & 0 deletions Core/UserDefaultsPropertyWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public struct UserDefaultsWrapper<T> {
case daxFireButtonEducationShownOrExpired = "com.duckduckgo.ios.daxfireButtonEducationShownOrExpired"
case fireButtonPulseDateShown = "com.duckduckgo.ios.fireButtonPulseDateShown"
case daxBrowsingFinalDialogShown = "com.duckduckgo.ios.daxOnboardingFinalDialogSeen"
case daxLastVisitedOnboardingWebsite = "com.duckduckgo.ios.daxOnboardingLastVisitedWebsite"
case daxLastShownContextualOnboardingDialogType = "com.duckduckgo.ios.daxLastShownContextualOnboardingDialogType"

case notFoundCache = "com.duckduckgo.ios.favicons.notFoundCache"
case faviconSizeNeedsMigration = "com.duckduckgo.ios.favicons.sizeNeedsMigration"
Expand Down
144 changes: 127 additions & 17 deletions DuckDuckGo/DaxDialogs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ protocol NewTabDialogSpecProvider {
}

protocol ContextualOnboardingLogic {
func setSearchMessageSeen()
func setFireEducationMessageSeen()
func setFinalOnboardingDialogSeen()
}
Expand Down Expand Up @@ -77,11 +78,15 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
settings.browsingWithTrackersShown = flag
case .afterSearch:
settings.browsingAfterSearchShown = flag
case .visitWebsite:
break
case .withoutTrackers:
settings.browsingWithoutTrackersShown = flag
case .siteIsMajorTracker, .siteOwnedByMajorTracker:
settings.browsingMajorTrackingSiteShown = flag
settings.browsingWithoutTrackersShown = flag
case .fire:
settings.fireButtonEducationShownOrExpired = flag
case .final:
settings.browsingFinalDialogShown = flag
}
Expand All @@ -90,13 +95,15 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
struct BrowsingSpec: Equatable {
// swiftlint:disable nesting

enum SpecType {
enum SpecType: String {
case afterSearch
case visitWebsite
case withoutTrackers
case siteIsMajorTracker
case siteOwnedByMajorTracker
case withOneTracker
case withMultipleTrackers
case fire
case final
}
// swiftlint:enable nesting
Expand Down Expand Up @@ -299,6 +306,61 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
private var fireButtonPulseTimer: Timer?
private static let timeToFireButtonExpire: TimeInterval = 1 * 60 * 60

private var lastVisitedOnboardingWebsiteURLPath: String? {
guard isNewOnboarding else { return nil }
return settings.lastVisitedOnboardingWebsiteURLPath
}

private func saveLastVisitedOnboardingWebsite(url: URL?) {
guard isNewOnboarding, let url = url else { return }
settings.lastVisitedOnboardingWebsiteURLPath = url.absoluteString
}

private func removeLastVisitedOnboardingWebsite() {
guard isNewOnboarding else { return }
settings.lastVisitedOnboardingWebsiteURLPath = nil
}

private var lastShownDaxDialogType: String? {
guard isNewOnboarding else { return nil }
return settings.lastShownContextualOnboardingDialogType
}

private func saveLastShownDaxDialog(specType: BrowsingSpec.SpecType) {
guard isNewOnboarding else { return }
settings.lastShownContextualOnboardingDialogType = specType.rawValue
}

private func removeLastShownDaxDialog() {
settings.lastShownContextualOnboardingDialogType = nil
}

private func lastShownDaxDialog(privacyInfo: PrivacyInfo) -> BrowsingSpec? {
guard let dialogType = lastShownDaxDialogType else { return nil }
switch dialogType {
case BrowsingSpec.SpecType.afterSearch.rawValue:
return BrowsingSpec.afterSearch
case BrowsingSpec.SpecType.visitWebsite.rawValue:
return BrowsingSpec(message: "", cta: "", highlightAddressBar: false, pixelName: .daxDialogsFireEducationConfirmed, type: .visitWebsite)
case BrowsingSpec.SpecType.withoutTrackers.rawValue:
return BrowsingSpec.withoutTrackers
case BrowsingSpec.SpecType.siteIsMajorTracker.rawValue:
guard let host = privacyInfo.domain else { return nil }
return majorTrackerMessage(host)
case BrowsingSpec.SpecType.siteOwnedByMajorTracker.rawValue:
guard let host = privacyInfo.domain, let owner = isOwnedByFacebookOrGoogle(host) else { return nil }
return majorTrackerOwnerMessage(host, owner)
case BrowsingSpec.SpecType.withOneTracker.rawValue, BrowsingSpec.SpecType.withMultipleTrackers.rawValue:
guard let entityNames = blockedEntityNames(privacyInfo.trackerInfo) else { return nil }
return trackersBlockedMessage(entityNames)
case BrowsingSpec.SpecType.fire.rawValue:
return BrowsingSpec(message: "", cta: "", highlightAddressBar: false, pixelName: .daxDialogsFireEducationConfirmed, type: .fire)
case BrowsingSpec.SpecType.final.rawValue:
return nil
default: return nil
}
}

func fireButtonPulseStarted() {
if settings.fireButtonPulseDateShown == nil {
settings.fireButtonPulseDateShown = Date()
Expand All @@ -325,9 +387,15 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
return ActionSheetSpec.fireButtonEducation
}

func setSearchMessageSeen() {
guard isNewOnboarding else { return }
saveLastShownDaxDialog(specType: .visitWebsite)
}

func setFireEducationMessageSeen() {
guard isNewOnboarding else { return }
settings.fireButtonEducationShownOrExpired = true
saveLastShownDaxDialog(specType: .fire)
}

func setFinalOnboardingDialogSeen() {
Expand All @@ -336,12 +404,13 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
}

func nextBrowsingMessageIfShouldShow(for privacyInfo: PrivacyInfo) -> BrowsingSpec? {
guard privacyInfo.url != lastURLDaxDialogReturnedFor else { return nil }

let message = if isNewOnboarding {
nextBrowsingMessageExperiment(privacyInfo: privacyInfo)

var message: BrowsingSpec?
if isNewOnboarding {
message = nextBrowsingMessageExperiment(privacyInfo: privacyInfo)
} else {
nextBrowsingMessage(privacyInfo: privacyInfo)
guard privacyInfo.url != lastURLDaxDialogReturnedFor else { return nil }
message = nextBrowsingMessage(privacyInfo: privacyInfo)
}

if message != nil {
Expand Down Expand Up @@ -378,43 +447,59 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
}

private func nextBrowsingMessageExperiment(privacyInfo: PrivacyInfo) -> BrowsingSpec? {


if let lastVisitedOnboardingWebsiteURLPath,
compareUrls(url1: URL(string: lastVisitedOnboardingWebsiteURLPath), url2: privacyInfo.url) {
return lastShownDaxDialog(privacyInfo: privacyInfo)
}

func hasTrackers(host: String) -> Bool {
isFacebookOrGoogle(privacyInfo.url) || isOwnedByFacebookOrGoogle(host) != nil || blockedEntityNames(privacyInfo.trackerInfo) != nil
}

guard isEnabled, nextHomeScreenMessageOverride == nil else { return nil }

guard let host = privacyInfo.domain else { return nil }

var spec: BrowsingSpec?

if privacyInfo.url.isDuckDuckGoSearch && !settings.browsingAfterSearchShown {
return searchMessage()
spec = searchMessage()
}

// won't be shown if owned by major tracker message has already been shown
if isFacebookOrGoogle(privacyInfo.url) && !settings.browsingMajorTrackingSiteShown {
return majorTrackerMessage(host)
spec = majorTrackerMessage(host)
}

// won't be shown if major tracker message has already been shown
if let owner = isOwnedByFacebookOrGoogle(host), !settings.browsingMajorTrackingSiteShown {
return majorTrackerOwnerMessage(host, owner)
spec = majorTrackerOwnerMessage(host, owner)
}

if let entityNames = blockedEntityNames(privacyInfo.trackerInfo), !settings.browsingWithTrackersShown {
return trackersBlockedMessage(entityNames)
spec = trackersBlockedMessage(entityNames)
}

// if non duck duck go search and no trackers found and no tracker message already shown, show no trackers message
if !settings.browsingWithoutTrackersShown && !privacyInfo.url.isDuckDuckGoSearch && !hasTrackers(host: host) {
return noTrackersMessage()
spec = noTrackersMessage()
}

// If the user visited a website and saw the fire dialog
if shouldDisplayFinalContextualBrowsingDialog {
return finalMessage()
spec = finalMessage()
}

return nil
if let spec {
saveLastShownDaxDialog(specType: spec.type)
saveLastVisitedOnboardingWebsite(url: privacyInfo.url)
} else {
removeLastVisitedOnboardingWebsite()
removeLastShownDaxDialog()
}

return spec
}

func nextHomeScreenMessage() -> HomeScreenSpec? {
Expand Down Expand Up @@ -481,7 +566,8 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
}

func majorTrackerOwnerMessage(_ host: String, _ majorTrackerEntity: Entity) -> DaxDialogs.BrowsingSpec? {
guard !settings.browsingMajorTrackingSiteShown else { return nil }
if !isNewOnboarding && settings.browsingMajorTrackingSiteShown { return nil }

guard let entityName = majorTrackerEntity.displayName,
let entityPrevalence = majorTrackerEntity.prevalence else { return nil }
settings.browsingMajorTrackingSiteShown = true
Expand All @@ -492,7 +578,8 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
}

private func majorTrackerMessage(_ host: String) -> DaxDialogs.BrowsingSpec? {
guard !settings.browsingMajorTrackingSiteShown else { return nil }
if !isNewOnboarding && settings.browsingMajorTrackingSiteShown { return nil }

guard let entityName = entityProviding.entity(forHost: host)?.displayName else { return nil }
settings.browsingMajorTrackingSiteShown = true
settings.browsingWithoutTrackersShown = true
Expand All @@ -512,7 +599,7 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
}

private func trackersBlockedMessage(_ entitiesBlocked: [String]) -> BrowsingSpec? {
guard !settings.browsingWithTrackersShown else { return nil }
if !isNewOnboarding && settings.browsingWithTrackersShown { return nil }

var spec: BrowsingSpec?
switch entitiesBlocked.count {
Expand Down Expand Up @@ -563,4 +650,27 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic {
guard let entity = entityProviding.entity(forHost: host) else { return nil }
return entity.domains?.contains(where: { MajorTrackers.domains.contains($0) }) ?? false ? entity : nil
}

private func compareUrls(url1: URL?, url2: URL?) -> Bool {
guard let url1, let url2 else { return false }

if url1 == url2 {
return true
}

guard url1.isDuckDuckGoSearch && url2.isDuckDuckGoSearch else { return false }

// Extract 'q' parameter from both URLs
let queryValue1 = URLComponents(url: url1, resolvingAgainstBaseURL: false)?.queryItems?.first(where: { $0.name == "q" })?.value
let queryValue2 = URLComponents(url: url2, resolvingAgainstBaseURL: false)?.queryItems?.first(where: { $0.name == "q" })?.value

let normalizedQuery1 = queryValue1?
.replacingOccurrences(of: "+", with: " ")
.replacingOccurrences(of: "%20", with: " ")
let normalizedQuery2 = queryValue2?
.replacingOccurrences(of: "+", with: " ")
.replacingOccurrences(of: "%20", with: " ")

return normalizedQuery1 == normalizedQuery2
}
}
14 changes: 14 additions & 0 deletions DuckDuckGo/DaxDialogsSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ protocol DaxDialogsSettings {

var browsingFinalDialogShown: Bool { get set }

var lastVisitedOnboardingWebsiteURLPath: String? { get set }

var lastShownContextualOnboardingDialogType: String? { get set }

}

class DefaultDaxDialogsSettings: DaxDialogsSettings {
Expand Down Expand Up @@ -70,6 +74,12 @@ class DefaultDaxDialogsSettings: DaxDialogsSettings {
@UserDefaultsWrapper(key: .daxBrowsingFinalDialogShown, defaultValue: false)
var browsingFinalDialogShown: Bool

@UserDefaultsWrapper(key: .daxLastVisitedOnboardingWebsite, defaultValue: nil)
var lastVisitedOnboardingWebsiteURLPath: String?

@UserDefaultsWrapper(key: .daxLastShownContextualOnboardingDialogType, defaultValue: nil)
var lastShownContextualOnboardingDialogType: String?

}

class InMemoryDaxDialogsSettings: DaxDialogsSettings {
Expand All @@ -92,4 +102,8 @@ class InMemoryDaxDialogsSettings: DaxDialogsSettings {

var browsingFinalDialogShown = false

var lastVisitedOnboardingWebsiteURLPath: String?

var lastShownContextualOnboardingDialogType: String?

}
10 changes: 6 additions & 4 deletions DuckDuckGo/HomeViewController+DaxDialogs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ extension HomeViewController {
}

func showNextDaxDialogNew(dialogProvider: NewTabDialogSpecProvider, factory: any NewTabDaxDialogProvider) {
dismissHostingController()
dismissHostingController(didFinishNTPOnboarding: false)
let onDismiss = {
dialogProvider.dismiss()
self.dismissHostingController()
self.dismissHostingController(didFinishNTPOnboarding: true)
}
guard let spec = dialogProvider.nextHomeScreenMessageNew() else { return }
let daxDialogView = AnyView(factory.createDaxDialog(for: spec, onDismiss: onDismiss))
Expand All @@ -75,10 +75,12 @@ extension HomeViewController {
configureCollectionView()
}

private func dismissHostingController() {
private func dismissHostingController(didFinishNTPOnboarding: Bool) {
hostingController?.willMove(toParent: nil)
hostingController?.view.removeFromSuperview()
hostingController?.removeFromParent()
delegate?.home(self, didRequestHideLogo: false)
if didFinishNTPOnboarding {
delegate?.home(self, didRequestHideLogo: false)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,28 @@ struct OnboardingFirstSearchDoneDialog: View {
OnboardingTryVisitingSiteDialogContent(viewModel: viewModel)
} else {
ContextualDaxDialogContent(message: message, cta: cta) {
buttonAction()
gotItAction()
withAnimation {
if shouldFollowUp {
showNextScreen = true
}
}
}
}
}
}
}
}
}

private func buttonAction() {
withAnimation {
if shouldFollowUp {
showNextScreen = true
} else {
gotItAction()
struct OnboardingFireDialog: View {

var body: some View {
ScrollView(.vertical, showsIndicators: false) {
DaxDialogView(logoPosition: .left) {
VStack {
OnboardingFireButtonDialogContent()
}
}
}
}
Expand Down
Loading

0 comments on commit fa9ef11

Please sign in to comment.