Skip to content

Commit

Permalink
Onboarding Contextual Tutorial Rendering (#3063)
Browse files Browse the repository at this point in the history
  • Loading branch information
alessandroboron committed Jul 29, 2024
1 parent 36bea0c commit 1e453dd
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 32 deletions.
24 changes: 24 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,10 @@
9F23B8032C2BCD0000950875 /* DaxDialogStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8022C2BCD0000950875 /* DaxDialogStyles.swift */; };
9F23B8062C2BE22700950875 /* OnboardingIntroViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8052C2BE22700950875 /* OnboardingIntroViewModelTests.swift */; };
9F23B8092C2BE9B700950875 /* MockURLOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8082C2BE9B700950875 /* MockURLOpener.swift */; };
9F5E5AAC2C3D0FCD00165F54 /* ContextualDaxDialogsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5E5AAB2C3D0FCD00165F54 /* ContextualDaxDialogsFactory.swift */; };
9F5E5AAE2C3E44DC00165F54 /* ContextualOnboardingBackgroundWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5E5AAD2C3E44DC00165F54 /* ContextualOnboardingBackgroundWrapper.swift */; };
9F5E5AB02C3E4C6000165F54 /* ContextualOnboardingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5E5AAF2C3E4C6000165F54 /* ContextualOnboardingPresenter.swift */; };
9F5E5AB22C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5E5AB12C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift */; };
9F8FE9492BAE50E50071E372 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 9F8FE9482BAE50E50071E372 /* Lottie */; };
9F9EE4CE2C377D4900D4118E /* OnboardingFirePixelMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9EE4CC2C377D3F00D4118E /* OnboardingFirePixelMock.swift */; };
9F9EE4D42C37BB1300D4118E /* OnboardingView+Landing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9EE4D32C37BB1300D4118E /* OnboardingView+Landing.swift */; };
Expand Down Expand Up @@ -2343,6 +2347,10 @@
9F23B8052C2BE22700950875 /* OnboardingIntroViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingIntroViewModelTests.swift; sourceTree = "<group>"; };
9F23B8082C2BE9B700950875 /* MockURLOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLOpener.swift; sourceTree = "<group>"; };
9F9EE4CC2C377D3F00D4118E /* OnboardingFirePixelMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFirePixelMock.swift; sourceTree = "<group>"; };
9F5E5AAB2C3D0FCD00165F54 /* ContextualDaxDialogsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualDaxDialogsFactory.swift; sourceTree = "<group>"; };
9F5E5AAD2C3E44DC00165F54 /* ContextualOnboardingBackgroundWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingBackgroundWrapper.swift; sourceTree = "<group>"; };
9F5E5AAF2C3E4C6000165F54 /* ContextualOnboardingPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingPresenter.swift; sourceTree = "<group>"; };
9F5E5AB12C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingPresenterTests.swift; sourceTree = "<group>"; };
9F9EE4D32C37BB1300D4118E /* OnboardingView+Landing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+Landing.swift"; sourceTree = "<group>"; };
9FA5E44A2BF1AF3400BDEF02 /* SubscriptionContainerViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerViewFactory.swift; sourceTree = "<group>"; };
9FB027112C2526DD009EA190 /* OnboardingView+IntroDialogContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+IntroDialogContent.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3521,6 +3529,7 @@
56D060232C35918D003BAEB5 /* ContextualOnboardingList.swift */,
56D060252C359D2E003BAEB5 /* ContextualOnboardingDialogs.swift */,
564DE4522C3ED1B700D23241 /* NewTabDaxDialogFactory.swift */,
9F5E5AAD2C3E44DC00165F54 /* ContextualOnboardingBackgroundWrapper.swift */,
);
path = ContextualDaxDialogs;
sourceTree = "<group>";
Expand Down Expand Up @@ -4410,6 +4419,7 @@
564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */,
9F9EE4CB2C377D2400D4118E /* Mocks */,
9FE05CEF2C3642F900D9046B /* OnboardingPixelReporterTests.swift */,
9F5E5AB12C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift */,
);
name = Onboarding;
sourceTree = "<group>";
Expand All @@ -4422,6 +4432,15 @@
name = Mocks;
sourceTree = "<group>";
};
9F5E5AAA2C3D0FAA00165F54 /* ContextualOnboarding */ = {
isa = PBXGroup;
children = (
9F5E5AAB2C3D0FCD00165F54 /* ContextualDaxDialogsFactory.swift */,
9F5E5AAF2C3E4C6000165F54 /* ContextualOnboardingPresenter.swift */,
);
path = ContextualOnboarding;
sourceTree = "<group>";
};
9FB027102C2526A8009EA190 /* DaxDialogs */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4474,6 +4493,7 @@
9FB027172C26BC0F009EA190 /* BrowsersComparison */,
9FB027102C2526A8009EA190 /* DaxDialogs */,
9F23B7FF2C2BABE000950875 /* OnboardingIntro */,
9F5E5AAA2C3D0FAA00165F54 /* ContextualOnboarding */,
9F23B8002C2BC94400950875 /* OnboardingBackground.swift */,
);
path = OnboardingExperiment;
Expand Down Expand Up @@ -6858,6 +6878,7 @@
F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */,
1EE52ABB28FB1D6300B750C1 /* UIImageExtension.swift in Sources */,
858650D12469BCDE00C36F8A /* DaxDialogs.swift in Sources */,
9F5E5AB02C3E4C6000165F54 /* ContextualOnboardingPresenter.swift in Sources */,
310D091B2799F54900DC0060 /* DownloadManager.swift in Sources */,
98D98A7425ED88D100D8E3DF /* BrowsingMenuEntryViewCell.swift in Sources */,
F1564F032B7B915F00D454A6 /* AppDelegate+SKAD4.swift in Sources */,
Expand All @@ -6872,6 +6893,7 @@
C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */,
4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */,
D63677F52BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift in Sources */,
9F5E5AAE2C3E44DC00165F54 /* ContextualOnboardingBackgroundWrapper.swift in Sources */,
8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */,
CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */,
986B16C425E92DF0007D23E8 /* BrowsingMenuViewController.swift in Sources */,
Expand Down Expand Up @@ -6922,6 +6944,7 @@
F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */,
D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */,
D668D9252B693778008E2FF2 /* SubscriptionITPView.swift in Sources */,
9F5E5AAC2C3D0FCD00165F54 /* ContextualDaxDialogsFactory.swift in Sources */,
C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */,
6FE127382C20492500EB5724 /* NewTabPage.swift in Sources */,
982E5630222C3D5B008D861B /* FeedbackPickerViewController.swift in Sources */,
Expand Down Expand Up @@ -7379,6 +7402,7 @@
C1CDA31E2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift in Sources */,
B6AD9E3A28D456820019CDE9 /* PrivacyConfigurationManagerMock.swift in Sources */,
F189AED71F18F6DE001EBAE1 /* TabTests.swift in Sources */,
9F5E5AB22C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift in Sources */,
F13B4BFB1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift in Sources */,
CB48D3372B90DF2000631D8B /* UserBehaviorMonitorTests.swift in Sources */,
8528AE7E212EF5FF00D0BD74 /* AppRatingPromptTests.swift in Sources */,
Expand Down
3 changes: 2 additions & 1 deletion DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ import WebKit
tabsModel: tabsModel,
syncPausedStateManager: syncErrorHandler,
privacyProDataReporter: privacyProDataReporter,
variantManager: variantManager)
variantManager: variantManager,
contextualOnboardingPresenter: ContextualOnboardingPresenter(variantManager: variantManager))

main.loadViewIfNeeded()
syncErrorHandler.alertPresenter = main
Expand Down
21 changes: 15 additions & 6 deletions DuckDuckGo/Base.lproj/Tab.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@
<rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3yc-Gh-Vqe">
<rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<edgeInsets key="layoutMargins" top="-50" left="0.0" bottom="0.0" right="0.0"/>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Wfg-yB-zj4">
<rect key="frame" x="0.0" y="20" width="768" height="1004"/>
<subviews>
<view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3yc-Gh-Vqe">
<rect key="frame" x="0.0" y="0.0" width="768" height="1004"/>
<edgeInsets key="layoutMargins" top="-50" left="0.0" bottom="0.0" right="0.0"/>
</view>
</subviews>
</stackView>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gSI-9K-1Ti" userLabel="Alert Container View">
<rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
<connections>
Expand Down Expand Up @@ -119,11 +123,15 @@
<constraint firstItem="Kd4-Oi-JP2" firstAttribute="centerY" secondItem="t0t-53-xVf" secondAttribute="centerY" id="3qe-NM-ikO"/>
<constraint firstItem="gSI-9K-1Ti" firstAttribute="leading" secondItem="Sgm-Wo-lho" secondAttribute="leading" id="4ZJ-81-h64"/>
<constraint firstItem="gSI-9K-1Ti" firstAttribute="top" secondItem="Sgm-Wo-lho" secondAttribute="top" id="9Fd-Ru-MPw"/>
<constraint firstItem="Wfg-yB-zj4" firstAttribute="leading" secondItem="t0t-53-xVf" secondAttribute="leading" id="DLc-Mq-QSc"/>
<constraint firstItem="t0t-53-xVf" firstAttribute="top" secondItem="ypz-s2-KJB" secondAttribute="top" constant="80" id="Hdu-Wc-sCB"/>
<constraint firstAttribute="trailing" secondItem="gSI-9K-1Ti" secondAttribute="trailing" id="WIt-GE-l23"/>
<constraint firstItem="Kd4-Oi-JP2" firstAttribute="width" secondItem="Sgm-Wo-lho" secondAttribute="width" id="WgD-oO-Ds7"/>
<constraint firstItem="Wfg-yB-zj4" firstAttribute="top" secondItem="t0t-53-xVf" secondAttribute="top" id="atL-ZI-N6g"/>
<constraint firstAttribute="trailing" secondItem="ypz-s2-KJB" secondAttribute="trailing" id="bXU-Fx-c7D"/>
<constraint firstItem="t0t-53-xVf" firstAttribute="trailing" secondItem="Wfg-yB-zj4" secondAttribute="trailing" id="bpw-ay-E03"/>
<constraint firstItem="Kd4-Oi-JP2" firstAttribute="centerX" secondItem="t0t-53-xVf" secondAttribute="centerX" id="oFL-Ft-NQV"/>
<constraint firstItem="t0t-53-xVf" firstAttribute="bottom" secondItem="Wfg-yB-zj4" secondAttribute="bottom" id="reE-gN-ecB"/>
<constraint firstAttribute="bottom" secondItem="gSI-9K-1Ti" secondAttribute="bottom" id="t3k-1K-dv4"/>
<constraint firstItem="ypz-s2-KJB" firstAttribute="leading" secondItem="Sgm-Wo-lho" secondAttribute="leading" id="ugB-DM-Oye"/>
</constraints>
Expand All @@ -133,6 +141,7 @@
</view>
<nil key="simulatedStatusBarMetrics"/>
<connections>
<outlet property="containerStackView" destination="Wfg-yB-zj4" id="TDY-ap-WRj"/>
<outlet property="error" destination="Kd4-Oi-JP2" id="zBn-cG-pfQ"/>
<outlet property="errorHeader" destination="6P7-7R-riV" id="IkB-Tc-Eat"/>
<outlet property="errorInfoImage" destination="TUO-E3-s7Q" id="ngN-8S-PwR"/>
Expand All @@ -153,7 +162,7 @@
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="1181.5999999999999" y="445.72713643178412"/>
<point key="canvasLocation" x="1181.25" y="445.31249999999994"/>
</scene>
<!--PrivacyDashboard-->
<scene sceneID="L6K-iW-ae0">
Expand Down
27 changes: 19 additions & 8 deletions DuckDuckGo/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ class MainViewController: UIViewController {
tabsModel: TabsModel,
syncPausedStateManager: any SyncPausedStateManaging,
privacyProDataReporter: PrivacyProDataReporting,
variantManager: VariantManager
variantManager: VariantManager,
contextualOnboardingPresenter: ContextualOnboardingPresenting
) {
self.bookmarksDatabase = bookmarksDatabase
self.bookmarksDatabaseCleaner = bookmarksDatabaseCleaner
Expand All @@ -198,7 +199,8 @@ class MainViewController: UIViewController {
bookmarksDatabase: bookmarksDatabase,
historyManager: historyManager,
syncService: syncService,
privacyProDataReporter: privacyProDataReporter)
privacyProDataReporter: privacyProDataReporter,
contextualOnboardingPresenter: contextualOnboardingPresenter)
self.syncPausedStateManager = syncPausedStateManager
self.privacyProDataReporter = privacyProDataReporter
self.homeTabManager = NewTabPageManager()
Expand Down Expand Up @@ -777,18 +779,27 @@ class MainViewController: UIViewController {
}

@IBAction func onFirePressed() {
Pixel.fire(pixel: .forgetAllPressedBrowsing)
wakeLazyFireButtonAnimator()

if let spec = DaxDialogs.shared.fireButtonEducationMessage() {
segueToActionSheetDaxDialogWithSpec(spec)
} else {

func showClearDataAlert() {
let alert = ForgetDataAlert.buildAlert(forgetTabsAndDataHandler: { [weak self] in
self?.forgetAllWithAnimation {}
})
self.present(controller: alert, fromView: self.viewCoordinator.toolbar)
}

Pixel.fire(pixel: .forgetAllPressedBrowsing)
wakeLazyFireButtonAnimator()

if DefaultVariantManager().isSupported(feature: .newOnboardingIntro) {
showClearDataAlert()
} else {
if let spec = DaxDialogs.shared.fireButtonEducationMessage() {
segueToActionSheetDaxDialogWithSpec(spec)
} else {
showClearDataAlert()
}
}

performCancel()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// ContextualOnboardingBackgroundWrapper.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 SwiftUI

struct ContextualOnboardingBackgroundWrapper<Content: View>: View {
private let content: Content

init(_ content: Content) {
self.content = content
}

var body: some View {
content
.padding()
.background(OnboardingBackground())
}
}

#Preview {
ContextualOnboardingBackgroundWrapper(
ContextualDaxDialog(
message: .init(string: "Hello World!!!"),
cta: "OK!",
action: {}
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// ContextualDaxDialogsFactory.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 SwiftUI

protocol ContextualDaxDialogsFactory {
func makeView(for spec: DaxDialogs.BrowsingSpec, onActionTapped: (() -> Void)?) -> UIViewController
}

extension ContextualDaxDialogsFactory {
func makeView(for spec: DaxDialogs.BrowsingSpec) -> UIViewController {
self.makeView(for: spec, onActionTapped: nil)
}
}

// TODO: This will be replaced with the factory that returns the Contextual Dax Dialogs for the new contextual flow
final class ExistingLogicContextualDaxDialogsFactory: ContextualDaxDialogsFactory {

func makeView(for spec: DaxDialogs.BrowsingSpec, onActionTapped: (() -> Void)?) -> UIViewController {
let contextualDialog = ContextualDaxDialog(
message: spec.message.attributedStringFromMarkdown(color: ThemeManager.shared.currentTheme.daxDialogTextColor),
cta: spec.cta,
action: onActionTapped
)

let hostingController = UIHostingController(rootView: ContextualOnboardingBackgroundWrapper(contextualDialog))
if #available(iOS 16.0, *) {
hostingController.sizingOptions = [.intrinsicContentSize]
}

return hostingController
}
}
Loading

0 comments on commit 1e453dd

Please sign in to comment.