Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions Experiments/Experiments/DefaultFeatureFlagService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
return true
case .systemStatusReportInSupportRequest:
return true
case .IPPInAppFeedbackBanner:
return buildConfig == .localDeveloper || buildConfig == .alpha
case .performanceMonitoring,
.performanceMonitoringCoreData,
.performanceMonitoringFileIO,
Expand Down
4 changes: 4 additions & 0 deletions Experiments/Experiments/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ public enum FeatureFlag: Int {
///
case systemStatusReportInSupportRequest

/// IPP in-app feedback banner
///
case IPPInAppFeedbackBanner

// MARK: - Performance Monitoring
//
// These flags are not transient. That is, they are not here to help us rollout a feature,
Expand Down
7 changes: 5 additions & 2 deletions WooCommerce/Classes/System/WooConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,12 @@ extension WooConstants {
/// URL for in-app feedback survey
///
#if DEBUG
case inAppFeedback = "https://automattic.survey.fm/woo-app-general-feedback-test-survey"
case generalFeedback = "https://automattic.survey.fm/woo-app-general-feedback-test-survey"
case IPPFeedback = "https://automattic.survey.fm/woo-app-ipp-in-app-feedback-testing"
#else
case inAppFeedback = "https://automattic.survey.fm/woo-app-general-feedback-user-survey"
case generalFeedback = "https://automattic.survey.fm/woo-app-general-feedback-user-survey"
// TODO: Create the production survey
case IPPFeedback = "https://automattic.survey.fm/woo-app-ipp-in-app-feedback-testing"
#endif

/// URL for the products feedback survey
Expand Down
11 changes: 10 additions & 1 deletion WooCommerce/Classes/Tools/Notices/DefaultNoticePresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class DefaultNoticePresenter: NoticePresenter {
///
private var keyboardFrameObserver: KeyboardFrameObserver?

/// Notices are dismissed automatically by default, unless set otherwise
///
var shouldDismissAutomatically: Bool? = true

/// Enqueues the specified Notice for display.
///
@discardableResult
Expand Down Expand Up @@ -171,7 +175,12 @@ private extension DefaultNoticePresenter {
}

animatePresentation(fromState: offScreenState, toState: onScreenState, completion: {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Animations.dismissDelay, execute: dismiss)
guard let shouldDismissAutomatically = self.shouldDismissAutomatically else {
return
}
if shouldDismissAutomatically {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Animations.dismissDelay, execute: dismiss)
}
})
}

Expand Down
7 changes: 6 additions & 1 deletion WooCommerce/Classes/Tools/Notices/Notice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ struct Notice {
///
let actionTitle: String?

/// An optional handler closure that will be called when the notice is tapped
///
var noticeTappedHandler: (() -> Void)?

/// An optional handler closure that will be called when the action button is tapped, if you've provided an action title
///
let actionHandler: (() -> Void)?


/// Designated Initializer
///
init(title: String,
Expand All @@ -44,13 +47,15 @@ struct Notice {
feedbackType: UINotificationFeedbackGenerator.FeedbackType? = nil,
notificationInfo: NoticeNotificationInfo? = nil,
actionTitle: String? = nil,
noticeTappedHandler: ((() -> Void))? = nil,
actionHandler: ((() -> Void))? = nil) {
self.title = title
self.subtitle = subtitle
self.message = message
self.feedbackType = feedbackType
self.notificationInfo = notificationInfo
self.actionTitle = actionTitle
self.noticeTappedHandler = noticeTappedHandler
self.actionHandler = actionHandler
}
}
Expand Down
3 changes: 3 additions & 0 deletions WooCommerce/Classes/Tools/Notices/NoticeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ private extension NoticeView {
if let subtitle = notice.subtitle {
subtitleLabel.isHidden = false
subtitleLabel.text = subtitle
// TODO: Remove color, just for testing:
subtitleLabel.textColor = .red
} else {
subtitleLabel.isHidden = true
}
Expand All @@ -193,6 +195,7 @@ private extension NoticeView {
private extension NoticeView {

@objc private func viewTapped() {
notice.noticeTappedHandler?()
dismissHandler?()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ final class InPersonPaymentsMenuViewController: UIViewController {
configureTableReload()
runCardPresentPaymentsOnboarding()
configureWebViewPresentation()
if featureFlagService.isFeatureFlagEnabled(.IPPInAppFeedbackBanner) {
createIPPFeedbackNoticeBanner()
}
viewModel.viewDidLoad()
}
}
Expand Down Expand Up @@ -334,6 +337,32 @@ private extension InPersonPaymentsMenuViewController {
self.navigationController?.show(connectionController, sender: nil)
}.store(in: &cancellables)
}

private func createIPPFeedbackNoticeBanner() {
let notice = Notice(
title: Localization.feedbackNoticeBannerTitle,
subtitle: Localization.shareFeedbackNoticeBannerButton,
actionTitle: "x", // Experimenting with dismiss.
noticeTappedHandler: {
print("Notice tapped")
self.displayFeedbackSurvey()
},
actionHandler: {
print("Dismiss tapped")
}
)

let noticePresenter = DefaultNoticePresenter()
noticePresenter.presentingViewController = self
noticePresenter.shouldDismissAutomatically = false
noticePresenter.enqueue(notice: notice)
}

private func displayFeedbackSurvey() {
// TODO: Different surveys will be shown:
let surveyNavigation = SurveyCoordinatingController(survey: .IPPFeedback)
self.present(surveyNavigation, animated: true, completion: nil)
}
}

// MARK: - Convenience methods
Expand Down Expand Up @@ -525,6 +554,16 @@ private extension InPersonPaymentsMenuViewController {
"Continue setup",
comment: "Call to Action to finish the setup of In-Person Payments in the Menu"
)

static let feedbackNoticeBannerTitle = NSLocalizedString(
"Do you sell in person?",
comment: "Title of the feedback notice banner in the Payments tab"
)

static let shareFeedbackNoticeBannerButton = NSLocalizedString(
"Share Feedback",
comment: "Title of the feedback action button on the feedback notice banner"
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ private extension SettingsViewController {
}

func presentSurveyForFeedback() {
let surveyNavigation = SurveyCoordinatingController(survey: .inAppFeedback)
let surveyNavigation = SurveyCoordinatingController(survey: .generalFeedback)
present(surveyNavigation, animated: true, completion: nil)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private extension InAppFeedbackCardViewController {
return
}

let surveyNavigation = SurveyCoordinatingController(survey: .inAppFeedback)
let surveyNavigation = SurveyCoordinatingController(survey: .generalFeedback)
self.present(surveyNavigation, animated: true, completion: nil)
self.onFeedbackGiven?()
self.analytics.track(event: .appFeedbackPrompt(action: .didntLike))
Expand Down
18 changes: 12 additions & 6 deletions WooCommerce/Classes/ViewRelated/Survey/SurveyViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,18 @@ final class SurveyViewController: UIViewController, SurveyViewControllerOutputs
//
extension SurveyViewController {
enum Source {
case inAppFeedback
case generalFeedback
case productsFeedback
case shippingLabelsRelease3Feedback
case addOnsI1
case orderCreation
case couponManagement
case IPPFeedback

fileprivate var url: URL {
switch self {
case .inAppFeedback:
return WooConstants.URLs.inAppFeedback
case .generalFeedback:
return WooConstants.URLs.generalFeedback
.asURL()
.tagPlatform("ios")
.tagAppVersion(Bundle.main.bundleVersion())
Expand Down Expand Up @@ -102,22 +103,27 @@ extension SurveyViewController {
.asURL()
.tagPlatform("ios")
.tagAppVersion(Bundle.main.bundleVersion())
case .IPPFeedback:
return WooConstants.URLs.IPPFeedback
.asURL()
.tagPlatform("ios")
.tagAppVersion(Bundle.main.bundleVersion())
}
}

fileprivate var title: String {
switch self {
case .inAppFeedback:
case .generalFeedback:
return Localization.title
case .productsFeedback, .shippingLabelsRelease3Feedback, .addOnsI1, .orderCreation, .couponManagement:
case .productsFeedback, .shippingLabelsRelease3Feedback, .addOnsI1, .orderCreation, .couponManagement, .IPPFeedback:
return Localization.giveFeedback
}
}

/// The corresponding `FeedbackContext` for event tracking purposes.
var feedbackContextForEvents: WooAnalyticsEvent.FeedbackContext {
switch self {
case .inAppFeedback:
case .generalFeedback, .IPPFeedback:
return .general
case .productsFeedback:
return .productsGeneral
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class SurveyCoordinatingControllerTests: XCTestCase {
let factory = MockSurveyViewControllersFactory()

// When
let coordinator = SurveyCoordinatingController(survey: .inAppFeedback, viewControllersFactory: factory)
let coordinator = SurveyCoordinatingController(survey: .generalFeedback, viewControllersFactory: factory)

// Then
XCTAssertTrue(coordinator.topViewController is SurveyViewControllerOutputs)
Expand All @@ -37,7 +37,7 @@ final class SurveyCoordinatingControllerTests: XCTestCase {
func test_it_navigates_to_SurveySubmittedViewController_when_survey_is_submitted() throws {
// Given
let factory = MockSurveyViewControllersFactory()
let coordinator = SurveyCoordinatingController(survey: .inAppFeedback, viewControllersFactory: factory)
let coordinator = SurveyCoordinatingController(survey: .generalFeedback, viewControllersFactory: factory)

// When
factory.surveyViewController.onCompletion()
Expand All @@ -51,7 +51,7 @@ final class SurveyCoordinatingControllerTests: XCTestCase {
func test_it_gets_dismissed_on_backToStore_action() throws {
// Given
let factory = MockSurveyViewControllersFactory()
let coordinator = SurveyCoordinatingController(survey: .inAppFeedback, viewControllersFactory: factory)
let coordinator = SurveyCoordinatingController(survey: .generalFeedback, viewControllersFactory: factory)

let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = UIViewController()
Expand All @@ -72,7 +72,7 @@ final class SurveyCoordinatingControllerTests: XCTestCase {
// Given
let zendeskManager = MockZendeskManager()
let factory = MockSurveyViewControllersFactory()
let coordinator = SurveyCoordinatingController(survey: .inAppFeedback, zendeskManager: zendeskManager, viewControllersFactory: factory)
let coordinator = SurveyCoordinatingController(survey: .generalFeedback, zendeskManager: zendeskManager, viewControllersFactory: factory)
assertEmpty(zendeskManager.newRequestIfPossibleInvocations)

// When
Expand All @@ -90,7 +90,7 @@ final class SurveyCoordinatingControllerTests: XCTestCase {
func test_it_tracks_a_surveyScreen_completed_event_when_the_survey_is_submitted() throws {
// Given
let factory = MockSurveyViewControllersFactory()
_ = SurveyCoordinatingController(survey: .inAppFeedback,
_ = SurveyCoordinatingController(survey: .generalFeedback,
viewControllersFactory: factory,
analytics: analytics)

Expand All @@ -113,7 +113,7 @@ final class SurveyCoordinatingControllerTests: XCTestCase {
let factory = MockSurveyViewControllersFactory()

// When
_ = SurveyCoordinatingController(survey: .inAppFeedback,
_ = SurveyCoordinatingController(survey: .generalFeedback,
viewControllersFactory: factory,
analytics: analytics)

Expand All @@ -138,7 +138,7 @@ final class SurveyCoordinatingControllerTests: XCTestCase {
window.rootViewController = rootViewController
window.isHidden = false

let coordinator = SurveyCoordinatingController(survey: .inAppFeedback,
let coordinator = SurveyCoordinatingController(survey: .generalFeedback,
viewControllersFactory: factory,
analytics: analytics)
waitForExpectation { exp in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ final class SurveyViewControllerTests: XCTestCase {

func test_it_loads_the_correct_inApp_feedback_survey() throws {
// Given
let viewController = SurveyViewController(survey: .inAppFeedback, onCompletion: {})
let viewController = SurveyViewController(survey: .generalFeedback, onCompletion: {})

// When
_ = try XCTUnwrap(viewController.view)
Expand Down Expand Up @@ -56,7 +56,7 @@ final class SurveyViewControllerTests: XCTestCase {
func test_it_completes_after_receiving_a_form_submitted_completed_callback_request() throws {
// Given
var surveyCompleted = false
let viewController = SurveyViewController(survey: .inAppFeedback, onCompletion: {
let viewController = SurveyViewController(survey: .generalFeedback, onCompletion: {
surveyCompleted = true
})

Expand All @@ -77,7 +77,7 @@ final class SurveyViewControllerTests: XCTestCase {
func test_it_does_not_complete_after_receiving_a_form_submitted_non_completed_callback_request() throws {
// Given
var surveyCompleted = false
let viewController = SurveyViewController(survey: .inAppFeedback, onCompletion: {
let viewController = SurveyViewController(survey: .generalFeedback, onCompletion: {
surveyCompleted = true
})

Expand All @@ -100,7 +100,7 @@ final class SurveyViewControllerTests: XCTestCase {
func test_it_does_not_complete_after_receiving_a_form_submitted_empty_callback_request() throws {
// Given
var surveyCompleted = false
let viewController = SurveyViewController(survey: .inAppFeedback, onCompletion: {
let viewController = SurveyViewController(survey: .generalFeedback, onCompletion: {
surveyCompleted = true
})

Expand All @@ -122,7 +122,7 @@ final class SurveyViewControllerTests: XCTestCase {

func test_it_shows_the_loading_view_when_loading_a_survey() throws {
// Given
let viewController = SurveyViewController(survey: .inAppFeedback, onCompletion: {})
let viewController = SurveyViewController(survey: .generalFeedback, onCompletion: {})

// When
_ = try XCTUnwrap(viewController.view)
Expand All @@ -134,7 +134,7 @@ final class SurveyViewControllerTests: XCTestCase {

func test_it_hides_the_loading_view_after_loading_a_survey() throws {
// Given
let viewController = SurveyViewController(survey: .inAppFeedback, onCompletion: {})
let viewController = SurveyViewController(survey: .generalFeedback, onCompletion: {})

// When
_ = try XCTUnwrap(viewController.view)
Expand Down