Skip to content

Commit

Permalink
[MOB-2917] APN Consent on launch Experiment (#800)
Browse files Browse the repository at this point in the history
* Create APNConsentOnLaunchExperiment and request on launch if needed

* Temporarily point to Core's feature branch

* Point back to Core main branch

* Add debug setting

* Handle conflicting experiments

* Always initialize Braze independent of experiment

* Make isEnabled private

* Remove unnecessary notification status retrieval
  • Loading branch information
lucaschifino authored Oct 25, 2024
1 parent a50e6b1 commit 209977f
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 40 deletions.
4 changes: 4 additions & 0 deletions Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
0BF1B7E31AC60DEA00A7B407 /* InsetButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BF1B7E21AC60DEA00A7B407 /* InsetButton.swift */; };
12187B502C89B43D00BA88CA /* OnboardingCardNTPExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12187B4F2C89B43D00BA88CA /* OnboardingCardNTPExperiment.swift */; };
12187B532C89E15000BA88CA /* NTPOnboardingCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12187B522C89E15000BA88CA /* NTPOnboardingCardCell.swift */; };
1285E2B72CC68BF00053506B /* APNConsentOnLaunchExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1285E2B62CC68BF00053506B /* APNConsentOnLaunchExperiment.swift */; };
158241282820698B00956B39 /* RustRemoteTabsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 158241272820698B00956B39 /* RustRemoteTabsTests.swift */; };
15DE98FD27FCED4F00F1ECDB /* RustRemoteTabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15DE98FC27FCED4F00F1ECDB /* RustRemoteTabs.swift */; };
1B3D99F1270E89D0006E1264 /* Telemetry in Frameworks */ = {isa = PBXBuildFile; productRef = 1B3D99F0270E89D0006E1264 /* Telemetry */; };
Expand Down Expand Up @@ -2023,6 +2024,7 @@
12674A038346A46589A0AC0B /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = "el.lproj/Default Browser.strings"; sourceTree = "<group>"; };
126A40A4A5AFDFD655B0FDF4 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/ClearHistoryConfirm.strings; sourceTree = "<group>"; };
126F44CCB14373DC7813DE1F /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/ClearPrivateDataConfirm.strings; sourceTree = "<group>"; };
1285E2B62CC68BF00053506B /* APNConsentOnLaunchExperiment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APNConsentOnLaunchExperiment.swift; sourceTree = "<group>"; };
12EA4881BFBE296298150D4A /* bn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bn; path = bn.lproj/LoginManager.strings; sourceTree = "<group>"; };
12F949169C30744CCC749588 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/HistoryPanel.strings; sourceTree = "<group>"; };
1323403C8071FC19BB79C191 /* su */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = su; path = su.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8713,6 +8715,7 @@
2C6188F82B7A8A22006B70D7 /* EngagementServiceExperiment.swift */,
12187B4F2C89B43D00BA88CA /* OnboardingCardNTPExperiment.swift */,
2C6B5B402CAAF3AA00F15323 /* SeedCounterNTPExperiment.swift */,
1285E2B62CC68BF00053506B /* APNConsentOnLaunchExperiment.swift */,
);
path = Unleash;
sourceTree = "<group>";
Expand Down Expand Up @@ -13895,6 +13898,7 @@
8A2783F1275FFDC50080D29D /* KeyboardPressesHandler.swift in Sources */,
E650755C1E37F747006961AC /* Swizzling.m in Sources */,
74821FC51DB56A2500EEEA72 /* OpenWithSettingsViewController.swift in Sources */,
1285E2B72CC68BF00053506B /* APNConsentOnLaunchExperiment.swift in Sources */,
C84656012887A0F700861B4A /* WallpaperMetadataUtility.swift in Sources */,
E1B04A9D28E20A8300670E54 /* InstructionsView.swift in Sources */,
D3B6923D1B9F9444004B87A4 /* FindInPageBar.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"location" : "https://github.com/ecosia/ios-core.git",
"state" : {
"branch" : "main",
"revision" : "0112c7295cda3b2d62a9365ea7aae05164108d53"
"revision" : "b2624be79f04a6f78ffbf37100ab75a422cef0b5"
}
},
{
Expand Down
4 changes: 3 additions & 1 deletion Client/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Ecosia: Lifecycle tracking. Needs to happen after Unleash start so that the flags are correctly added to the analytics context.
analytics.activity(.launch)
// Ecosia: Engagement Service Initialization helper
ClientEngagementService.shared.initializeAndUpdateNotificationRegistrationIfNeeded(notificationCenterDelegate: self)
await ClientEngagementService.shared.initializeAndRefreshNotificationRegistration(notificationCenterDelegate: self)
// Ecosia: Experiment that directly asks for consent
await APNConsentOnLaunchExperiment.requestAPNConsentIfNeeded(delegate: self)
}

// Ecosia: fetching statistics before they are used
Expand Down
6 changes: 6 additions & 0 deletions Client/Ecosia/Analytics/Analytics.Values.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ extension Analytics {
disable,
home

enum APNConsent: String {
case
home,
onLaunchExperiment = "on_launch_experiment"
}

enum Bookmarks: String {
case
`import`,
Expand Down
66 changes: 38 additions & 28 deletions Client/Ecosia/Analytics/Analytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,33 +70,6 @@ final class Analytics: AnalyticsProtocol {
track(event)
}

/// Sends the analytics event for a given action
/// The function is EngagementService agnostic e.g. doesn't have context
/// of the engagement service being used (i.e. `Braze`)
/// but it does get the `Toggle.Name` from the one
/// defined in the `APNConsentUIExperiment`
/// so to leverage decoupling.
func apnConsent(_ action: Action.APNConsent) {
let event = Structured(category: Category.pushNotificationConsent.rawValue,
action: action.rawValue)
.label("\(User.shared.apnConsentReminderModel.optInScreenCount)")
.property(Property.home.rawValue)

// When the user sees the APNConsent
// we add the number of search counts as value of the event
if action == .view {
event.value = NSNumber(integerLiteral: User.shared.searchCount)
}

// Add context (if any) from current EngagementService enabled
if let toggleName = Unleash.Toggle.Name(rawValue: EngagementServiceExperiment.toggleName),
let context = Self.getTestContext(from: toggleName) {
event.entities.append(context)
}

track(event)
}

// MARK: Bookmarks
func bookmarksPerformImportExport(_ property: Property.Bookmarks) {
let event = Structured(category: Category.bookmarks.rawValue,
Expand Down Expand Up @@ -241,7 +214,7 @@ final class Analytics: AnalyticsProtocol {
.value(value)
)
}

// MARK: Onboarding
func introDisplaying(page: Property.OnboardingPage?, at index: Int) {
guard let page else {
Expand All @@ -266,6 +239,43 @@ final class Analytics: AnalyticsProtocol {
track(event)
}

// MARK: Push Notifications Consent

/// Sends the analytics event for a given action
/// The function is EngagementService agnostic e.g. doesn't have context
/// of the engagement service being used (i.e. `Braze`)
/// but it does get the `Toggle.Name` from the one
/// defined in the `APNConsentUIExperiment`
/// so to leverage decoupling.
func apnConsent(_ action: Action.APNConsent) {
let event = Structured(category: Category.pushNotificationConsent.rawValue,
action: action.rawValue)
.label("\(User.shared.apnConsentReminderModel.optInScreenCount)")
.property(Property.APNConsent.home.rawValue)

// When the user sees the APNConsent
// we add the number of search counts as value of the event
if action == .view {
event.value = NSNumber(integerLiteral: User.shared.searchCount)
}

// Add context (if any) from current EngagementService enabled
if let toggleName = Unleash.Toggle.Name(rawValue: EngagementServiceExperiment.toggleName),
let context = Self.getTestContext(from: toggleName) {
event.entities.append(context)
}

track(event)
}

func apnConsentOnLaunchExperiment(_ action: Action.APNConsent) {
let event = Structured(category: Category.pushNotificationConsent.rawValue,
action: action.rawValue)
.property(Property.APNConsent.onLaunchExperiment.rawValue)
addABTestContexts(to: event, toggles: [APNConsentOnLaunchExperiment.toggleName])
track(event)
}

// MARK: Referrals
func referral(action: Action.Referral, label: Label.Referral? = nil) {
track(Structured(category: Category.invitations.rawValue,
Expand Down
15 changes: 5 additions & 10 deletions Client/Ecosia/EngagementService/EngagementService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ final class ClientEngagementService {
parameters["id"] as? String
}

func initialize(parameters: [String: Any]) {
func initialize(parameters: [String: Any]) async {
do {
try service.initialize(parameters: parameters)
self.parameters = parameters
Task {
await retrieveUserCurrentNotificationAuthStatus()
}
await retrieveUserCurrentNotificationAuthStatus()
} catch {
debugPrint(error)
}
Expand Down Expand Up @@ -49,12 +47,9 @@ final class ClientEngagementService {

extension ClientEngagementService {

func initializeAndUpdateNotificationRegistrationIfNeeded(notificationCenterDelegate: UNUserNotificationCenterDelegate) {
guard EngagementServiceExperiment.isEnabled else { return }
initialize(parameters: ["id": User.shared.analyticsId.uuidString])
Task.detached {
await self.refreshAPNRegistrationIfNeeded(notificationCenterDelegate: notificationCenterDelegate)
}
func initializeAndRefreshNotificationRegistration(notificationCenterDelegate: UNUserNotificationCenterDelegate) async {
await initialize(parameters: ["id": User.shared.analyticsId.uuidString])
await refreshAPNRegistrationIfNeeded(notificationCenterDelegate: notificationCenterDelegate)
}

func retrieveUserCurrentNotificationAuthStatus() async {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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
import Core

struct APNConsentOnLaunchExperiment {
private init() {}

static var toggleName: Unleash.Toggle.Name {
.apnConsentOnLaunch
}

private static var isEnabled: Bool {
// Make sure that the other engagement service experiment is not enabled since they are conflicting
Unleash.isEnabled(toggleName) && !Unleash.isEnabled(.braze)
}

static func requestAPNConsentIfNeeded(delegate: UNUserNotificationCenterDelegate) async {
guard isEnabled, ClientEngagementService.shared.notificationAuthorizationStatus == .notDetermined else {
return
}
Analytics.shared.apnConsentOnLaunchExperiment(.view)
ClientEngagementService.shared.requestAPNConsent(notificationCenterDelegate: delegate) { granted, error in
Analytics.shared.apnConsentOnLaunchExperiment(granted ? .allow : .deny)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ extension AppSettingsTableViewController {
EngagementServiceIdentifierSetting(settings: self),
FasterInactiveTabs(settings: self, settingsDelegate: self),
UnleashOnboardingCardNTPSetting(settings: self),
UnleashAPNConsentOnLaunchSetting(settings: self),
UnleashSeedCounterNTPSetting(settings: self)
]

Expand Down
10 changes: 10 additions & 0 deletions Client/Ecosia/Settings/EcosiaDebugSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,16 @@ final class UnleashOnboardingCardNTPSetting: UnleashVariantResetSetting {
}
}

final class UnleashAPNConsentOnLaunchSetting: UnleashVariantResetSetting {
override var titleName: String? {
"APN Consent On Launch"
}

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

final class EngagementServiceIdentifierSetting: HiddenSetting {
override var title: NSAttributedString? {
return NSAttributedString(string: "Debug: Engagement Service Identifier parameter", attributes: [NSAttributedString.Key.foregroundColor: UIColor.legacyTheme.tableView.rowText])
Expand Down

0 comments on commit 209977f

Please sign in to comment.