Skip to content

Commit 7d701b2

Browse files
Merge pull request #19571 from wordpress-mobile/task/19447-phase-1
Jetpack Focus: Display phase one overlays for Stats, Reader, and Notifications
2 parents d2e849b + 6426d1a commit 7d701b2

18 files changed

+667
-36
lines changed

WordPress/Classes/ViewRelated/Jetpack/Branding/Coordinator/JetpackFeaturesRemovalCoordinator.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ class JetpackFeaturesRemovalCoordinator {
1111
case three
1212
case four
1313
case newUsers
14+
15+
var frequencyConfig: JetpackOverlayFrequencyTracker.FrequencyConfig {
16+
switch self {
17+
case .one:
18+
fallthrough
19+
case .two:
20+
return .init(featureSpecificInDays: 7, generalInDays: 2)
21+
case .three:
22+
return .init(featureSpecificInDays: 4, generalInDays: 1)
23+
default:
24+
return .defaultConfig
25+
}
26+
}
1427
}
1528

1629
/// Enum descibing the current phase of the site creation flow removal
@@ -20,6 +33,15 @@ class JetpackFeaturesRemovalCoordinator {
2033
case two
2134
}
2235

36+
enum OverlaySource: String {
37+
case stats
38+
case notifications
39+
case reader
40+
case card
41+
case login
42+
case appOpen = "app_open"
43+
}
44+
2345
static func generalPhase(featureFlagStore: RemoteFeatureFlagStore = RemoteFeatureFlagStore()) -> GeneralPhase {
2446
if AppConfiguration.isJetpack {
2547
return .normal // Always return normal for Jetpack
@@ -61,4 +83,27 @@ class JetpackFeaturesRemovalCoordinator {
6183

6284
return .normal
6385
}
86+
87+
/// Used to display feature-specific or feature-collection overlays.
88+
/// - Parameters:
89+
/// - source: The source that triggers the display of the overlay.
90+
/// - viewController: View controller where the overlay should be presented in.
91+
static func presentOverlayIfNeeded(from source: OverlaySource, in viewController: UIViewController) {
92+
let phase = generalPhase()
93+
let frequencyConfig = phase.frequencyConfig
94+
let config = JetpackFullscreenOverlayGeneralConfig(phase: phase, source: source)
95+
let frequencyTracker = JetpackOverlayFrequencyTracker(frequencyConfig: frequencyConfig, source: source)
96+
guard config.shouldShowOverlay, frequencyTracker.shouldShow() else {
97+
return
98+
}
99+
createAndPresentOverlay(with: config, in: viewController)
100+
frequencyTracker.track()
101+
}
102+
103+
private static func createAndPresentOverlay(with config: JetpackFullscreenOverlayConfig, in viewController: UIViewController) {
104+
let overlay = JetpackFullscreenOverlayViewController(with: config)
105+
let navigationViewController = UINavigationController(rootViewController: overlay)
106+
navigationViewController.modalPresentationStyle = .formSheet
107+
viewController.present(navigationViewController, animated: true)
108+
}
64109
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import Foundation
2+
3+
class JetpackOverlayFrequencyTracker {
4+
5+
private let frequencyConfig: FrequencyConfig
6+
private let source: JetpackFeaturesRemovalCoordinator.OverlaySource
7+
private let persistenceStore: UserPersistentRepository
8+
9+
private var lastSavedGenericDate: Date? {
10+
get {
11+
let key = Constants.lastDateKeyPrefix
12+
return persistenceStore.object(forKey: key) as? Date
13+
}
14+
set {
15+
let key = Constants.lastDateKeyPrefix
16+
persistenceStore.set(newValue, forKey: key)
17+
}
18+
}
19+
20+
private var lastSavedSourceDate: Date? {
21+
get {
22+
let sourceKey = "\(Constants.lastDateKeyPrefix)-\(source.rawValue)"
23+
return persistenceStore.object(forKey: sourceKey) as? Date
24+
}
25+
set {
26+
let sourceKey = "\(Constants.lastDateKeyPrefix)-\(source.rawValue)"
27+
persistenceStore.set(newValue, forKey: sourceKey)
28+
}
29+
}
30+
31+
init(frequencyConfig: FrequencyConfig = .defaultConfig,
32+
source: JetpackFeaturesRemovalCoordinator.OverlaySource,
33+
persistenceStore: UserPersistentRepository = UserDefaults.standard) {
34+
self.frequencyConfig = frequencyConfig
35+
self.source = source
36+
self.persistenceStore = persistenceStore
37+
}
38+
39+
func shouldShow() -> Bool {
40+
switch source {
41+
case .stats:
42+
fallthrough
43+
case .notifications:
44+
fallthrough
45+
case .reader:
46+
return frequenciesPassed()
47+
case .card:
48+
return true
49+
case .login:
50+
fallthrough
51+
case .appOpen:
52+
return lastSavedSourceDate == nil
53+
}
54+
}
55+
56+
func track() {
57+
let date = Date()
58+
lastSavedSourceDate = date
59+
lastSavedGenericDate = date
60+
}
61+
62+
private func frequenciesPassed() -> Bool {
63+
guard let lastSavedGenericDate = lastSavedGenericDate else {
64+
return true // First overlay ever
65+
}
66+
let secondsSinceLastSavedGenericDate = -lastSavedGenericDate.timeIntervalSinceNow
67+
let generalFreqPassed = secondsSinceLastSavedGenericDate > frequencyConfig.generalInSeconds
68+
if generalFreqPassed == false {
69+
return false // An overlay was shown recently so we can't show one now
70+
}
71+
72+
guard let lastSavedSourceDate = lastSavedSourceDate else {
73+
return true // This specific overlay was never shown, so we can show it
74+
}
75+
76+
let secondsSinceLastSavedSourceDate = -lastSavedSourceDate.timeIntervalSinceNow
77+
let featureSpecificFreqPassed = secondsSinceLastSavedSourceDate > frequencyConfig.featureSpecificInSeconds
78+
// Check if this specific overlay was shown recently
79+
return featureSpecificFreqPassed
80+
}
81+
}
82+
83+
extension JetpackOverlayFrequencyTracker {
84+
struct FrequencyConfig {
85+
// MARK: Instance Variables
86+
let featureSpecificInDays: Int
87+
let generalInDays: Int
88+
89+
// MARK: Static Variables
90+
static let defaultConfig = FrequencyConfig(featureSpecificInDays: 0, generalInDays: 0)
91+
private static let secondsInDay: TimeInterval = 86_400
92+
93+
// MARK: Computed Variables
94+
var featureSpecificInSeconds: TimeInterval {
95+
return TimeInterval(featureSpecificInDays) * Self.secondsInDay
96+
}
97+
98+
var generalInSeconds: TimeInterval {
99+
return TimeInterval(generalInDays) * Self.secondsInDay
100+
}
101+
}
102+
103+
enum Constants {
104+
static let lastDateKeyPrefix = "JetpackOverlayLastDate"
105+
}
106+
}

WordPress/Classes/ViewRelated/Jetpack/Branding/Overlay/JetpackFullscreenOverlayConfig.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import Foundation
22

3-
struct JetpackFullscreenOverlayConfig {
4-
let title: String
5-
let subtitle: String
6-
let animationLtr: String
7-
let animationRtl: String
8-
let footnote: String?
9-
let shouldShowLearnMoreButton: Bool
10-
let switchButtonText: String
11-
let continueButtonText: String?
12-
let shouldShowCloseButton: Bool
13-
let analyticsSource: String
3+
/// Protocol used to configure `JetpackFullscreenOverlayViewController`
4+
protocol JetpackFullscreenOverlayConfig {
5+
var title: String { get }
6+
var subtitle: String { get }
7+
var animationLtr: String { get }
8+
var animationRtl: String { get }
9+
var footnote: String? { get }
10+
var shouldShowLearnMoreButton: Bool { get }
11+
var switchButtonText: String { get }
12+
var continueButtonText: String? { get }
13+
var shouldShowCloseButton: Bool { get }
14+
var analyticsSource: String { get }
1415
}
1516

1617
extension JetpackFullscreenOverlayConfig {

0 commit comments

Comments
 (0)