diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c11874c279..5de7a983b4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -648,6 +648,7 @@ 9F4CC51D2C48D240006A96EB /* CoreDataDatabaseTestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4CC51C2C48D240006A96EB /* CoreDataDatabaseTestUtilities.swift */; }; 9F4CC51F2C48D758006A96EB /* ContextualDaxDialogsFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4CC51E2C48D758006A96EB /* ContextualDaxDialogsFactoryTests.swift */; }; 9F4CC5242C4A4F0D006A96EB /* SwiftUITestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4CC5232C4A4F0D006A96EB /* SwiftUITestUtilities.swift */; }; + 9F4CC5272C4E230C006A96EB /* PrivacyIconContextualOnboardingAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4CC5262C4E230C006A96EB /* PrivacyIconContextualOnboardingAnimator.swift */; }; 9F5E5AAC2C3D0FCD00165F54 /* ContextualDaxDialogsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5E5AAB2C3D0FCD00165F54 /* ContextualDaxDialogsFactory.swift */; }; 9F5E5AB02C3E4C6000165F54 /* ContextualOnboardingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5E5AAF2C3E4C6000165F54 /* ContextualOnboardingPresenter.swift */; }; 9F5E5AB22C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5E5AB12C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift */; }; @@ -2353,6 +2354,7 @@ 9F4CC51C2C48D240006A96EB /* CoreDataDatabaseTestUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataDatabaseTestUtilities.swift; sourceTree = ""; }; 9F4CC51E2C48D758006A96EB /* ContextualDaxDialogsFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualDaxDialogsFactoryTests.swift; sourceTree = ""; }; 9F4CC5232C4A4F0D006A96EB /* SwiftUITestUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUITestUtilities.swift; sourceTree = ""; }; + 9F4CC5262C4E230C006A96EB /* PrivacyIconContextualOnboardingAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyIconContextualOnboardingAnimator.swift; sourceTree = ""; }; 9F5E5AAB2C3D0FCD00165F54 /* ContextualDaxDialogsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualDaxDialogsFactory.swift; sourceTree = ""; }; 9F5E5AAF2C3E4C6000165F54 /* ContextualOnboardingPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingPresenter.swift; sourceTree = ""; }; 9F5E5AB12C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingPresenterTests.swift; sourceTree = ""; }; @@ -4418,6 +4420,14 @@ name = Onboarding; sourceTree = ""; }; + 9F4CC5252C4E22F9006A96EB /* ContextualOnboarding */ = { + isa = PBXGroup; + children = ( + 9F4CC5262C4E230C006A96EB /* PrivacyIconContextualOnboardingAnimator.swift */, + ); + name = ContextualOnboarding; + sourceTree = ""; + }; 9F5E5AAA2C3D0FAA00165F54 /* ContextualOnboarding */ = { isa = PBXGroup; children = ( @@ -5650,6 +5660,7 @@ 1EEF123E2850A68A003DDE57 /* PrivacyInfoContainerView.swift */, 1E7A71152934E4C700B7EA19 /* OmniBarNotifications */, 1EE411F42857C5130003FE64 /* PrivacyIconAndTrackers */, + 9F4CC5252C4E22F9006A96EB /* ContextualOnboarding */, ); name = OmniBar; sourceTree = ""; @@ -7303,6 +7314,7 @@ C1B7B52528941F2A0098FD6A /* RemoteMessagingClient.swift in Sources */, 3132FA2827A0788400DD7A12 /* PassKitPreviewHelper.swift in Sources */, 6FA343922C3D3C3B00470677 /* FavoriteIconView.swift in Sources */, + 9F4CC5272C4E230C006A96EB /* PrivacyIconContextualOnboardingAnimator.swift in Sources */, 8505836C219F424500ED4EDB /* TextFieldWithInsets.swift in Sources */, CBD4F13F279EBFAF00B20FD7 /* HomeMessageViewModel.swift in Sources */, 1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */, diff --git a/DuckDuckGo/DaxDialogs.swift b/DuckDuckGo/DaxDialogs.swift index b41a526e90..18c124ae2b 100644 --- a/DuckDuckGo/DaxDialogs.swift +++ b/DuckDuckGo/DaxDialogs.swift @@ -142,11 +142,21 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic { let type: SpecType func format(args: CVarArg...) -> BrowsingSpec { - return BrowsingSpec(message: String(format: message, arguments: args), - cta: cta, - highlightAddressBar: highlightAddressBar, - pixelName: pixelName, - type: type) + format(message: message, args: args) + } + + func format(message: String, args: CVarArg...) -> BrowsingSpec { + withUpdatedMessage(String(format: message, arguments: args)) + } + + func withUpdatedMessage(_ message: String) -> BrowsingSpec { + BrowsingSpec( + message: message, + cta: cta, + highlightAddressBar: highlightAddressBar, + pixelName: pixelName, + type: type + ) } } @@ -246,7 +256,11 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic { } var shouldShowFireButtonPulse: Bool { - return nonDDGBrowsingMessageSeen && !fireButtonBrowsingMessageSeenOrExpired && isEnabled + if isNewOnboarding { + nonDDGBrowsingMessageSeen && fireButtonBrowsingMessageSeenOrExpired && isEnabled + } else { + nonDDGBrowsingMessageSeen && !fireButtonBrowsingMessageSeenOrExpired && isEnabled + } } func isStillOnboarding() -> Bool { @@ -508,13 +522,21 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic { case 1: settings.browsingWithTrackersShown = true - spec = BrowsingSpec.withOneTracker.format(args: entitiesBlocked[0]) + let args = entitiesBlocked[0] + spec = if isNewOnboarding { + BrowsingSpec.withOneTracker.format(message: UserText.DaxOnboardingExperiment.ContextualOnboarding.daxDialogBrowsingWithOneTracker, args: args) + } else { + BrowsingSpec.withOneTracker.format(args: args) + } default: settings.browsingWithTrackersShown = true - spec = BrowsingSpec.withMultipleTrackers.format(args: entitiesBlocked.count - 2, - entitiesBlocked[0], - entitiesBlocked[1]) + let args: [CVarArg] = [entitiesBlocked.count - 2, entitiesBlocked[0], entitiesBlocked[1]] + spec = if isNewOnboarding { + BrowsingSpec.withMultipleTrackers.format(message: UserText.DaxOnboardingExperiment.ContextualOnboarding.daxDialogBrowsingWithMultipleTrackers, args: args) + } else { + BrowsingSpec.withMultipleTrackers.format(args: args) + } } // New Contextual onboarding doesn't highlight the address bar. This checks prevents to cancel the lottie animation. if isNewOnboarding { diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index f61aa2996d..a692ad9ce9 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -2250,8 +2250,12 @@ extension MainViewController: TabDelegate { showFireButtonPulse() } - func tabDidRequestPrivacyDashboardButtonPulse(tab: TabViewController) { - showPrivacyDashboardButtonPulse() + func tabDidRequestPrivacyDashboardButtonPulse(tab: TabViewController, animated: Bool) { + if animated { + showPrivacyDashboardButtonPulse() + } else { + dismissPrivacyDashboardButtonPulse() + } } func tabDidRequestSearchBarRect(tab: TabViewController) -> CGRect { @@ -2571,9 +2575,11 @@ extension MainViewController: AutoClearWorker { } private func showPrivacyDashboardButtonPulse() { - // TODO: - // 1. Wait for Lottie Tracker animation to complete - // 2. Animate Privacy Icon with pulsing animation similar to `showFireButtonPulse()`. + viewCoordinator.omniBar.showOrScheduleOnboardingPrivacyIconAnimation() + } + + private func dismissPrivacyDashboardButtonPulse() { + viewCoordinator.omniBar.dismissOnboardingPrivacyIconAnimation() } } diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index f020b9a6d8..1dcabd004a 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -71,7 +71,8 @@ class OmniBar: UIView { private var privacyIconAndTrackersAnimator = PrivacyIconAndTrackersAnimator() private var notificationAnimator = OmniBarNotificationAnimator() - + private let privacyIconContextualOnboardingAnimator = PrivacyIconContextualOnboardingAnimator() + // Set up a view to add a custom icon to the Omnibar private var customIconView: UIImageView = UIImageView(frame: CGRect(x: 4, y: 8, width: 26, height: 26)) @@ -315,13 +316,28 @@ class OmniBar: UIView { func showOrScheduleCookiesManagedNotification(isCosmetic: Bool) { let type: OmniBarNotificationType = isCosmetic ? .cookiePopupHidden : .cookiePopupManaged + enqueueAnimationIfNeeded { [weak self] in + guard let self else { return } + self.notificationAnimator.showNotification(type, in: self) + } + } + + func showOrScheduleOnboardingPrivacyIconAnimation() { + enqueueAnimationIfNeeded { [weak self] in + guard let self else { return } + self.privacyIconContextualOnboardingAnimator.showPrivacyIconAnimation(in: self) + } + } + + func dismissOnboardingPrivacyIconAnimation() { + privacyIconContextualOnboardingAnimator.dismissPrivacyIconAnimation() + } + + private func enqueueAnimationIfNeeded(_ block: @escaping () -> Void) { if privacyIconAndTrackersAnimator.state == .completed { - notificationAnimator.showNotification(type, in: self) + block() } else { - privacyIconAndTrackersAnimator.onAnimationCompletion = { [weak self] in - guard let self = self else { return } - self.notificationAnimator.showNotification(type, in: self) - } + privacyIconAndTrackersAnimator.onAnimationCompletion(block) } } diff --git a/DuckDuckGo/OnboardingExperiment/ContextualOnboarding/ContextualOnboardingPresenter.swift b/DuckDuckGo/OnboardingExperiment/ContextualOnboarding/ContextualOnboardingPresenter.swift index f7208ea184..36ac539d47 100644 --- a/DuckDuckGo/OnboardingExperiment/ContextualOnboarding/ContextualOnboardingPresenter.swift +++ b/DuckDuckGo/OnboardingExperiment/ContextualOnboarding/ContextualOnboardingPresenter.swift @@ -34,10 +34,16 @@ protocol ContextualOnboardingPresenting { final class ContextualOnboardingPresenter: ContextualOnboardingPresenting { private let variantManager: VariantManager private let daxDialogsFactory: ContextualDaxDialogsFactory + private let appSettings: AppSettings - init(variantManager: VariantManager, daxDialogsFactory: ContextualDaxDialogsFactory = ExperimentContextualDaxDialogsFactory()) { + init( + variantManager: VariantManager, + daxDialogsFactory: ContextualDaxDialogsFactory = ExperimentContextualDaxDialogsFactory(), + appSettings: AppSettings = AppUserDefaults() + ) { self.variantManager = variantManager self.daxDialogsFactory = daxDialogsFactory + self.appSettings = appSettings } func presentContextualOnboarding(for spec: DaxDialogs.BrowsingSpec, in vc: TabViewOnboardingDelegate) { @@ -71,8 +77,14 @@ private extension ContextualOnboardingPresenter { $0.removeFromSuperview() } + // Adjust message hand emoji based on address bar position + let platformSpecificMessage = spec.message.replacingOccurrences( + of: "☝️", + with: appSettings.currentAddressBarPosition == .bottom ? "👇" : "☝️" + ) + let platformSpecificSpec = spec.withUpdatedMessage(platformSpecificMessage) // Ask the Dax Dialogs Factory for a view for the given spec - let controller = daxDialogsFactory.makeView(for: spec, delegate: vc) + let controller = daxDialogsFactory.makeView(for: platformSpecificSpec, delegate: vc) controller.view.isHidden = true controller.view.alpha = 0 diff --git a/DuckDuckGo/OnboardingExperiment/DaxDialogs/DaxDialogView.swift b/DuckDuckGo/OnboardingExperiment/DaxDialogs/DaxDialogView.swift index 5c642d5dc5..1402c24f11 100644 --- a/DuckDuckGo/OnboardingExperiment/DaxDialogs/DaxDialogView.swift +++ b/DuckDuckGo/OnboardingExperiment/DaxDialogs/DaxDialogView.swift @@ -46,7 +46,7 @@ struct DaxDialogView: View { @State private var logoPosition: DaxDialogLogoPosition - private let matchLogoAnimation: (id: String, namespace: Namespace.ID) + private let matchLogoAnimation: (id: String, namespace: Namespace.ID)? private let showDialogBox: Binding private let cornerRadius: CGFloat private let arrowSize: CGSize @@ -55,7 +55,7 @@ struct DaxDialogView: View { init( logoPosition: DaxDialogLogoPosition, - matchLogoAnimation: (String, Namespace.ID) = ("", Namespace().wrappedValue), + matchLogoAnimation: (String, Namespace.ID)? = nil, showDialogBox: Binding = .constant(true), cornerRadius: CGFloat = 16.0, arrowSize: CGSize = .init(width: 16.0, height: 8.0), @@ -109,12 +109,18 @@ struct DaxDialogView: View { Metrics.stackSpacing + arrowSize.height } + @ViewBuilder private var daxLogo: some View { - Image(.daxIconExperiment) + let icon = Image(.daxIconExperiment) .resizable() - .matchedGeometryEffect(id: matchLogoAnimation.id, in: matchLogoAnimation.namespace) .aspectRatio(contentMode: .fill) .frame(width: Metrics.DaxLogo.size, height: Metrics.DaxLogo.size) + + if let matchLogoAnimation { + icon.matchedGeometryEffect(id: matchLogoAnimation.id, in: matchLogoAnimation.namespace) + } else { + icon + } } private var wrappedContent: some View { diff --git a/DuckDuckGo/PrivacyIconAndTrackersAnimator.swift b/DuckDuckGo/PrivacyIconAndTrackersAnimator.swift index 69726cdcc3..ea1bc96313 100644 --- a/DuckDuckGo/PrivacyIconAndTrackersAnimator.swift +++ b/DuckDuckGo/PrivacyIconAndTrackersAnimator.swift @@ -38,8 +38,8 @@ final class PrivacyIconAndTrackersAnimator { private(set) var state: State = .notStarted - var onAnimationCompletion: (() -> Void)? - + private var animationCompletionObservers: [() -> Void] = [] + func configure(_ container: PrivacyInfoContainerView, with privacyInfo: PrivacyInfo) { state = .notStarted isAnimatingForDaxDialog = false @@ -96,8 +96,8 @@ final class PrivacyIconAndTrackersAnimator { if completed { self?.state = .completed - self?.onAnimationCompletion?() - self?.onAnimationCompletion = nil + self?.animationCompletionObservers.forEach { action in action() } + self?.animationCompletionObservers = [] } } } @@ -143,8 +143,8 @@ final class PrivacyIconAndTrackersAnimator { func completeForNoAnimation() { state = .completed - onAnimationCompletion?() - onAnimationCompletion = nil + animationCompletionObservers.forEach { action in action() } + animationCompletionObservers = [] } func cancelAnimations(in omniBar: OmniBar) { @@ -169,4 +169,9 @@ final class PrivacyIconAndTrackersAnimator { func resetImageProvider() { trackerAnimationImageProvider.reset() } + + func onAnimationCompletion(_ completion: @escaping () -> Void) { + animationCompletionObservers.append(completion) + } + } diff --git a/DuckDuckGo/PrivacyIconContextualOnboardingAnimator.swift b/DuckDuckGo/PrivacyIconContextualOnboardingAnimator.swift new file mode 100644 index 0000000000..d4deef2eba --- /dev/null +++ b/DuckDuckGo/PrivacyIconContextualOnboardingAnimator.swift @@ -0,0 +1,33 @@ +// +// PrivacyIconContextualOnboardingAnimator.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +final class PrivacyIconContextualOnboardingAnimator { + + func showPrivacyIconAnimation(in omniBar: OmniBar) { + guard let window = omniBar.window else { return } + ViewHighlighter.showIn(window, focussedOnView: omniBar.privacyInfoContainer.privacyIcon, scale: .custom(3)) + } + + func dismissPrivacyIconAnimation() { + ViewHighlighter.hideAll() + } + +} diff --git a/DuckDuckGo/TabDelegate.swift b/DuckDuckGo/TabDelegate.swift index 990d46f083..ccc15e1ebe 100644 --- a/DuckDuckGo/TabDelegate.swift +++ b/DuckDuckGo/TabDelegate.swift @@ -74,8 +74,8 @@ protocol TabDelegate: AnyObject { func tabDidRequestForgetAll(tab: TabViewController) func tabDidRequestFireButtonPulse(tab: TabViewController) - - func tabDidRequestPrivacyDashboardButtonPulse(tab: TabViewController) + + func tabDidRequestPrivacyDashboardButtonPulse(tab: TabViewController, animated: Bool) func tabDidRequestSearchBarRect(tab: TabViewController) -> CGRect diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 9faae4687d..8b1caa8f5c 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -857,7 +857,9 @@ class TabViewController: UIViewController { @IBSegueAction private func makePrivacyDashboardViewController(coder: NSCoder) -> PrivacyDashboardViewController? { - PrivacyDashboardViewController(coder: coder, + delegate?.tabDidRequestPrivacyDashboardButtonPulse(tab: self, animated: false) + + return PrivacyDashboardViewController(coder: coder, privacyInfo: privacyInfo, entryPoint: .dashboard, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, @@ -1375,6 +1377,8 @@ extension TabViewController: WKNavigationDelegate { guard let spec = DaxDialogs.shared.nextBrowsingMessageIfShouldShow(for: privacyInfo) else { // Dismiss Contextual onboarding if there's no message to show. contextualOnboardingPresenter.dismissContextualOnboardingIfNeeded(from: self) + // Dismiss privacy dashbooard pulse animation when no browsing dialog to show. + delegate?.tabDidRequestPrivacyDashboardButtonPulse(tab: self, animated: false) if DaxDialogs.shared.shouldShowFireButtonPulse { delegate?.tabDidRequestFireButtonPulse(tab: self) @@ -2881,7 +2885,7 @@ extension TabViewController: ContextualOnboardingEventDelegate { } func didShowContextualOnboardingTrackersDialog() { - delegate?.tabDidRequestPrivacyDashboardButtonPulse(tab: self) + delegate?.tabDidRequestPrivacyDashboardButtonPulse(tab: self, animated: true) } func didTapDismissContextualOnboardingAction() { diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 6be0ab45fb..e5635a89e1 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1212,7 +1212,7 @@ But if you *do* want a peek under the hood, you can find more information about static let onboardingTryASearchMessage = NSLocalizedString("contextual.onboarding.try-a-search.message", value: "Your DuckDuckGo searches are always anonymous.", comment: "Message of a popover on the browser that invites the user to try a search explaining that their searches are anonymous") static let onboardingTryASiteTitle = NSLocalizedString("contextual.onboarding.try-a-site.title", value: "Try visiting a site!", comment: "Title of a popover on the browser that invites the user to try a visiting a website") static let onboardingTryASiteMessage = NSLocalizedString("contextual.onboarding.try-a-site.message", value: "We’ll block trackers so they can’t spy on you.", comment: "Message of a popover on the browser that invites the user to try visiting a website to explain that we block trackers") - static let onboardingTryFireButtonMessage = NSLocalizedString("contextual.onboarding.try-fire-button.message", value: "Instantly clear your browsing activity with the Fire Button.\n\nGive it a try! ☝️", comment: "Message of a popover on the browser that invites the user to try visiting the browser Fire Button. Please leave the line break") + static let onboardingTryFireButtonMessage = NSLocalizedString("contextual.onboarding.try-fire-button.message", value: "Instantly clear your browsing activity with the Fire Button.\n\nGive it a try! 🔥", comment: "Message of a popover on the browser that invites the user to try visiting the browser Fire Button. Please leave the line break") static let onboardingGotItButton = NSLocalizedString("contextual.onboarding.got-it.button", value: "Got it!", comment: "During onboarding steps this button is shown and takes either to the next steps or closes the onboarding.") static let onboardingFirstSearchDoneMessage = NSLocalizedString("contextual.onboarding.first-search-done.message", value: "That’s DuckDuckGo Search. Private. Fast. Fewer ads.", comment: "After the user performs their first search using the browser, this dialog explains the advantages of using DuckDuckGo") static let onboardingFinalScreenTitle = NSLocalizedString("contextual.onboarding.final-screen.title", value: "You’ve got this!", comment: "Title of the last screen of the onboarding to the browser app") @@ -1225,6 +1225,9 @@ But if you *do* want a peek under the hood, you can find more information about static let tryASearchOption3 = NSLocalizedString("contextual.onboarding.try-search.option3", value: "local weather", comment: "Browser Search query for local weather") static let tryASearchOptionSurpriseMeEnglish = NSLocalizedString("contextual.onboarding.try-search.surprise-me-english", value: "chocolate chip cookie recipes", comment: "Browser Search query for chocolate chip cookie recipes") static let tryASearchOptionSurpriseMeInternational = NSLocalizedString("contextual.onboarding.try-search.surprise-me-international", value: "dinner recipes", comment: "Browser Search query for dinner recipes") + + static let daxDialogBrowsingWithOneTracker = NSLocalizedString("dax.onboarding.experiment.browsing.one.tracker", value: "*%1$@* was trying to track you here.\n\nI blocked them!\n\n☝️ Tap the shield for more info.", comment: "Parameter is domain name (string)") + static let daxDialogBrowsingWithMultipleTrackers = NSLocalizedString("dax.onboarding.experiment.browsing.multiple.trackers", comment: "First parameter is a count of additional trackers, second and third are names of the tracker networks (strings)") } } } diff --git a/DuckDuckGo/ViewHighlighter.swift b/DuckDuckGo/ViewHighlighter.swift index f208154305..b08682beeb 100644 --- a/DuckDuckGo/ViewHighlighter.swift +++ b/DuckDuckGo/ViewHighlighter.swift @@ -29,9 +29,9 @@ class ViewHighlighter { static var addedViews = [WeaklyHeldView]() static var highlightedViews = [WeaklyHeldView]() - static func showIn(_ window: UIWindow, focussedOnView view: UIView) { + static func showIn(_ window: UIWindow, focussedOnView view: UIView, scale: HighlightScale = .default) { guard let center = view.superview?.convert(view.center, to: nil) else { return } - let size = max(view.frame.width, view.frame.height) * 5.5 + let size = max(view.frame.width, view.frame.height) * scale.value let highlightView = LottieAnimationView(name: "view_highlight") highlightView.frame = CGRect(x: 0, y: 0, width: size, height: size) @@ -66,3 +66,17 @@ class ViewHighlighter { } } + +extension ViewHighlighter { + enum HighlightScale { + case `default` + case custom(CGFloat) + + fileprivate var value: CGFloat { + switch self { + case .default: 5.5 + case let .custom(value): value + } + } + } +} diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index e002272787..8db7ee8656 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -728,7 +728,7 @@ "contextual.onboarding.try-a-site.title" = "Try visiting a site!"; /* Message of a popover on the browser that invites the user to try visiting the browser Fire Button. Please leave the line break */ -"contextual.onboarding.try-fire-button.message" = "Instantly clear your browsing activity with the Fire Button.\n\nGive it a try! ☝️"; +"contextual.onboarding.try-fire-button.message" = "Instantly clear your browsing activity with the Fire Button.\n\nGive it a try! 🔥"; /* Browser Search query for how to say duck in english */ "contextual.onboarding.try-search.option1-English" = "how to say “duck” in spanish"; @@ -823,6 +823,12 @@ /* No comment provided by engineer. */ "dax.onboarding.browsing.without.trackers.cta" = "Got It"; +/* First parameter is a count of additional trackers, second and third are names of the tracker networks (strings) */ +"dax.onboarding.experiment.browsing.multiple.trackers" = "dax.onboarding.experiment.browsing.multiple.trackers"; + +/* Parameter is domain name (string) */ +"dax.onboarding.experiment.browsing.one.tracker" = "*%1$@* was trying to track you here.\n\nI blocked them!\n\n☝️ Tap the shield for more info."; + /* Encourage user to try clearing data with the fire button */ "dax.onboarding.fire.button" = "Personal data can build up in your browser. Yuck. Use the Fire Button to burn it all away. Give it a try now! 👇"; diff --git a/DuckDuckGo/en.lproj/Localizable.stringsdict b/DuckDuckGo/en.lproj/Localizable.stringsdict index 132ea6de50..f85b4b0b39 100644 --- a/DuckDuckGo/en.lproj/Localizable.stringsdict +++ b/DuckDuckGo/en.lproj/Localizable.stringsdict @@ -48,6 +48,36 @@ I blocked them! ☝️ You can check the address bar to see who is trying to track you when you visit a new site. + dax.onboarding.experiment.browsing.multiple.trackers + + NSStringLocalizedFormatKey + %1#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + *%2$@, %3$@* and *1 other* were trying to track you here. + +I blocked them! + +☝️ Tap the shield for more info. + zero + *%2$@ and %3$@* were trying to track you here. + +I blocked them! + +☝️ Tap the shield for more info. + other + *%2$@, %3$@* and *%d others* were trying to track you here. + +I blocked them! + +☝️ Tap the shield for more info. + + privacy.protection.major.trackers.found NSStringLocalizedFormatKey diff --git a/DuckDuckGoTests/MockTabDelegate.swift b/DuckDuckGoTests/MockTabDelegate.swift index a1d2f81094..dc512310b3 100644 --- a/DuckDuckGoTests/MockTabDelegate.swift +++ b/DuckDuckGoTests/MockTabDelegate.swift @@ -81,7 +81,7 @@ final class MockTabDelegate: TabDelegate { didRequestFireButtonPulseCalled = true } - func tabDidRequestPrivacyDashboardButtonPulse(tab: DuckDuckGo.TabViewController) { + func tabDidRequestPrivacyDashboardButtonPulse(tab: DuckDuckGo.TabViewController, animated: Bool) { didRequestPrivacyDashboardButtonPulseCalled = true }