Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a92bddd
Add IPP banner visibility state as a new publisher
iamgabrielma Jan 17, 2023
8de3516
Add initial dismiss alert using ActionSheet
iamgabrielma Jan 17, 2023
cf03baf
Make UIAlertController style to display as alert
iamgabrielma Jan 17, 2023
232f4bc
Update banner status as dismissed, remind later, or never
iamgabrielma Jan 17, 2023
bcb327e
Merge branch 'trunk' into issue/8589-ipp-banner-dismiss
iamgabrielma Jan 19, 2023
398874b
Remove trailing whitespace
iamgabrielma Jan 19, 2023
c7356c7
Add remindAfter param to setFeatureAnnouncementDismissed action
iamgabrielma Jan 19, 2023
2aa3298
Unit Test new remindAfter param
iamgabrielma Jan 19, 2023
7b68aa7
Fix Line Length Violation
iamgabrielma Jan 19, 2023
523fe0a
Add dismiss upon tapping on survey button
iamgabrielma Jan 19, 2023
70d9cbd
Add missing weak references to self in closures
iamgabrielma Jan 19, 2023
909bea5
Rename method
iamgabrielma Jan 19, 2023
f8a2586
Rename test method for consistency
iamgabrielma Jan 19, 2023
80a1b1b
Move dismiss logic to view model
iamgabrielma Jan 20, 2023
2177584
Modify setFeatureAnnouncementCampaign action
iamgabrielma Jan 20, 2023
d6b1d0a
Simplify dismissIPPFeedbackBanner to use only remindAfterDays
iamgabrielma Jan 20, 2023
cdc5089
Add comment to NSLocalizedString
iamgabrielma Jan 20, 2023
f294478
Resolve storeDismissedSetting with Optional remindAfterDays
iamgabrielma Jan 20, 2023
d5e02a2
Contain dismissal logic to view model only
iamgabrielma Jan 20, 2023
6a53fd6
Rename test for consistency
iamgabrielma Jan 20, 2023
9884f22
Rename dismiss methods to be more explicit
iamgabrielma Jan 20, 2023
914d62c
Merge branch 'trunk' into issue/8589-ipp-banner-dismiss
iamgabrielma Jan 20, 2023
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 @@ -4,6 +4,7 @@ public enum FeatureAnnouncementCampaign: String, Codable, Equatable {
case upsellCardReaders = "upsell_card_readers"
case linkedProductsPromo = "linked_products_promo"
case productsOnboarding = "products_onboarding_first_product"
case IPP = "IPP_feedback_request"

/// Added for use in `test_setFeatureAnnouncementDismissed_with_another_campaign_previously_dismissed_keeps_values_for_both`
/// This can be removed when we have a second campaign, which can be used in the above test instead.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,9 @@ class FeatureAnnouncementCardViewModel: AnnouncementCardViewModelProtocol {
}

private func storeDismissedSetting(remindLater: Bool) {
let remindAfterDays = remindLater ? 0 : nil
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: config.campaign,
remindLater: remindLater,
remindAfterDays: remindAfterDays,
onCompletion: nil)
stores.dispatch(action)
shouldBeVisible = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct ProductsOnboardingAnnouncementCardViewModel: AnnouncementCardViewModelPro
///
func dontShowAgainTapped() {
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .productsOnboarding,
remindLater: false,
remindAfterDays: nil,
onCompletion: nil)
ServiceLocator.stores.dispatch(action)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,16 +794,20 @@ private extension OrderListViewController {
}

private func createIPPFeedbackTopBanner() -> TopBannerView {
let shareIPPFeedbackAction = TopBannerViewModel.ActionButton(title: Localization.shareFeedbackButton, action: { _ in
self.displayIPPFeedbackBannerSurvey()
let shareIPPFeedbackAction = TopBannerViewModel.ActionButton(title: Localization.shareFeedbackButton, action: { [weak self] _ in
self?.displayIPPFeedbackBannerSurvey()
// We dismiss the banner at this point as we cannot know if the user successfully submitted it
self?.viewModel.IPPFeedbackBannerWasDismissed()
})

let viewModel = TopBannerViewModel(
title: Localization.feedbackBannerTitle,
infoText: Localization.feedbackBannerContent,
icon: UIImage.gridicon(.comment),
isExpanded: true,
topButton: .dismiss(handler: { }),
topButton: .dismiss(handler: {
self.showIPPFeedbackDismissAlert()
}),
actionButtons: [shareIPPFeedbackAction]
)
let topBannerView = TopBannerView(viewModel: viewModel)
Expand All @@ -816,6 +820,26 @@ private extension OrderListViewController {
let surveyNavigation = SurveyCoordinatingController(survey: .IPPFeedback)
self.present(surveyNavigation, animated: true, completion: nil)
}

private func showIPPFeedbackDismissAlert() {
let actionSheet = UIAlertController(
title: Localization.dismissTitle,
message: Localization.dismissMessage,
preferredStyle: .alert
)

let remindMeLaterAction = UIAlertAction( title: Localization.remindMeLater, style: .default) { [weak self] _ in
self?.viewModel.IPPFeedbackBannerRemindMeLaterTapped()
}
actionSheet.addAction(remindMeLaterAction)

let dontShowAgainAction = UIAlertAction( title: Localization.dontShowAgain, style: .default) { [weak self] _ in
self?.viewModel.IPPFeedbackBannerDontShowAgainTapped()
}
actionSheet.addAction(dontShowAgainAction)

self.present(actionSheet, animated: true)
}
}

// MARK: - Constants
Expand Down Expand Up @@ -846,6 +870,21 @@ private extension OrderListViewController {
comment: "Title of the feedback action button on the In-Person Payments feedback banner"
)

static let dismissTitle = NSLocalizedString("Give feedback",
comment: "Title of the modal confirmation screen when the In-Person Payments feedback banner is dismissed"
)

static let dismissMessage = NSLocalizedString("No worries! You can always go to Settings in the Menu to send us feedback.",
comment: "Message of the modal confirmation screen when the In-Person Payments feedback banner is dismissed")

static let remindMeLater = NSLocalizedString("Remind me later",
comment: "Title of the button shown when the In-Person Payments feedback banner is dismissed."
)

static let dontShowAgain = NSLocalizedString("Don't show again",
comment: "Title of the button shown when the In-Person Payments feedback banner is dismissed."
)

static func markCompletedNoticeTitle(orderID: Int64) -> String {
let format = NSLocalizedString(
"Order #%1$d marked as completed",
Expand Down
33 changes: 33 additions & 0 deletions WooCommerce/Classes/ViewRelated/Orders/OrderListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,38 @@ final class OrderListViewModel {
}
}

// MARK: - In-Person Payments Feedback Banner

extension OrderListViewModel {
func dismissIPPFeedbackBanner(remindAfterDays: Int?) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice change moving the logic to the view model! However, the remindAfterDays logic can be contained in the view model; the view doesn't need to know about when are we going to show it again. With that, we can remove this parameter remindAfterDays from this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed here the remaining use of remindAfterDays: d5e02a2 to a new method, so this is entirely contained in the view model now 👍

// Updates the IPP feedback banner status as dismissed
let updateFeedbackStatus = AppSettingsAction.updateFeedbackStatus(type: .IPP, status: .dismissed) { [weak self] _ in
self?.hideIPPFeedbackBanner = true
}
stores.dispatch(updateFeedbackStatus)

// Updates the IPP feedback banner status to be reminded later, or never
let updateBannerVisibility = AppSettingsAction.setFeatureAnnouncementDismissed(
campaign: .IPP,
remindAfterDays: remindAfterDays,
onCompletion: nil
)
stores.dispatch(updateBannerVisibility)
}

func IPPFeedbackBannerRemindMeLaterTapped() {
dismissIPPFeedbackBanner(remindAfterDays: Constants.remindIPPBannerDismissalAfterDays)
}

func IPPFeedbackBannerDontShowAgainTapped() {
dismissIPPFeedbackBanner(remindAfterDays: nil)
}

func IPPFeedbackBannerWasDismissed() {
dismissIPPFeedbackBanner(remindAfterDays: nil)
}
}

// MARK: - Remote Notifications Observation

private extension OrderListViewModel {
Expand Down Expand Up @@ -466,5 +498,6 @@ private extension OrderListViewModel {
static let paymentMethodTitle = "WooCommerce In-Person Payments"
static let receiptURLKey = "receipt_url"
static let numberOfTransactions = 10
static let remindIPPBannerDismissalAfterDays = 7
}
}
6 changes: 5 additions & 1 deletion Yosemite/Yosemite/Actions/AppSettingsAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,11 @@ public enum AppSettingsAction: Action {

// MARK: - Feature Announcement Card Visibility

case setFeatureAnnouncementDismissed(campaign: FeatureAnnouncementCampaign, remindLater: Bool, onCompletion: ((Result<Bool, Error>) -> ())?)
case setFeatureAnnouncementDismissed(
campaign: FeatureAnnouncementCampaign,
remindAfterDays: Int?,
onCompletion: ((Result<Bool, Error>) -> ())?
)

case getFeatureAnnouncementVisibility(campaign: FeatureAnnouncementCampaign, onCompletion: (Result<Bool, Error>) -> ())

Expand Down
36 changes: 21 additions & 15 deletions Yosemite/Yosemite/Stores/AppSettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ public class AppSettingsStore: Store {
setCouponManagementFeatureSwitchState(isEnabled: isEnabled, onCompletion: onCompletion)
case .loadCouponManagementFeatureSwitchState(let onCompletion):
loadCouponManagementFeatureSwitchState(onCompletion: onCompletion)
case .setFeatureAnnouncementDismissed(campaign: let campaign, remindLater: let remindLater, onCompletion: let completion):
setFeatureAnnouncementDismissed(campaign: campaign, remindLater: remindLater, onCompletion: completion)
case .setFeatureAnnouncementDismissed(campaign: let campaign, remindAfterDays: let remindAfterDays, onCompletion: let completion):
setFeatureAnnouncementDismissed(campaign: campaign, remindAfterDays: remindAfterDays, onCompletion: completion)
case .getFeatureAnnouncementVisibility(campaign: let campaign, onCompletion: let completion):
getFeatureAnnouncementVisibility(campaign: campaign, onCompletion: completion)
case .setSkippedCashOnDeliveryOnboardingStep(siteID: let siteID):
Expand Down Expand Up @@ -758,20 +758,26 @@ private extension AppSettingsStore {

extension AppSettingsStore {

func setFeatureAnnouncementDismissed(campaign: FeatureAnnouncementCampaign, remindLater: Bool, onCompletion: ((Result<Bool, Error>) -> ())?) {
do {
let remindAfter = remindLater ? Date().addingDays(14) : nil
let newSettings = FeatureAnnouncementCampaignSettings(dismissedDate: Date(), remindAfter: remindAfter)

let settings = generalAppSettings.settings
let settingsToSave = settings.replacing(featureAnnouncementSettings: newSettings, for: campaign)
try generalAppSettings.saveSettings(settingsToSave)

onCompletion?(.success(true))
} catch {
onCompletion?(.failure(error))
func setFeatureAnnouncementDismissed(
campaign: FeatureAnnouncementCampaign,
remindAfterDays: Int?,
onCompletion: ((Result<Bool, Error>) -> ())?) {
do {
guard let remindAfterDays else {
return
}
let remindAfter = Date().addingDays(remindAfterDays)
let newSettings = FeatureAnnouncementCampaignSettings(dismissedDate: Date(), remindAfter: remindAfter)

let settings = generalAppSettings.settings
let settingsToSave = settings.replacing(featureAnnouncementSettings: newSettings, for: campaign)
try generalAppSettings.saveSettings(settingsToSave)

onCompletion?(.success(true))
} catch {
onCompletion?(.failure(error))
}
}
}

func getFeatureAnnouncementVisibility(campaign: FeatureAnnouncementCampaign, onCompletion: (Result<Bool, Error>) -> ()) {
guard let campaignSettings = generalAppSettings.value(for: \.featureAnnouncementCampaignSettings)[campaign] else {
Expand Down
50 changes: 44 additions & 6 deletions Yosemite/YosemiteTests/Stores/AppSettingsStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -841,14 +841,31 @@ final class AppSettingsStoreTests: XCTestCase {

extension AppSettingsStoreTests {

func test_setFeatureAnnouncementDismissed_for_campaign_when_remindAfterDays_is_nil_then_does_not_store_current_date() throws {
// Given
try fileStorage?.deleteFile(at: expectedGeneralStoreSettingsFileURL)
// When
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .upsellCardReaders, remindAfterDays: nil, onCompletion: nil)
subject?.onAction(action)

// Then
var savedSettings: GeneralAppSettings? = try XCTUnwrap(fileStorage?.data(for: expectedGeneralAppSettingsFileURL))
XCTAssertNil(savedSettings)
guard let savedSettings else {
return
}
var savedDate: Date? = try XCTUnwrap( savedSettings.featureAnnouncementCampaignSettings[.upsellCardReaders]?.dismissedDate)
XCTAssertNil(savedDate)
}

func test_setFeatureAnnouncementDismissed_for_campaign_stores_current_date() throws {
// Given
let currentTime = Date()

try fileStorage?.deleteFile(at: expectedGeneralStoreSettingsFileURL)

// When
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .upsellCardReaders, remindLater: false, onCompletion: nil)
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .upsellCardReaders, remindAfterDays: 0, onCompletion: nil)
subject?.onAction(action)

// Then
Expand All @@ -859,14 +876,15 @@ extension AppSettingsStoreTests {
XCTAssert(Calendar.current.isDate(actualDismissDate, inSameDayAs: currentTime))
}

func test_setFeatureAnnouncementDismissed_with_remindLater_true_stores_reminder_date_in_two_weeks() throws {
func test_setFeatureAnnouncementDismissed_when_remindAfterDays_is_two_weeks_then_stores_reminder_date_is_two_weeks() throws {
// Given
let twoWeeksTime = Calendar.current.date(byAdding: .day, value: 14, to: Date())!
let remindAfterDays = 14
let twoWeeksTime = Calendar.current.date(byAdding: .day, value: remindAfterDays, to: Date())!

try fileStorage?.deleteFile(at: expectedGeneralStoreSettingsFileURL)

// When
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .upsellCardReaders, remindLater: true, onCompletion: nil)
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .upsellCardReaders, remindAfterDays: remindAfterDays, onCompletion: nil)
subject?.onAction(action)

// Then
Expand All @@ -877,18 +895,38 @@ extension AppSettingsStoreTests {
XCTAssert(Calendar.current.isDate(actualRemindAfter, inSameDayAs: twoWeeksTime))
}

func test_setFeatureAnnouncementDismissed_when_remindAfterDays_is_seven_days_stores_reminder_then_date_saved_date_is_one_week() throws {
// Given
let remindAfterDays = 7
let oneWeekTime = Calendar.current.date(byAdding: .day, value: remindAfterDays, to: Date())!

try fileStorage?.deleteFile(at: expectedGeneralStoreSettingsFileURL)

// When
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .upsellCardReaders, remindAfterDays: remindAfterDays, onCompletion: nil)
subject?.onAction(action)

// Then
let savedSettings: GeneralAppSettings = try XCTUnwrap(fileStorage?.data(for: expectedGeneralAppSettingsFileURL))

let actualRemindAfter = try XCTUnwrap( savedSettings.featureAnnouncementCampaignSettings[.upsellCardReaders]?.remindAfter)

XCTAssert(Calendar.current.isDate(actualRemindAfter, inSameDayAs: oneWeekTime))
}

func test_setFeatureAnnouncementDismissed_with_another_campaign_previously_dismissed_keeps_values_for_both() throws {
// Given
try fileStorage?.deleteFile(at: expectedGeneralStoreSettingsFileURL)

let currentTime = Date()
let date = Date(timeIntervalSince1970: 100)
let datePrior = Date(timeIntervalSince1970: 100)
let date = Date()

let settings = createAppSettings(featureAnnouncementCampaignSettings: [.test: .init(dismissedDate: date, remindAfter: nil)])
try fileStorage?.write(settings, to: expectedGeneralAppSettingsFileURL)

// When
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .upsellCardReaders, remindLater: false, onCompletion: nil)
let action = AppSettingsAction.setFeatureAnnouncementDismissed(campaign: .upsellCardReaders, remindAfterDays: 0, onCompletion: nil)
subject?.onAction(action)

// Then
Expand Down