Skip to content

Commit

Permalink
Fix brave#1416: Add user onboarding.
Browse files Browse the repository at this point in the history
  • Loading branch information
iccub committed Aug 20, 2019
1 parent f44d41e commit f6d2369
Show file tree
Hide file tree
Showing 10 changed files with 600 additions and 0 deletions.
10 changes: 10 additions & 0 deletions BraveShared/BraveStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,13 @@ extension Strings {
public static let QuickActionNewPrivateTab = NSLocalizedString("ShortcutItemTitleNewPrivateTab", tableName: "BraveShared", bundle: Bundle.braveShared, value: "New Private Tab", comment: "Quick Action for 3D-Touch on the Application Icon")
public static let QuickActionScanQRCode = NSLocalizedString("ShortcutItemTitleQRCode", tableName: "BraveShared", bundle: Bundle.braveShared, value: "Scan QR Code", comment: "Quick Action for 3D-Touch on the Application Icon")
}

// MARK: - Onboarding
extension Strings {
public static let OBContinueButton = NSLocalizedString("OnboardingContinueButton", bundle: Bundle.shared, value: "Continue", comment: "Continue button to navigate to next onboarding screen.")
public static let OBSkipButton = NSLocalizedString("OnboardingSkipButton", bundle: Bundle.shared, value: "Skip", comment: "Skip button to skip onboarding and start using the app.")
public static let OBSearchEngineTitle = NSLocalizedString("OBSearchEngineTitle", bundle: Bundle.shared, value: "Welcome to Brave Browser", comment: "Title for search engine onboarding screen")
public static let OBSearchEngineDetail = NSLocalizedString("OBSearchEngineDetail", bundle: Bundle.shared, value: "Select your default search engine", comment: "Detail text for search engine onboarding screen")
public static let OBShieldsTitle = NSLocalizedString("OBShieldsTitle", bundle: Bundle.shared, value: "Brave Shields", comment: "Title for shields onboarding screen")
public static let OBShieldsDetail = NSLocalizedString("OBShieldsDetail", bundle: Bundle.shared, value: "Block privacy-invading trackers so you can browse without being followed around the web", comment: "Detail text for shields onboarding screen")
}
32 changes: 32 additions & 0 deletions Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
0A1E84462190A57F0042F782 /* SyncAddDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A1E843C2190A57F0042F782 /* SyncAddDeviceViewController.swift */; };
0A2F921C22146B7700304249 /* FrecencyQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2F921B22146B7700304249 /* FrecencyQuery.swift */; };
0A39D56D21930B89008B2772 /* ScriptOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A39D56C21930B89008B2772 /* ScriptOpener.swift */; };
0A3C789D23055C4A0022F6D8 /* OnboardingSearchEnginesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3C789C23055C4A0022F6D8 /* OnboardingSearchEnginesViewController.swift */; };
0A3C789F23056C910022F6D8 /* OnboardingSearchEnginesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3C789E23056C910022F6D8 /* OnboardingSearchEnginesView.swift */; };
0A3C78A1230597DA0022F6D8 /* OnboardingShieldsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3C78A0230597DA0022F6D8 /* OnboardingShieldsViewController.swift */; };
0A3C78A3230597F10022F6D8 /* OnboardingShieldsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3C78A2230597F10022F6D8 /* OnboardingShieldsView.swift */; };
0A4214E921A6EBCF006B8E39 /* SafeBrowsingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4214E821A6EBCF006B8E39 /* SafeBrowsingTests.swift */; };
0A4214FF21AC0D6C006B8E39 /* TimeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4214F621AC0AFF006B8E39 /* TimeExtensions.swift */; };
0A42150121AC0E8E006B8E39 /* TimeExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A42150021AC0E8E006B8E39 /* TimeExtensionTests.swift */; };
Expand All @@ -48,6 +52,8 @@
0A4BEFDA221EF3360005551A /* NetworkResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4BEFD9221EF3360005551A /* NetworkResourceType.swift */; };
0A4BEFDE221F03C80005551A /* NetworkManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4BEFDD221F03C80005551A /* NetworkManagerTests.swift */; };
0A53F3E721E6560A0086E80C /* InMemoryDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A53F3E621E6560A0086E80C /* InMemoryDataController.swift */; };
0A6112AC230B00E7001BBC45 /* OnboardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6112AB230B00E7001BBC45 /* OnboardingNavigationController.swift */; };
0A6112BD230B4306001BBC45 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6112BC230B4306001BBC45 /* OnboardingViewController.swift */; };
0A771D35218C8FDC00336E0D /* Bookmark+Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A771D34218C8FDC00336E0D /* Bookmark+Sync.swift */; };
0A7B5D6722689C5D00AADF22 /* AddEditHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7B5D6622689C5D00AADF22 /* AddEditHeaderView.swift */; };
0A7B5D702269E72C00AADF22 /* BookmarkSaveLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7B5D6F2269E72C00AADF22 /* BookmarkSaveLocation.swift */; };
Expand Down Expand Up @@ -1170,6 +1176,10 @@
0A2F921B22146B7700304249 /* FrecencyQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrecencyQuery.swift; sourceTree = "<group>"; };
0A38EA7F216532DB00142710 /* CRUDProtocolsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRUDProtocolsTests.swift; sourceTree = "<group>"; };
0A39D56C21930B89008B2772 /* ScriptOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptOpener.swift; sourceTree = "<group>"; };
0A3C789C23055C4A0022F6D8 /* OnboardingSearchEnginesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSearchEnginesViewController.swift; sourceTree = "<group>"; };
0A3C789E23056C910022F6D8 /* OnboardingSearchEnginesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSearchEnginesView.swift; sourceTree = "<group>"; };
0A3C78A0230597DA0022F6D8 /* OnboardingShieldsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingShieldsViewController.swift; sourceTree = "<group>"; };
0A3C78A2230597F10022F6D8 /* OnboardingShieldsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingShieldsView.swift; sourceTree = "<group>"; };
0A4214E821A6EBCF006B8E39 /* SafeBrowsingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeBrowsingTests.swift; sourceTree = "<group>"; };
0A4214F621AC0AFF006B8E39 /* TimeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeExtensions.swift; sourceTree = "<group>"; };
0A42150021AC0E8E006B8E39 /* TimeExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeExtensionTests.swift; sourceTree = "<group>"; };
Expand All @@ -1188,6 +1198,8 @@
0A4BEFD9221EF3360005551A /* NetworkResourceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResourceType.swift; sourceTree = "<group>"; };
0A4BEFDD221F03C80005551A /* NetworkManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManagerTests.swift; sourceTree = "<group>"; };
0A53F3E621E6560A0086E80C /* InMemoryDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemoryDataController.swift; sourceTree = "<group>"; };
0A6112AB230B00E7001BBC45 /* OnboardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationController.swift; sourceTree = "<group>"; };
0A6112BC230B4306001BBC45 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
0A6231E32121F761007B429B /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0A771D34218C8FDC00336E0D /* Bookmark+Sync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmark+Sync.swift"; sourceTree = "<group>"; };
0A7B5D6622689C5D00AADF22 /* AddEditHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditHeaderView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2481,6 +2493,19 @@
path = Sync;
sourceTree = "<group>";
};
0A3C789423055AC80022F6D8 /* Onboarding */ = {
isa = PBXGroup;
children = (
0A6112BC230B4306001BBC45 /* OnboardingViewController.swift */,
0A6112AB230B00E7001BBC45 /* OnboardingNavigationController.swift */,
0A3C789C23055C4A0022F6D8 /* OnboardingSearchEnginesViewController.swift */,
0A3C789E23056C910022F6D8 /* OnboardingSearchEnginesView.swift */,
0A3C78A0230597DA0022F6D8 /* OnboardingShieldsViewController.swift */,
0A3C78A2230597F10022F6D8 /* OnboardingShieldsView.swift */,
);
path = Onboarding;
sourceTree = "<group>";
};
0A4319B321B1C4D60041625B /* Adblock */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -3747,6 +3772,7 @@
D3A994941A368691008AD1AC /* Browser */ = {
isa = PBXGroup;
children = (
0A3C789423055AC80022F6D8 /* Onboarding */,
C6D267612137E45D00465DFA /* PrivacyProtection */,
C6D267592137E39A00465DFA /* ImageCache */,
0AADC4BB20D2A4F700FDE368 /* HomePanel */,
Expand Down Expand Up @@ -5685,6 +5711,7 @@
E4A960061ABB9C450069AD6F /* ReaderModeUtils.swift in Sources */,
0A1E843E2190A57F0042F782 /* SyncCodewordsView.swift in Sources */,
E68F36981EA694000048CF44 /* PanelDataObservers.swift in Sources */,
0A3C789F23056C910022F6D8 /* OnboardingSearchEnginesView.swift in Sources */,
31ADB5DA1E58CEC300E87909 /* ClipboardBarDisplayHandler.swift in Sources */,
0A4B012020D02EC4004D4011 /* TabsBarViewController.swift in Sources */,
4422D4D921BFFB7600BF1855 /* merger.cc in Sources */,
Expand Down Expand Up @@ -5722,6 +5749,7 @@
C615FAD9212A1E2600A8168C /* WebImageCache.swift in Sources */,
C615FAED212ACAD200A8168C /* BraveWebView.swift in Sources */,
C817B34D1FC609500086018E /* UIScrollViewSwizzled.swift in Sources */,
0A3C78A3230597F10022F6D8 /* OnboardingShieldsView.swift in Sources */,
4422D4E221BFFB7600BF1855 /* format.cc in Sources */,
39F819C61FD70F5D009E31E4 /* TabEventHandlers.swift in Sources */,
4422D50B21BFFB7600BF1855 /* memenv.cc in Sources */,
Expand Down Expand Up @@ -5768,6 +5796,7 @@
0A0D3D5221A565C300BEE65B /* SafeBrowsingHandler.swift in Sources */,
D0C95E36200FDC5500E4E51C /* MetadataParserHelper.swift in Sources */,
4422D55B21BFFB7F00BF1855 /* onepass.cc in Sources */,
0A3C78A1230597DA0022F6D8 /* OnboardingShieldsViewController.swift in Sources */,
A1CDF22D20BDDB66005C6E58 /* BasicAnimationController.swift in Sources */,
0BF1B7E31AC60DEA00A7B407 /* InsetButton.swift in Sources */,
D0C95EF6201A55A800E4E51C /* BrowserViewController+UIDropInteractionDelegate.swift in Sources */,
Expand Down Expand Up @@ -5866,6 +5895,7 @@
D03F8EB22004014E003C2224 /* FaviconHandler.swift in Sources */,
0AB2442C22AA789B00B4D9DD /* ReaderModeButton.swift in Sources */,
4422D55E21BFFB7F00BF1855 /* mimics_pcre.cc in Sources */,
0A6112AC230B00E7001BBC45 /* OnboardingNavigationController.swift in Sources */,
4422D57321BFFB7F00BF1855 /* stringpiece.cc in Sources */,
27187808216526090006036E /* AlertPopupView.swift in Sources */,
4422D56421BFFB7F00BF1855 /* re2.cc in Sources */,
Expand All @@ -5888,6 +5918,7 @@
D02816C21ECA5E2A00240CAA /* HistoryStateHelper.swift in Sources */,
0AEFB84922244135007AF600 /* AdblockDebugMenuTableViewController.swift in Sources */,
E68E7ACB1CAC1D4500FDCA76 /* PagingPasscodeViewController.swift in Sources */,
0A6112BD230B4306001BBC45 /* OnboardingViewController.swift in Sources */,
D04D1B92209790B60074B35F /* Toast.swift in Sources */,
D34DC8531A16C40C00D49B7B /* Profile.swift in Sources */,
0A93F1892264C72000A3571B /* BookmarkFormFieldsProtocol.swift in Sources */,
Expand All @@ -5910,6 +5941,7 @@
4422D56521BFFB7F00BF1855 /* filtered_re2.cc in Sources */,
279C756B219DDE3B001CD1CB /* FingerprintingProtection.swift in Sources */,
E650755F1E37F756006961AC /* Try.m in Sources */,
0A3C789D23055C4A0022F6D8 /* OnboardingSearchEnginesViewController.swift in Sources */,
3B6889C51D66950E002AC85E /* UIImageColors.swift in Sources */,
0A1E84402190A57F0042F782 /* SyncSelectDeviceTypeViewController.swift in Sources */,
392ED7E41D0AEF56009D9B62 /* NewTabAccessors.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions Client/Application/ClientPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ extension Preferences {
///
/// Currently unused.
static let showClipboardBar = Option<Bool>(key: "general.show-clipboard-bar", default: false)
/// Whether or not user onboarding has completed.
/// User skipping onboarding counts as completed too.
static let onboardingCompleted = Option<Bool>(key: "general.onboarding-completed", default: false)
}
final class Search {
/// Whether or not to show suggestions while the user types
Expand Down
19 changes: 19 additions & 0 deletions Client/Frontend/Browser/BrowserViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,8 @@ class BrowserViewController: UIViewController {
}

override func viewDidAppear(_ animated: Bool) {
presentOnboardingIntro()

screenshotHelper.viewIsVisible = true
screenshotHelper.takePendingScreenshots(tabManager.allTabs)

Expand Down Expand Up @@ -623,6 +625,16 @@ class BrowserViewController: UIViewController {
}
}
}

func presentOnboardingIntro() {
if Preferences.General.onboardingCompleted.value { return }

guard let onboarding = OnboardingNavigationController(profile: profile) else { return }
onboarding.onboardingDelegate = self


present(onboarding, animated: true)
}

// THe logic for shouldShowWhatsNewTab is as follows: If we do not have the LatestAppVersionProfileKey in
// the profile, that means that this is a fresh install and we do not show the What's New. If we do have
Expand Down Expand Up @@ -3105,3 +3117,10 @@ extension BrowserViewController {
}
}
}

extension BrowserViewController: OnboardingControllerDelegate {
func onboardingCompleted(_ onboardingController: OnboardingNavigationController) {
Preferences.General.onboardingCompleted.value = true
onboardingController.dismiss(animated: true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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 UIKit
import Shared

private let log = Logger.browserLogger

protocol Onboardable: class {
/// Show next on boarding screen if possible.
/// If last screen is currently presenting, the view is dimissed instead(onboarding finished).
func presentNextScreen(current: OnboardingViewController)
/// Skip all onboarding screens, onboarding is considered as completed.
func skip()
}

protocol OnboardingControllerDelegate: class {
func onboardingCompleted(_ onboardingController: OnboardingNavigationController)
}

class OnboardingNavigationController: UINavigationController {

private struct UX {
/// The onboarding screens are showing as a modal on iPads.
static let preferredModalSize = CGSize(width: 375, height: 667)
}

weak var onboardingDelegate: OnboardingControllerDelegate?

enum Screens: CaseIterable {
case searchEnginePicker
case shieldsInfo

func viewController(with profile: Profile) -> OnboardingViewController {
switch self {
case .searchEnginePicker:
return OnboardingSearchEnginesViewController(profile: profile)
case .shieldsInfo:
return OnboardingShieldsViewController(profile: profile)
}
}

var type: AnyClass {
switch self {
case .searchEnginePicker: return OnboardingSearchEnginesViewController.self
case .shieldsInfo: return OnboardingShieldsViewController.self
}
}
}

init?(profile: Profile) {
guard let firstScreen = Screens.allCases.first else { return nil }

let firstViewController = firstScreen.viewController(with: profile)
super.init(rootViewController: firstViewController)
firstViewController.delegate = self

isNavigationBarHidden = true
if UIDevice.current.userInterfaceIdiom == .phone {
modalPresentationStyle = .fullScreen
} else {
modalPresentationStyle = .formSheet
}

if #available(iOS 13.0, *) {
// Prevent dismissing the modal by swipe
isModalInPresentation = true
}
preferredContentSize = UX.preferredModalSize
}

@available(*, unavailable)
required init(coder: NSCoder) { fatalError() }

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}

extension OnboardingNavigationController: Onboardable {

func presentNextScreen(current: OnboardingViewController) {
let allScreens = Screens.allCases
let index = allScreens.map { $0.type }.firstIndex(where: { $0 == type(of: current) })

guard let nextIndex = index?.advanced(by: 1),
let nextScreen = allScreens[safe: nextIndex]?.viewController(with: current.profile) else {
log.info("Last screen reached, onboarding is complete")
onboardingDelegate?.onboardingCompleted(self)
return
}

nextScreen.delegate = self

pushViewController(nextScreen, animated: true)
}

func skip() {
onboardingDelegate?.onboardingCompleted(self)
}
}

// Disabling orientation changes
extension OnboardingNavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .default
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}

override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .portrait
}

override var shouldAutorotate: Bool {
return false
}
}
Loading

0 comments on commit f6d2369

Please sign in to comment.