Skip to content

Commit 54d567c

Browse files
authored
Merge pull request #8614 from woocommerce/issue/8534-generate-from-empty-states
Variations: Create all variations from no variations empty state screen
2 parents b14c1c7 + 9f70bcd commit 54d567c

File tree

4 files changed

+227
-132
lines changed

4 files changed

+227
-132
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import Foundation
2+
import UIKit
3+
4+
/// Type to encapsulate view controllers and notices shown while generating all variations.
5+
///
6+
final class GenerateAllVariationsPresenter {
7+
8+
/// Base view controller where the loading indicators and notices will be presented.
9+
///
10+
private let baseViewController: UIViewController
11+
12+
/// Default notices presenter.
13+
///
14+
private let noticePresenter: NoticePresenter
15+
16+
init(baseViewController: UIViewController, noticePresenter: NoticePresenter = ServiceLocator.noticePresenter) {
17+
self.baseViewController = baseViewController
18+
self.noticePresenter = noticePresenter
19+
}
20+
21+
/// Respond to necessary presentation changes.
22+
///
23+
func handleStateChanges(state: ProductVariationsViewModel.GenerationState) {
24+
switch state {
25+
case .fetching:
26+
presentFetchingIndicator()
27+
case .confirmation(let variationCount, let onCompletion):
28+
presentGenerationConfirmation(numberOfVariations: variationCount, onCompletion: onCompletion)
29+
case .creating:
30+
presentCreatingIndicator()
31+
case .canceled:
32+
dismissBlockingIndicator()
33+
case .finished(let variationsCreated):
34+
dismissBlockingIndicator()
35+
if variationsCreated {
36+
presentVariationsCreatedNotice()
37+
} else {
38+
presentNoGenerationNotice()
39+
}
40+
break
41+
case .error(let error):
42+
dismissBlockingIndicator()
43+
presentGenerationError(error)
44+
}
45+
}
46+
}
47+
48+
// MARK: Helper Methods
49+
//
50+
private extension GenerateAllVariationsPresenter {
51+
/// Informs the merchant about errors that happen during the variation generation
52+
///
53+
private func presentGenerationError(_ error: ProductVariationsViewModel.GenerationError) {
54+
let notice = Notice(title: error.errorTitle, message: error.errorDescription)
55+
noticePresenter.enqueue(notice: notice)
56+
}
57+
58+
/// Asks the merchant for confirmation before generating all variations.
59+
///
60+
private func presentGenerationConfirmation(numberOfVariations: Int, onCompletion: @escaping (_ confirmed: Bool) -> Void) {
61+
let controller = UIAlertController(title: Localization.confirmationTitle,
62+
message: Localization.confirmationDescription(variationCount: numberOfVariations),
63+
preferredStyle: .alert)
64+
controller.addDefaultActionWithTitle(Localization.ok) { _ in
65+
onCompletion(true)
66+
}
67+
controller.addCancelActionWithTitle(Localization.cancel) { _ in
68+
onCompletion(false)
69+
}
70+
71+
// There should be an `inProgressViewController` being presented. Fallback to `self` in case there isn't one.
72+
let baseViewController = baseViewController.presentedViewController ?? baseViewController
73+
baseViewController.present(controller, animated: true)
74+
}
75+
76+
/// Presents a blocking view controller while variations are being fetched.
77+
///
78+
private func presentFetchingIndicator() {
79+
let inProgressViewController = InProgressViewController(viewProperties: .init(title: Localization.fetchingVariations, message: ""))
80+
baseViewController.present(inProgressViewController, animated: true)
81+
}
82+
83+
/// Presents a blocking view controller while variations are being created.
84+
///
85+
private func presentCreatingIndicator() {
86+
let newViewProperties = InProgressViewProperties(title: Localization.creatingVariations, message: "")
87+
88+
// There should be already a presented `InProgressViewController`. But we present one in case there isn;t.
89+
guard let inProgressViewController = baseViewController.presentedViewController as? InProgressViewController else {
90+
let inProgressViewController = InProgressViewController(viewProperties: newViewProperties)
91+
return baseViewController.present(inProgressViewController, animated: true)
92+
}
93+
94+
// Update the already presented view controller with the "Creating..." copy.
95+
inProgressViewController.updateViewProperties(newViewProperties)
96+
}
97+
98+
/// Dismiss any `InProgressViewController` being presented.
99+
/// By default retires the dismissal one time to overcome UIKit presentation delays.
100+
///
101+
private func dismissBlockingIndicator(retry: Bool = true) {
102+
if let inProgressViewController = baseViewController.presentedViewController as? InProgressViewController {
103+
inProgressViewController.dismiss(animated: true)
104+
} else {
105+
// When this method is invoked right after `InProgressViewController` is presented, chances are that it won't exists in the view controller
106+
// hierarchy yet.
107+
// Here we are retrying it with a small delay to give UIKit APIs time to finish its presentation.
108+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
109+
self.dismissBlockingIndicator(retry: false)
110+
}
111+
}
112+
}
113+
114+
/// Informs the merchant that no variations were created.
115+
///
116+
private func presentNoGenerationNotice() {
117+
let notice = Notice(title: Localization.noVariationsCreatedTitle, message: Localization.noVariationsCreatedDescription)
118+
noticePresenter.enqueue(notice: notice)
119+
}
120+
121+
/// Informs the merchant that some variations were created.
122+
///
123+
private func presentVariationsCreatedNotice() {
124+
let notice = Notice(title: Localization.variationsCreatedTitle)
125+
noticePresenter.enqueue(notice: notice)
126+
}
127+
}
128+
129+
// MARK: Localization
130+
//
131+
private extension GenerateAllVariationsPresenter {
132+
enum Localization {
133+
static let confirmationTitle = NSLocalizedString("Generate all variations?",
134+
comment: "Alert title to allow the user confirm if they want to generate all variations")
135+
static func confirmationDescription(variationCount: Int) -> String {
136+
let format = NSLocalizedString("This will create a variation for each and every possible combination of variation attributes (%d variations).",
137+
comment: "Alert description to allow the user confirm if they want to generate all variations")
138+
return String.localizedStringWithFormat(format, variationCount)
139+
}
140+
static let ok = NSLocalizedString("OK", comment: "Button text to confirm that we want to generate all variations")
141+
static let cancel = NSLocalizedString("Cancel", comment: "Button text to confirm that we don't want to generate all variations")
142+
static let fetchingVariations = NSLocalizedString("Fetching Variations...",
143+
comment: "Blocking indicator text when fetching existing variations prior generating them.")
144+
static let creatingVariations = NSLocalizedString("Creating Variations...",
145+
comment: "Blocking indicator text when creating multiple variations remotely.")
146+
static let noVariationsCreatedTitle = NSLocalizedString("No variations to generate",
147+
comment: "Title for the notice when there were no variations to generate")
148+
static let noVariationsCreatedDescription = NSLocalizedString("All variations are already generated.",
149+
comment: "Message for the notice when there were no variations to generate")
150+
static let variationsCreatedTitle = NSLocalizedString("Variations created successfully",
151+
comment: "Title for the notice when after variations were created")
152+
}
153+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Foundation
2+
import UIKit
3+
import Yosemite
4+
5+
/// Type to encapsulate the options presented when generating variations.
6+
///
7+
final class GenerateVariationsOptionsPresenter {
8+
/// Options available when generating variations
9+
///
10+
enum Option {
11+
case single
12+
case all
13+
}
14+
15+
/// Base view controller where the loading indicators and notices will be presented.
16+
///
17+
private let baseViewController: UIViewController
18+
19+
init(baseViewController: UIViewController) {
20+
self.baseViewController = baseViewController
21+
}
22+
23+
/// Displays a bottom sheet allowing the merchant to choose whether to generate one variation or to generate all variations.
24+
///
25+
func presentGenerationOptions(sourceView: UIView, onCompletion: @escaping (_ selectedOption: Option) -> Void) {
26+
guard ServiceLocator.featureFlagService.isFeatureFlagEnabled(.generateAllVariations) else {
27+
return onCompletion(.single)
28+
}
29+
30+
let viewProperties = BottomSheetListSelectorViewProperties(title: Localization.addVariationAction)
31+
let command = GenerateVariationsSelectorCommand(selected: nil) { [baseViewController] option in
32+
baseViewController.dismiss(animated: true)
33+
switch option {
34+
case .single:
35+
onCompletion(.single)
36+
case .all:
37+
onCompletion(.all)
38+
}
39+
}
40+
let bottomSheetPresenter = BottomSheetListSelectorPresenter(viewProperties: viewProperties, command: command)
41+
bottomSheetPresenter.show(from: baseViewController, sourceView: sourceView)
42+
}
43+
}
44+
45+
// MARK: Localization
46+
//
47+
private extension GenerateVariationsOptionsPresenter {
48+
enum Localization {
49+
static let addVariationAction = NSLocalizedString("Add Variation",
50+
comment: "Title on the bottom sheet to choose what variation process to start")
51+
}
52+
}

0 commit comments

Comments
 (0)