Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3949480
[MOB-3897] Create WelcomeView using SwiftUI and replace old Welcome
lucaschifino Oct 22, 2025
550351c
[MOB-3897] Cleanup WelcomeTour classes and assets
lucaschifino Oct 22, 2025
c01626c
[MOB-3897] Fix video player
lucaschifino Nov 6, 2025
c971a30
[MOB-3897] Animated transition from Launch Screen
lucaschifino Nov 7, 2025
549ee31
[MOB-3897] Add some todos
lucaschifino Nov 10, 2025
4474e82
[MOB-3897] Implement exit animation
lucaschifino Nov 10, 2025
33e7909
[MOB-3897] Animate browser toolbars after welcome screen
lucaschifino Nov 10, 2025
222f6f0
[MOB-3897] Adjust logo color and animation
lucaschifino Nov 10, 2025
6f6a402
[MOB-3897] Adjust video frame
lucaschifino Nov 10, 2025
cd2f98a
[MOB-3897] Simplify unnecessary extension
lucaschifino Nov 10, 2025
3270934
[MOB-3897] Add experiment unleash flag
lucaschifino Nov 11, 2025
f70e941
[MOB-3897] Add fade transition to welcome screen
lucaschifino Nov 11, 2025
57a1291
[MOB-3897] Adjust toolbars animation
lucaschifino Nov 11, 2025
69042be
[MOB-3897] Add analytics events
lucaschifino Nov 12, 2025
6831184
[MOB-3897] Make sure onboarding gets presented on iPad
lucaschifino Nov 12, 2025
ac612cd
[MOB-3897] Adjust spacings and offsets
lucaschifino Nov 12, 2025
90ed778
[MOB-3897] Use dynamic welcome text height
lucaschifino Nov 12, 2025
10c62d7
[MOB-3897] Adjust top offset on iPad
lucaschifino Nov 12, 2025
2586618
[MOB-3897] Add line break and adjust margin
lucaschifino Nov 12, 2025
bb336fe
[MOB-3897] Match NTP background on fade transition dismissal
lucaschifino Nov 12, 2025
460d88a
[MOB-3897] Make sure initial logo matches launch screen
lucaschifino Nov 12, 2025
09c7dd3
[MOB-3897] Add gradients behind content for visibility
lucaschifino Nov 12, 2025
6f638a9
[MOB-3897] Fix fade transition
lucaschifino Nov 13, 2025
77597e3
[MOB-3897] Move video player to separate file
lucaschifino Nov 13, 2025
ccb93f2
[MOB-3897] Wait for video to be ready and support reduced motion
lucaschifino Nov 13, 2025
bb1618f
[MOB-3897] Use Task instead of Dispatch
lucaschifino Nov 13, 2025
9798e4a
[MOB-3897] Fix AnalyticsSpyTests
lucaschifino Nov 13, 2025
a2df0be
[MOB-3897] Add text strings
lucaschifino Nov 13, 2025
3cb6198
[MOB-3897] Handle retriggering onAppear
lucaschifino Nov 13, 2025
ccddf74
[MOB-3897] Fix tests mock
lucaschifino Nov 13, 2025
47d5dee
[MOB-3897] Remove spy tests for now
lucaschifino Nov 13, 2025
68fd500
[MOB-3897] Add Ecosia comments
lucaschifino Nov 13, 2025
93c6dd7
[MOB-3897] Wait for feature flags before presenting welcome screen
lucaschifino Nov 14, 2025
c18d321
[MOB-3897] [MOB-3903] Do not show APN consent during experiment
lucaschifino Nov 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public protocol EcosiaSemanticColors {
var textPrimary: UIColor { get }
var textInversePrimary: UIColor { get }
var textSecondary: UIColor { get }
var textStaticLight: UIColor { get }
}

public protocol EcosiaThemeColourPalette: ThemeColourPalette {
Expand Down Expand Up @@ -103,4 +104,5 @@ class FakeEcosiaSemanticColors: EcosiaSemanticColors {
var textPrimary: UIColor = .systemGray
var textInversePrimary: UIColor = .systemGray
var textSecondary: UIColor = .systemGray
var textStaticLight: UIColor = .systemGray
}
64 changes: 32 additions & 32 deletions firefox-ios/Client.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions firefox-ios/Client/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
.preLaunchDependenciesComplete,
.postLaunchDependenciesComplete,
.accountManagerInitialized,
.browserIsReady
.browserIsReady,
// Ecosia: Add Feature Management dependency
.featureManagementInitialized
])

// Then setup dependency container as it's needed for everything else
Expand Down Expand Up @@ -135,7 +137,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
EcosiaInstallType.evaluateCurrentEcosiaInstallType()
// Ecosia: Disable BG sync //backgroundSyncUtil = BackgroundSyncUtil(profile: profile, application: application)

/*
/*
Ecosia: Feature Management fetch
We perform the same configuration retrieval in
`applicationDidBecomeActive(:)` and sounds redundant;
Expand All @@ -148,6 +150,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
*/
Task {
await FeatureManagement.fetchConfiguration()
// Signal that feature management initialization is complete on main thread
AppEventQueue.signal(event: .featureManagementInitialized)
// Ecosia: Braze Service Initialization after feature flags are fetched for conditional initialization
BrazeService.shared.initialize()
// Ecosia: Lifecycle tracking. Needs to happen after Unleash start so that the flags are correctly added to the analytics context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ class BrowserCoordinator: BaseCoordinator,
// MARK: - LaunchCoordinatorDelegate

func didFinishLaunch(from coordinator: LaunchCoordinator) {
/* Ecosia: Animate transition from welcome screen
router.dismiss(animated: true, completion: nil)
*/
router.dismiss(animated: true) { [weak self] in
self?.browserViewController.animateToolbarsIn()
}
remove(child: coordinator)

// Once launch is done, we check for any saved Route
Expand Down
87 changes: 48 additions & 39 deletions firefox-ios/Client/Coordinators/Launch/LaunchCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import Common
import Foundation
import Ecosia

protocol LaunchCoordinatorDelegate: AnyObject {
func didFinishLaunch(from coordinator: LaunchCoordinator)
Expand Down Expand Up @@ -46,48 +47,56 @@ class LaunchCoordinator: BaseCoordinator,
// MARK: - Intro
private func presentIntroOnboarding(with manager: IntroScreenManager,
isFullScreen: Bool) {
/* Ecosia: Disable old onboarding since outdated. Remove and clean up once properly updated.

let onboardingModel = NimbusOnboardingFeatureLayer().getOnboardingModel(for: .freshInstall)
let telemetryUtility = OnboardingTelemetryUtility(with: onboardingModel)
let introViewModel = IntroViewModel(introScreenManager: manager,
profile: profile,
model: onboardingModel,
telemetryUtility: telemetryUtility)
/* Ecosia: custom onboarding
let introViewController = IntroViewController(viewModel: introViewModel, windowUUID: windowUUID)
introViewController.qrCodeNavigationHandler = self
introViewController.didFinishFlow = { [weak self] in
// Wait for feature flags to be available since Onboarding is behind an experiment
AppEventQueue.wait(for: .featureManagementInitialized) { [weak self] in
guard let self = self else { return }
self.parentCoordinator?.didFinishLaunch(from: self)
}
*/
let introViewController = WelcomeNavigation(rootViewController: Welcome(delegate: self, windowUUID: windowUUID))
introViewController.isNavigationBarHidden = true
introViewController.edgesForExtendedLayout = UIRectEdge(rawValue: 0)
if isFullScreen {
introViewController.modalPresentationStyle = .fullScreen
router.present(introViewController, animated: false)
} else {
introViewController.preferredContentSize = CGSize(
width: ViewControllerConsts.PreferredSize.IntroViewController.width,
height: ViewControllerConsts.PreferredSize.IntroViewController.height)
introViewController.modalPresentationStyle = .formSheet
// Disables dismissing the view by tapping outside the view, based on
// Nimbus's configuration
if !introViewModel.isDismissable {
introViewController.isModalInPresentation = true

// Ecosia: Hide onboarding out of experiment
guard OnboardingProductTourExperiment.isEnabled else {
self.parentCoordinator?.didFinishLaunch(from: self)
return
}
/* Ecosia: Remove completion
router.present(introViewController, animated: true) {
introViewController.closeOnboarding()

/* Ecosia: custom onboarding
let onboardingModel = NimbusOnboardingFeatureLayer().getOnboardingModel(for: .freshInstall)
let telemetryUtility = OnboardingTelemetryUtility(with: onboardingModel)
let introViewModel = IntroViewModel(introScreenManager: manager,
profile: profile,
model: onboardingModel,
telemetryUtility: telemetryUtility)

let introViewController = IntroViewController(viewModel: introViewModel, windowUUID: windowUUID)
introViewController.qrCodeNavigationHandler = self
introViewController.didFinishFlow = { [weak self] in
guard let self = self else { return }
self.parentCoordinator?.didFinishLaunch(from: self)
}

if isFullScreen {
introViewController.modalPresentationStyle = .fullScreen
router.present(introViewController, animated: false)
} else {
introViewController.preferredContentSize = CGSize(
width: ViewControllerConsts.PreferredSize.IntroViewController.width,
height: ViewControllerConsts.PreferredSize.IntroViewController.height)
introViewController.modalPresentationStyle = .formSheet
// Disables dismissing the view by tapping outside the view, based on
// Nimbus's configuration
if !introViewModel.isDismissable {
introViewController.isModalInPresentation = true
}
router.present(introViewController, animated: true)
}
*/
router.present(introViewController, animated: true)
*/
let introViewController = WelcomeNavigation(
rootViewController: WelcomeViewController(delegate: self, windowUUID: self.windowUUID),
windowUUID: self.windowUUID
)
introViewController.isNavigationBarHidden = true
introViewController.edgesForExtendedLayout = UIRectEdge(rawValue: 0)
introViewController.modalPresentationStyle = .fullScreen
self.router.present(introViewController, animated: false)
}

*/
parentCoordinator?.didFinishLaunch(from: self)
}

// MARK: - Update
Expand Down Expand Up @@ -186,7 +195,7 @@ class LaunchCoordinator: BaseCoordinator,

// Ecosia: custom onboarding
extension LaunchCoordinator: WelcomeDelegate {
func welcomeDidFinish(_ welcome: Welcome) {
func welcomeDidFinish(_ welcome: WelcomeViewController) {
self.parentCoordinator?.didFinishLaunch(from: self)
}
}
15 changes: 15 additions & 0 deletions firefox-ios/Client/Coordinators/Scene/SceneCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,23 @@ class SceneCoordinator: BaseCoordinator, LaunchCoordinatorDelegate, LaunchFinish
// MARK: - LaunchCoordinatorDelegate

func didFinishLaunch(from coordinator: LaunchCoordinator) {
/* Ecosia: Custom transition
router.dismiss(animated: true)
remove(child: coordinator)
*/
startBrowser(with: nil)

// Ecosia: Animate transition from welcome screen
guard let browserCoordinator = childCoordinators.first(where: { $0 is BrowserCoordinator }) as? BrowserCoordinator else {
router.dismiss(animated: true)
remove(child: coordinator)
return
}

browserCoordinator.browserViewController.prepareToolbarsForWelcomeTransition()
router.dismiss(animated: true) {
browserCoordinator.browserViewController.animateToolbarsIn()
}
remove(child: coordinator)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ extension AppSettingsTableViewController {
OpenFiftyTabsDebugOption(settings: self, settingsDelegate: self),
ToggleDefaultBrowserPromo(settings: self),
ToggleImpactIntro(settings: self),
ShowTour(settings: self, windowUUID: windowUUID),
ShowWelcomeScreen(settings: self, windowUUID: windowUUID),
CreateReferralCode(settings: self),
AddReferral(settings: self),
AddClaim(settings: self),
Expand All @@ -164,6 +164,7 @@ extension AppSettingsTableViewController {
UnleashBrazeIntegrationSetting(settings: self),
UnleashNativeSRPVAnalyticsSetting(settings: self),
UnleashAISearchMVPSetting(settings: self),
UnleashOnboardingSetting(settings: self),
UnleashIdentifierSetting(settings: self),
AnalyticsIdentifierSetting(settings: self)
]
Expand Down
17 changes: 17 additions & 0 deletions firefox-ios/Client/Ecosia/Extensions/Task+Sleep.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 Foundation

extension Task where Success == Never, Failure == Never {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we move this into our Ecosia framework?

/// Sleep for a given duration in seconds, with iOS 15 compatibility
/// When iOS 16+ becomes minimum, this can be replaced with Task.sleep(for: .seconds())
static func sleep(duration: TimeInterval) async throws {
if #available(iOS 16.0, *) {
try await Task.sleep(for: .seconds(duration))
} else {
try await Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))
}
}
}
22 changes: 18 additions & 4 deletions firefox-ios/Client/Ecosia/Settings/EcosiaDebugSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ final class ToggleDefaultBrowserPromo: HiddenSetting {
}
}

final class ShowTour: HiddenSetting, WelcomeDelegate {
final class ShowWelcomeScreen: HiddenSetting, WelcomeDelegate {
override var title: NSAttributedString? {
return NSAttributedString(string: "Debug: Show Intro", attributes: [:])
return NSAttributedString(string: "Debug: Show Welcome Screen", attributes: [:])
}

let windowUUID: WindowUUID
Expand All @@ -75,7 +75,7 @@ final class ShowTour: HiddenSetting, WelcomeDelegate {

var parentPresenter: UIViewController?
override func onClick(_ navigationController: UINavigationController?) {
let welcome = Welcome(delegate: self, windowUUID: windowUUID)
let welcome = WelcomeViewController(delegate: self, windowUUID: windowUUID)
welcome.modalPresentationStyle = .fullScreen
welcome.modalTransitionStyle = .coverVertical
let presentingViewController = navigationController?.presentingViewController
Expand All @@ -84,7 +84,7 @@ final class ShowTour: HiddenSetting, WelcomeDelegate {
}
}

func welcomeDidFinish(_ welcome: Welcome) {
func welcomeDidFinish(_ welcome: WelcomeViewController) {
if let presentedTour = welcome.presentedViewController {
presentedTour.dismiss(animated: true) {
welcome.dismiss(animated: true)
Expand Down Expand Up @@ -289,6 +289,20 @@ final class UnleashAISearchMVPSetting: UnleashVariantResetSetting {
}
}

final class UnleashOnboardingSetting: UnleashVariantResetSetting {
override var titleName: String? {
"Onboarding Product Tour"
}

override var variant: Unleash.Variant? {
Unleash.getVariant(.onboardingProductTour)
}

override var unleashEnabled: Bool? {
Unleash.isEnabled(.onboardingProductTour)
}
}

final class AnalyticsIdentifierSetting: HiddenSetting {
override var title: NSAttributedString? {
return NSAttributedString(string: "Debug: Analytics Identifier", attributes: [:])
Expand Down

This file was deleted.

Binary file not shown.
Binary file not shown.

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.

This file was deleted.

This file was deleted.

Binary file not shown.
Binary file not shown.

This file was deleted.

Binary file not shown.
Loading
Loading