Skip to content

Commit 6011ff8

Browse files
Merge pull request #19785 from wordpress-mobile/task/19450-phase-3-card-actions
Jetpack Focus: Implements actions inside the Jetpack menu card
2 parents b60f7e7 + c7bfa99 commit 6011ff8

File tree

10 files changed

+334
-56
lines changed

10 files changed

+334
-56
lines changed

WordPress/Classes/Stores/RemoteConfigStore.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ class RemoteConfigStore {
2525

2626
// MARK: Public Functions
2727

28+
/// Looks up the value for a remote config parameter.
29+
/// - Parameters:
30+
/// - key: The key associated with a remote config parameter
31+
public func value(for key: String) -> Any? {
32+
return cache[key]
33+
}
34+
2835
/// Fetches remote config values from the server.
2936
/// - Parameter callback: An optional callback that can be used to update UI following the fetch. It is not called on the UI thread.
3037
public func update(then callback: FetchCallback? = nil) {
@@ -49,7 +56,7 @@ extension RemoteConfigStore {
4956
typealias FetchCallback = () -> Void
5057

5158
/// The local cache stores remote config values between runs so that the most recently fetched set are ready to go as soon as this object is instantiated.
52-
private(set) var cache: [String: Any] {
59+
private var cache: [String: Any] {
5360
get {
5461
// Read from the cache in a thread-safe way
5562
queue.sync {

WordPress/Classes/Utility/BuildInformation/RemoteConfigParameter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct RemoteConfigParameter<T> {
1212
private let store: RemoteConfigStore
1313

1414
private var serverValue: T? {
15-
return store.cache[key] as? T
15+
return store.value(for: key) as? T
1616
}
1717

1818
// MARK: Initializer

WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@ - (void)setRestorableSelectedIndexPath:(NSIndexPath *)restorableSelectedIndexPat
615615
switch (section.category) {
616616
case BlogDetailsSectionCategoryQuickAction:
617617
case BlogDetailsSectionCategoryQuickStart:
618+
case BlogDetailsSectionCategoryJetpackBrandingCard:
618619
case BlogDetailsSectionCategoryDomainCredit: {
619620
_restorableSelectedIndexPath = nil;
620621
}
@@ -700,6 +701,7 @@ - (void)reloadTableViewPreservingSelection
700701
switch (section.category) {
701702
case BlogDetailsSectionCategoryQuickAction:
702703
case BlogDetailsSectionCategoryQuickStart:
704+
case BlogDetailsSectionCategoryJetpackBrandingCard:
703705
case BlogDetailsSectionCategoryDomainCredit: {
704706
BlogDetailsSubsection subsection = [self shouldShowDashboard] ? BlogDetailsSubsectionHome : BlogDetailsSubsectionStats;
705707
BlogDetailsSectionCategory category = [self sectionCategoryWithSubsection:subsection blog: self.blog];
@@ -742,7 +744,7 @@ - (void)configureTableViewData
742744
if (MigrationSuccessCardView.shouldShowMigrationSuccessCard == YES) {
743745
[marr addObject:[self migrationSuccessSectionViewModel]];
744746
}
745-
if (JetpackBrandingMenuCardCoordinator.shouldShowCard == YES) {
747+
if (self.shouldShowJetpackBrandingMenuCard == YES) {
746748
[marr addObject:[self jetpackCardSectionViewModel]];
747749
}
748750

WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayGeneralViewModel.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ struct JetpackFullscreenOverlayGeneralViewModel: JetpackFullscreenOverlayViewMod
7070
return Strings.PhaseTwoAndThree.notificationsTitle
7171
case (.three, .reader):
7272
return Strings.PhaseTwoAndThree.readerTitle
73+
case (.three, _):
74+
return Strings.PhaseThree.generalTitle
7375
default:
7476
return ""
7577
}
@@ -110,7 +112,7 @@ struct JetpackFullscreenOverlayGeneralViewModel: JetpackFullscreenOverlayViewMod
110112
case .login:
111113
fallthrough
112114
case .appOpen:
113-
return "" // TODO: Add new animation when ready
115+
return Constants.allFeaturesLogosAnimationLtr
114116
}
115117
}
116118

@@ -127,7 +129,7 @@ struct JetpackFullscreenOverlayGeneralViewModel: JetpackFullscreenOverlayViewMod
127129
case .login:
128130
fallthrough
129131
case .appOpen:
130-
return "" // TODO: Add new animation when ready
132+
return Constants.allFeaturesLogosAnimationRtl
131133
}
132134
}
133135

@@ -175,13 +177,15 @@ struct JetpackFullscreenOverlayGeneralViewModel: JetpackFullscreenOverlayViewMod
175177
}
176178

177179
var continueButtonText: String? {
178-
switch source {
179-
case .stats:
180+
switch (source, phase) {
181+
case (.stats, _):
180182
return Strings.General.statsContinueButtonTitle
181-
case .notifications:
183+
case (.notifications, _):
182184
return Strings.General.notificationsContinueButtonTitle
183-
case .reader:
185+
case (.reader, _):
184186
return Strings.General.readerContinueButtonTitle
187+
case (_, .three):
188+
return Strings.PhaseThree.generalContinueButtonTitle
185189
default:
186190
return nil
187191
}
@@ -242,6 +246,8 @@ private extension JetpackFullscreenOverlayGeneralViewModel {
242246
static let readerLogoAnimationRtl = "JetpackReaderLogoAnimation_rtl"
243247
static let notificationsLogoAnimationLtr = "JetpackNotificationsLogoAnimation_ltr"
244248
static let notificationsLogoAnimationRtl = "JetpackNotificationsLogoAnimation_rtl"
249+
static let allFeaturesLogosAnimationLtr = "JetpackAllFeaturesLogosAnimation_ltr"
250+
static let allFeaturesLogosAnimationRtl = "JetpackAllFeaturesLogosAnimation_rtl"
245251
}
246252

247253
enum Strings {
@@ -314,5 +320,15 @@ private extension JetpackFullscreenOverlayGeneralViewModel {
314320
value: "Switching is free and only takes a minute.",
315321
comment: "A footnote in a screen displayed when the user accesses a Jetpack powered feature from the WordPress app. The screen showcases the Jetpack app.")
316322
}
323+
324+
enum PhaseThree {
325+
static let generalTitle = NSLocalizedString("jetpack.fullscreen.overlay.phaseThree.general.title",
326+
value: "Jetpack features are moving soon.",
327+
comment: "Title of a screen that showcases the Jetpack app.")
328+
329+
static let generalContinueButtonTitle = NSLocalizedString("jetpack.fullscreen.overlay.phaseThree.general.continue.title",
330+
value: "Continue without Jetpack",
331+
comment: "Title of a button that dismisses an overlay that showcases the Jetpack app.")
332+
}
317333
}
318334
}

WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/BlogDetailsViewController+JetpackBrandingMenuCard.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,26 @@ import Foundation
22

33
extension BlogDetailsViewController {
44

5+
@objc var shouldShowJetpackBrandingMenuCard: Bool {
6+
let presenter = JetpackBrandingMenuCardPresenter()
7+
return presenter.shouldShowCard()
8+
}
9+
510
@objc func jetpackCardSectionViewModel() -> BlogDetailsSection {
611
let row = BlogDetailsRow()
7-
row.callback = {}
12+
row.callback = {
13+
JetpackFeaturesRemovalCoordinator.presentOverlayIfNeeded(from: .card, in: self)
14+
}
815

916
let section = BlogDetailsSection(title: nil,
1017
rows: [row],
1118
footerTitle: nil,
1219
category: .jetpackBrandingCard)
1320
return section
1421
}
22+
23+
func reloadTableView() {
24+
configureTableViewData()
25+
reloadTableViewPreservingSelection()
26+
}
1527
}

WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ class JetpackBrandingMenuCardCell: UITableViewCell {
55

66
// MARK: Private Variables
77

8-
private weak var viewController: UIViewController?
8+
private weak var viewController: BlogDetailsViewController?
9+
private var presenter: JetpackBrandingMenuCardPresenter
910

1011
/// Sets the animation based on the language orientation
1112
private var animation: Animation? {
@@ -119,11 +120,13 @@ class JetpackBrandingMenuCardCell: UITableViewCell {
119120
// MARK: Initializers
120121

121122
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
123+
presenter = JetpackBrandingMenuCardPresenter()
122124
super.init(style: style, reuseIdentifier: reuseIdentifier)
123125
commonInit()
124126
}
125127

126128
required init?(coder: NSCoder) {
129+
presenter = JetpackBrandingMenuCardPresenter()
127130
super.init(coder: coder)
128131
commonInit()
129132
}
@@ -144,15 +147,15 @@ class JetpackBrandingMenuCardCell: UITableViewCell {
144147

145148
private func setupContent() {
146149
logosAnimationView.play()
147-
let config = JetpackBrandingMenuCardCoordinator.cardConfig
150+
let config = presenter.cardConfig()
148151
descriptionLabel.text = config?.description
149152
learnMoreSuperview.isHidden = config?.learnMoreButtonURL == nil
150153
}
151154

152155
// MARK: Actions
153156

154157
@objc private func learnMoreButtonTapped() {
155-
guard let config = JetpackBrandingMenuCardCoordinator.cardConfig,
158+
guard let config = presenter.cardConfig(),
156159
let urlString = config.learnMoreButtonURL,
157160
let url = URL(string: urlString) else {
158161
return
@@ -186,11 +189,15 @@ private extension JetpackBrandingMenuCardCell {
186189
// MARK: Actions
187190

188191
private func remindMeLaterTapped() {
189-
// TODO: Implement this
192+
presenter.remindLaterTapped()
193+
viewController?.reloadTableView()
194+
// TODO: Track button tapped
190195
}
191196

192197
private func hideThisTapped() {
193-
// TODO: Implement this
198+
presenter.hideThisTapped()
199+
viewController?.reloadTableView()
200+
// TODO: Track button tapped
194201
}
195202
}
196203

@@ -276,7 +283,7 @@ private extension JetpackBrandingMenuCardCell {
276283
extension JetpackBrandingMenuCardCell {
277284

278285
@objc(configureWithViewController:)
279-
func configure(with viewController: UIViewController) {
286+
func configure(with viewController: BlogDetailsViewController) {
280287
self.viewController = viewController
281288
}
282289
}

WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCoordinator.swift

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import Foundation
2+
3+
class JetpackBrandingMenuCardPresenter {
4+
5+
struct Config {
6+
let description: String
7+
let learnMoreButtonURL: String?
8+
}
9+
10+
// MARK: Private Variables
11+
12+
private let remoteConfigStore: RemoteConfigStore
13+
private let persistenceStore: UserPersistentRepository
14+
private let featureFlagStore: RemoteFeatureFlagStore
15+
private let currentDateProvider: CurrentDateProvider
16+
17+
// MARK: Initializers
18+
19+
init(remoteConfigStore: RemoteConfigStore = RemoteConfigStore(),
20+
featureFlagStore: RemoteFeatureFlagStore = RemoteFeatureFlagStore(),
21+
persistenceStore: UserPersistentRepository = UserDefaults.standard,
22+
currentDateProvider: CurrentDateProvider = DefaultCurrentDateProvider()) {
23+
self.remoteConfigStore = remoteConfigStore
24+
self.featureFlagStore = featureFlagStore
25+
self.persistenceStore = persistenceStore
26+
self.currentDateProvider = currentDateProvider
27+
}
28+
29+
// MARK: Public Functions
30+
31+
func cardConfig() -> Config? {
32+
let phase = JetpackFeaturesRemovalCoordinator.generalPhase(featureFlagStore: featureFlagStore)
33+
switch phase {
34+
case .three:
35+
let description = Strings.phaseThreeDescription
36+
let url = RemoteConfig(store: remoteConfigStore).phaseThreeBlogPostUrl.value
37+
return .init(description: description, learnMoreButtonURL: url)
38+
default:
39+
return nil
40+
}
41+
}
42+
43+
func shouldShowCard() -> Bool {
44+
let showCardOnDate = showCardOnDate ?? .distantPast // If not set, then return distant past so that the condition below always succeeds
45+
guard shouldHideCard == false, // Card not hidden
46+
showCardOnDate < currentDateProvider.date(), // Interval has passed if temporarily hidden
47+
let _ = cardConfig() else { // Card is enabled in the current phase
48+
return false
49+
}
50+
return true
51+
}
52+
53+
func remindLaterTapped() {
54+
let now = currentDateProvider.date()
55+
let duration = Constants.remindLaterDurationInDays * Constants.secondsInDay
56+
let newDate = now.addingTimeInterval(TimeInterval(duration))
57+
showCardOnDate = newDate
58+
}
59+
60+
func hideThisTapped() {
61+
shouldHideCard = true
62+
}
63+
}
64+
65+
private extension JetpackBrandingMenuCardPresenter {
66+
var shouldHideCard: Bool {
67+
get {
68+
persistenceStore.bool(forKey: Constants.shouldHideCardKey)
69+
}
70+
71+
set {
72+
persistenceStore.set(newValue, forKey: Constants.shouldHideCardKey)
73+
}
74+
}
75+
76+
var showCardOnDate: Date? {
77+
get {
78+
persistenceStore.object(forKey: Constants.showCardOnDateKey) as? Date
79+
}
80+
81+
set {
82+
persistenceStore.set(newValue, forKey: Constants.showCardOnDateKey)
83+
}
84+
}
85+
}
86+
87+
private extension JetpackBrandingMenuCardPresenter {
88+
enum Constants {
89+
static let secondsInDay = 86_400
90+
static let remindLaterDurationInDays = 4
91+
static let shouldHideCardKey = "JetpackBrandingShouldHideCardKey"
92+
static let showCardOnDateKey = "JetpackBrandingShowCardOnDateKey"
93+
}
94+
95+
enum Strings {
96+
static let phaseThreeDescription = NSLocalizedString("jetpack.menuCard.description",
97+
value: "Stats, Reader, Notifications and other features will move to the Jetpack mobile app soon.",
98+
comment: "Description inside a menu card communicating that features are moving to the Jetpack app.")
99+
}
100+
}

0 commit comments

Comments
 (0)