Skip to content

Commit fb36d16

Browse files
committed
Create GenerateAllVariationsPresenter to encapsulation presentation duties
1 parent e284d81 commit fb36d16

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
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+
}

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@
643643
2678897C270E6E8B00BD249E /* SimplePaymentsAmount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2678897B270E6E8B00BD249E /* SimplePaymentsAmount.swift */; };
644644
267D6882296485850072ED0C /* ProductVariationGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 267D6881296485850072ED0C /* ProductVariationGeneratorTests.swift */; };
645645
26838354296F460B00CCF60A /* GenerateVariationsOptionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26838353296F460B00CCF60A /* GenerateVariationsOptionPresenter.swift */; };
646+
26838356296F702B00CCF60A /* GenerateAllVariationsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26838355296F702B00CCF60A /* GenerateAllVariationsPresenter.swift */; };
646647
2687165524D21BC80042F6AE /* SurveySubmittedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2687165324D21BC80042F6AE /* SurveySubmittedViewController.swift */; };
647648
2687165624D21BC80042F6AE /* SurveySubmittedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2687165424D21BC80042F6AE /* SurveySubmittedViewController.xib */; };
648649
2687165A24D350C20042F6AE /* SurveyCoordinatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2687165924D350C20042F6AE /* SurveyCoordinatingController.swift */; };
@@ -2705,6 +2706,7 @@
27052706
267CFE1824435A5500AF3A13 /* ProductCategoryViewModelBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductCategoryViewModelBuilderTests.swift; sourceTree = "<group>"; };
27062707
267D6881296485850072ED0C /* ProductVariationGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationGeneratorTests.swift; sourceTree = "<group>"; };
27072708
26838353296F460B00CCF60A /* GenerateVariationsOptionPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateVariationsOptionPresenter.swift; sourceTree = "<group>"; };
2709+
26838355296F702B00CCF60A /* GenerateAllVariationsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateAllVariationsPresenter.swift; sourceTree = "<group>"; };
27082710
2687165324D21BC80042F6AE /* SurveySubmittedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveySubmittedViewController.swift; sourceTree = "<group>"; };
27092711
2687165424D21BC80042F6AE /* SurveySubmittedViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SurveySubmittedViewController.xib; sourceTree = "<group>"; };
27102712
2687165924D350C20042F6AE /* SurveyCoordinatingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyCoordinatingController.swift; sourceTree = "<group>"; };
@@ -4800,6 +4802,7 @@
48004802
269A2F46295CC683000828A8 /* GenerateVariationsSelectorCommand.swift */,
48014803
263C4CBF2963784900CA7E05 /* ProductVariationGenerator.swift */,
48024804
26838353296F460B00CCF60A /* GenerateVariationsOptionPresenter.swift */,
4805+
26838355296F702B00CCF60A /* GenerateAllVariationsPresenter.swift */,
48034806
4515262B2577D48D0076B03C /* Add Attributes */,
48044807
AEDDDA0825CA9C0A0077F9B2 /* Edit Attributes */,
48054808
);
@@ -10698,6 +10701,7 @@
1069810701
D8C2A291231BD0FD00F503E9 /* ReviewsDataSource.swift in Sources */,
1069910702
CE855366209BA6A700938BDC /* CustomerInfoTableViewCell.swift in Sources */,
1070010703
02DAE7FC291B7B8B009342B7 /* DomainSelectorViewModel.swift in Sources */,
10704+
26838356296F702B00CCF60A /* GenerateAllVariationsPresenter.swift in Sources */,
1070110705
4521396E27FEE55200964ED3 /* FullScreenTextView.swift in Sources */,
1070210706
DE126D0B26CA2331007F901D /* ValidationErrorRow.swift in Sources */,
1070310707
E1E649E92846188C0070B194 /* BetaFeature.swift in Sources */,

0 commit comments

Comments
 (0)