Skip to content

Commit a487574

Browse files
authored
Merge pull request #8640 from woocommerce/issue/8636-orders-view-ippiaf-banner
[IPPInAppFeedback] Orders screen banner placement
2 parents f58c9ac + 1190434 commit a487574

File tree

7 files changed

+161
-13
lines changed

7 files changed

+161
-13
lines changed

Storage/Storage/Model/FeedbackType.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@ public enum FeedbackType: String, Codable {
1616
/// Identifier for the orders creation feedback survey
1717
///
1818
case ordersCreation
19+
20+
/// Identifier for the In-Person Payments feedback survey
21+
///
22+
case IPP
1923
}

WooCommerce/Classes/System/WooConstants.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,10 @@ extension WooConstants {
155155
///
156156
#if DEBUG
157157
case inAppFeedback = "https://automattic.survey.fm/woo-app-general-feedback-test-survey"
158+
case IPPFeedback = "https://automattic.survey.fm/woo-app-ipp-in-app-feedback-testing"
158159
#else
159160
case inAppFeedback = "https://automattic.survey.fm/woo-app-general-feedback-user-survey"
161+
case IPPFeedback = "https://automattic.survey.fm/woo-app-ipp-in-app-feedback-testing"
160162
#endif
161163

162164
/// URL for the products feedback survey

WooCommerce/Classes/ViewRelated/Orders/OrderListViewController.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ private extension OrderListViewController {
254254
self.setErrorTopBanner()
255255
case .orderCreation:
256256
self.setOrderCreationTopBanner()
257+
case .IPPFeedback:
258+
self.setIPPFeedbackTopBanner()
257259
}
258260
}
259261
.store(in: &cancellables)
@@ -783,6 +785,37 @@ private extension OrderListViewController {
783785
})
784786
showTopBannerView()
785787
}
788+
789+
/// Sets the `topBannerView` property to an IPP feedback banner.
790+
///
791+
func setIPPFeedbackTopBanner() {
792+
topBannerView = createIPPFeedbackTopBanner()
793+
showTopBannerView()
794+
}
795+
796+
private func createIPPFeedbackTopBanner() -> TopBannerView {
797+
let shareIPPFeedbackAction = TopBannerViewModel.ActionButton(title: Localization.shareFeedbackButton, action: { _ in
798+
self.displayIPPFeedbackBannerSurvey()
799+
})
800+
801+
let viewModel = TopBannerViewModel(
802+
title: Localization.feedbackBannerTitle,
803+
infoText: Localization.feedbackBannerContent,
804+
icon: UIImage.gridicon(.comment),
805+
isExpanded: true,
806+
topButton: .dismiss(handler: { }),
807+
actionButtons: [shareIPPFeedbackAction]
808+
)
809+
let topBannerView = TopBannerView(viewModel: viewModel)
810+
topBannerView.translatesAutoresizingMaskIntoConstraints = false
811+
return topBannerView
812+
}
813+
814+
private func displayIPPFeedbackBannerSurvey() {
815+
// TODO: Survey will change based on conditions
816+
let surveyNavigation = SurveyCoordinatingController(survey: .IPPFeedback)
817+
self.present(surveyNavigation, animated: true, completion: nil)
818+
}
786819
}
787820

788821
// MARK: - Constants
@@ -801,6 +834,18 @@ private extension OrderListViewController {
801834

802835
static let markCompleted = NSLocalizedString("Mark Completed", comment: "Title for the swipe order action to mark it as completed")
803836

837+
static let feedbackBannerTitle = NSLocalizedString("Let us know what you think",
838+
comment: "Title of the In-Person Payments feedback banner in the Orders tab"
839+
)
840+
841+
static let feedbackBannerContent = NSLocalizedString("Rate your In-Person Payment experience.",
842+
comment: "Content of the In-Person Payments feedback banner in the Orders tab"
843+
)
844+
845+
static let shareFeedbackButton = NSLocalizedString("Share feedback",
846+
comment: "Title of the feedback action button on the In-Person Payments feedback banner"
847+
)
848+
804849
static func markCompletedNoticeTitle(orderID: Int64) -> String {
805850
let format = NSLocalizedString(
806851
"Order #%1$d marked as completed",

WooCommerce/Classes/ViewRelated/Orders/OrderListViewModel.swift

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ final class OrderListViewModel {
131131
///
132132
@Published var hideOrdersBanners: Bool = true
133133

134+
/// If true, no IPP feedback banner will be shown as the user has told us that they are not interested in this information.
135+
/// It is persisted through app sessions.
136+
///
137+
@Published var hideIPPFeedbackBanner: Bool = true
138+
134139
init(siteID: Int64,
135140
stores: StoresManager = ServiceLocator.stores,
136141
storageManager: StorageManagerType = ServiceLocator.storageManager,
@@ -143,6 +148,10 @@ final class OrderListViewModel {
143148
self.pushNotificationsManager = pushNotificationsManager
144149
self.notificationCenter = notificationCenter
145150
self.filters = filters
151+
152+
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.IPPInAppFeedbackBanner) && !hideIPPFeedbackBanner {
153+
topBanner = .IPPFeedback
154+
}
146155
}
147156

148157
deinit {
@@ -165,10 +174,12 @@ final class OrderListViewModel {
165174

166175
observeForegroundRemoteNotifications()
167176
bindTopBannerState()
168-
loadOrdersBannerVisibility()
169177

170178
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.IPPInAppFeedbackBanner) {
171-
fetchIPPTransactions()
179+
syncIPPBannerVisibility()
180+
loadOrdersBannerVisibility()
181+
} else {
182+
loadOrdersBannerVisibility()
172183
}
173184
}
174185

@@ -208,6 +219,30 @@ final class OrderListViewModel {
208219
stores.dispatch(action)
209220
}
210221

222+
// This is a temporary method in order to update the IPP feedback status to `.pending`, and
223+
// then load feedback visibility. We need to reset the banner status on UserDefaults for
224+
// the banner to appear again for testing purposes.
225+
private func syncIPPBannerVisibility() {
226+
let action = AppSettingsAction.updateFeedbackStatus(type: .IPP, status: .pending) { _ in
227+
self.loadIPPFeedbackBannerVisibility()
228+
self.fetchIPPTransactions()
229+
}
230+
stores.dispatch(action)
231+
}
232+
233+
private func loadIPPFeedbackBannerVisibility() {
234+
let action = AppSettingsAction.loadFeedbackVisibility(type: .IPP) { [weak self] result in
235+
switch result {
236+
case .success(let visible):
237+
self?.hideIPPFeedbackBanner = !visible
238+
case .failure(let error):
239+
self?.hideIPPFeedbackBanner = true
240+
ServiceLocator.crashLogging.logError(error)
241+
}
242+
}
243+
self.stores.dispatch(action)
244+
}
245+
211246
@objc private func handleAppDeactivation() {
212247
isAppActive = false
213248
}
@@ -365,18 +400,18 @@ extension OrderListViewModel {
365400
private func bindTopBannerState() {
366401
let errorState = $hasErrorLoadingData.removeDuplicates()
367402

368-
Publishers.CombineLatest(errorState, $hideOrdersBanners)
369-
.map { hasError, hasDismissedOrdersBanners -> TopBanner in
403+
Publishers.CombineLatest3(errorState, $hideIPPFeedbackBanner, $hideOrdersBanners)
404+
.map { hasError, hasDismissedIPPFeedbackBanner, hasDismissedOrdersBanners -> TopBanner in
370405

371-
if hasError {
406+
guard !hasError else {
372407
return .error
373408
}
374409

375-
if hasDismissedOrdersBanners {
376-
return .none
410+
guard hasDismissedIPPFeedbackBanner else {
411+
return .IPPFeedback
377412
}
378413

379-
return .orderCreation
414+
return hasDismissedOrdersBanners ? .none : .orderCreation
380415
}
381416
.assign(to: &$topBanner)
382417
}
@@ -419,6 +454,7 @@ extension OrderListViewModel {
419454
enum TopBanner {
420455
case error
421456
case orderCreation
457+
case IPPFeedback
422458
case none
423459
}
424460
}

WooCommerce/Classes/ViewRelated/Survey/SurveyViewController.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ extension SurveyViewController {
6868
case addOnsI1
6969
case orderCreation
7070
case couponManagement
71+
case IPPFeedback
7172

7273
fileprivate var url: URL {
7374
switch self {
@@ -102,22 +103,27 @@ extension SurveyViewController {
102103
.asURL()
103104
.tagPlatform("ios")
104105
.tagAppVersion(Bundle.main.bundleVersion())
106+
case .IPPFeedback:
107+
return WooConstants.URLs.IPPFeedback
108+
.asURL()
109+
.tagPlatform("ios")
110+
.tagAppVersion(Bundle.main.bundleVersion())
105111
}
106112
}
107113

108114
fileprivate var title: String {
109115
switch self {
110116
case .inAppFeedback:
111117
return Localization.title
112-
case .productsFeedback, .shippingLabelsRelease3Feedback, .addOnsI1, .orderCreation, .couponManagement:
118+
case .productsFeedback, .shippingLabelsRelease3Feedback, .addOnsI1, .orderCreation, .couponManagement, .IPPFeedback:
113119
return Localization.giveFeedback
114120
}
115121
}
116122

117123
/// The corresponding `FeedbackContext` for event tracking purposes.
118124
var feedbackContextForEvents: WooAnalyticsEvent.FeedbackContext {
119125
switch self {
120-
case .inAppFeedback:
126+
case .inAppFeedback, .IPPFeedback:
121127
return .general
122128
case .productsFeedback:
123129
return .productsGeneral

WooCommerce/WooCommerceTests/ViewRelated/Orders/OrderListViewModelTests.swift

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ final class OrderListViewModelTests: XCTestCase {
239239
XCTAssertFalse(resynchronizeRequested)
240240
}
241241

242-
func test_when_having_no_error__and_orders_banner_should_not_be_shown_shows_nothing() {
242+
func test_when_having_no_error_and_orders_banner_should_not_be_shown_shows_nothing() {
243243
// Given
244244
let viewModel = OrderListViewModel(siteID: siteID, stores: stores, filters: nil)
245245
stores.whenReceivingAction(ofType: AppSettingsAction.self) { action in
@@ -253,13 +253,36 @@ final class OrderListViewModelTests: XCTestCase {
253253

254254
// When
255255
viewModel.activate()
256+
viewModel.hideIPPFeedbackBanner = true
256257

257258
// Then
258259
waitUntil {
259260
viewModel.topBanner == .none
260261
}
261262
}
262263

264+
func test_when_having_no_error_and_IPP_banner_should_be_shown_shows_IPP_banner() {
265+
// Given
266+
let viewModel = OrderListViewModel(siteID: siteID, stores: stores, filters: nil)
267+
stores.whenReceivingAction(ofType: AppSettingsAction.self) { action in
268+
switch action {
269+
case let .loadFeedbackVisibility(.IPP, onCompletion):
270+
onCompletion(.success(true))
271+
default:
272+
break
273+
}
274+
}
275+
276+
// When
277+
viewModel.activate()
278+
viewModel.hideIPPFeedbackBanner = false
279+
280+
// Then
281+
waitUntil {
282+
viewModel.topBanner == .IPPFeedback
283+
}
284+
}
285+
263286
func test_when_having_no_error_and_orders_banner_should_be_shown_shows_orders_banner() {
264287
// Given
265288
let viewModel = OrderListViewModel(siteID: siteID, stores: stores, filters: nil)
@@ -274,13 +297,44 @@ final class OrderListViewModelTests: XCTestCase {
274297

275298
// When
276299
viewModel.activate()
300+
viewModel.hideIPPFeedbackBanner = true
277301

278302
// Then
279303
waitUntil {
280304
viewModel.topBanner == .orderCreation
281305
}
282306
}
283307

308+
func test_when_having_no_error_and_orders_banner_or_IPP_banner_should_be_shown_shows_correct_banner() {
309+
// Given
310+
let isIPPFeatureFlagEnabled = ServiceLocator.featureFlagService.isFeatureFlagEnabled(.IPPInAppFeedbackBanner)
311+
let viewModel = OrderListViewModel(siteID: siteID, stores: stores, filters: nil)
312+
313+
stores.whenReceivingAction(ofType: AppSettingsAction.self) { action in
314+
switch action {
315+
case let .loadFeedbackVisibility(.ordersCreation, onCompletion):
316+
onCompletion(.success(true))
317+
default:
318+
break
319+
}
320+
}
321+
322+
// When
323+
viewModel.activate()
324+
325+
// Then
326+
if isIPPFeatureFlagEnabled {
327+
viewModel.hideIPPFeedbackBanner = false
328+
waitUntil {
329+
viewModel.topBanner == .IPPFeedback
330+
}
331+
} else {
332+
waitUntil {
333+
viewModel.topBanner == .orderCreation
334+
}
335+
}
336+
}
337+
284338
func test_when_having_no_error_and_orders_banner_visibility_loading_fails_shows_nothing() {
285339
// Given
286340
let viewModel = OrderListViewModel(siteID: siteID, stores: stores, filters: nil)
@@ -317,12 +371,13 @@ final class OrderListViewModelTests: XCTestCase {
317371
}
318372
}
319373

320-
func test_dismissing_orders_banners_does_not_show_banners() {
374+
func test_dismissing_banners_does_not_show_banners() {
321375
// Given
322376
let viewModel = OrderListViewModel(siteID: siteID, stores: stores, filters: nil)
323377

324378
// When
325379
viewModel.activate()
380+
viewModel.hideIPPFeedbackBanner = true
326381
viewModel.hideOrdersBanners = true
327382

328383
// Then

Yosemite/Yosemite/Stores/AppSettings/InAppFeedbackCardVisibilityUseCase.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct InAppFeedbackCardVisibilityUseCase {
3737
switch feedbackType {
3838
case .general:
3939
return try shouldGeneralFeedbackBeVisible(currentDate: currentDate)
40-
case .shippingLabelsRelease3, .couponManagement, .ordersCreation:
40+
case .shippingLabelsRelease3, .couponManagement, .ordersCreation, .IPP:
4141
return settings.feedbackStatus(of: feedbackType) == .pending
4242
}
4343
}

0 commit comments

Comments
 (0)