Skip to content

Commit 108b55a

Browse files
committed
Implementing OrderLoaderViewController
1 parent 11bdd9f commit 108b55a

File tree

1 file changed

+241
-0
lines changed

1 file changed

+241
-0
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import Foundation
2+
import UIKit
3+
import Yosemite
4+
5+
6+
// MARK: - OrderLoaderViewController: Loads asynchronously an Order (given it's OrderID + SiteID).
7+
// On Success the OrderDetailsViewController will be rendered "in place".
8+
//
9+
class OrderLoaderViewController: UIViewController {
10+
11+
/// UI Spinner
12+
///
13+
private let activityIndicator = UIActivityIndicatorView(style: .gray)
14+
15+
/// Target OrderID
16+
///
17+
private let orderID: Int
18+
19+
/// Target Order's SiteID
20+
///
21+
private let siteID: Int
22+
23+
/// UI Active State
24+
///
25+
private var state: State = .loading {
26+
didSet {
27+
didLeave(state: oldValue)
28+
didEnter(state: state)
29+
}
30+
}
31+
32+
33+
// MARK: - Initializers
34+
35+
init(orderID: Int, siteID: Int) {
36+
self.orderID = orderID
37+
self.siteID = siteID
38+
39+
super.init(nibName: nil, bundle: nil)
40+
}
41+
42+
required init?(coder aDecoder: NSCoder) {
43+
fatalError("Please specify the OrderID and SiteID!")
44+
}
45+
46+
47+
// MARK: - Overridden Methods
48+
49+
override func viewDidLoad() {
50+
super.viewDidLoad()
51+
52+
configureNavigationItem()
53+
configureSpinner()
54+
configureMainView()
55+
}
56+
57+
override func viewWillAppear(_ animated: Bool) {
58+
super.viewWillAppear(animated)
59+
reloadOrder()
60+
}
61+
}
62+
63+
64+
// MARK: - Actions
65+
//
66+
private extension OrderLoaderViewController {
67+
68+
/// Loads (and displays) the specified Order.
69+
///
70+
func reloadOrder() {
71+
let action = OrderAction.retrieveOrder(siteID: siteID, orderID: orderID) { [weak self] (order, error) in
72+
guard let `self` = self else {
73+
return
74+
}
75+
76+
guard let order = order else {
77+
DDLogError("## Error loading Order \(self.siteID).\(self.orderID): \(error.debugDescription)")
78+
self.state = .failure
79+
return
80+
}
81+
82+
self.state = .success(order: order)
83+
}
84+
85+
state = .loading
86+
StoresManager.shared.dispatch(action)
87+
}
88+
}
89+
90+
91+
// MARK: - Configuration
92+
//
93+
private extension OrderLoaderViewController {
94+
95+
/// Setup: Navigation
96+
///
97+
func configureNavigationItem() {
98+
title = NSLocalizedString("Loading Order", comment: "Displayed when an Order is being retrieved")
99+
navigationItem.backBarButtonItem = UIBarButtonItem(title: String(), style: .plain, target: nil, action: nil)
100+
}
101+
102+
/// Setup: Main View
103+
///
104+
func configureMainView() {
105+
view.backgroundColor = StyleManager.tableViewBackgroundColor
106+
view.addSubview(activityIndicator)
107+
view.pinSubviewAtCenter(activityIndicator)
108+
}
109+
110+
/// Setup: Spinner
111+
///
112+
func configureSpinner() {
113+
activityIndicator.hidesWhenStopped = true
114+
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
115+
}
116+
}
117+
118+
119+
// MARK: - Overlays
120+
//
121+
private extension OrderLoaderViewController {
122+
123+
/// Starts the Spinner
124+
///
125+
func startSpinner() {
126+
activityIndicator.startAnimating()
127+
}
128+
129+
/// Stops the Spinner
130+
///
131+
func stopSpinner() {
132+
activityIndicator.stopAnimating()
133+
}
134+
135+
/// Displays the Loading Overlay.
136+
///
137+
func displayFailureOverlay() {
138+
let overlayView: OverlayMessageView = OverlayMessageView.instantiateFromNib()
139+
overlayView.messageImage = .waitingForCustomersImage
140+
overlayView.messageText = NSLocalizedString("The Order couldn't be loaded!", comment: "Fetching an Order Failed")
141+
overlayView.actionText = NSLocalizedString("Retry", comment: "Retry the last action")
142+
overlayView.onAction = { [weak self] in
143+
self?.reloadOrder()
144+
}
145+
146+
overlayView.attach(to: view)
147+
}
148+
149+
/// Removes all of the the OverlayMessageView instances in the view hierarchy.
150+
///
151+
func removeAllOverlays() {
152+
for subview in view.subviews where subview is OverlayMessageView {
153+
subview.removeFromSuperview()
154+
}
155+
}
156+
157+
/// Presents the OrderDetailsViewController, as a childViewController, for a given Order.
158+
///
159+
func presentOrderDetails(for order: Order) {
160+
let identifier = OrderDetailsViewController.classNameWithoutNamespaces
161+
guard let detailsViewController = UIStoryboard.orders.instantiateViewController(withIdentifier: identifier) as? OrderDetailsViewController else {
162+
fatalError()
163+
}
164+
165+
// Setup the DetailsViewController
166+
detailsViewController.viewModel = OrderDetailsViewModel(order: order)
167+
168+
// Attach
169+
addChild(detailsViewController)
170+
attachSubview(detailsViewController.view)
171+
detailsViewController.didMove(toParent: self)
172+
173+
// And, of course, borrow the Child's Title
174+
title = detailsViewController.title
175+
}
176+
177+
/// Removes all of the children UIViewControllers
178+
///
179+
func detachChildrenViewControllers() {
180+
for child in children {
181+
child.view.removeFromSuperview()
182+
child.removeFromParent()
183+
child.didMove(toParent: nil)
184+
}
185+
}
186+
}
187+
188+
189+
// MARK: - UI Methods
190+
//
191+
private extension OrderLoaderViewController {
192+
193+
/// Attaches a given Subview, and ensures it's pinned to all the edges
194+
///
195+
func attachSubview(_ subview: UIView) {
196+
subview.translatesAutoresizingMaskIntoConstraints = false
197+
view.addSubview(subview)
198+
view.pinSubviewToAllEdges(subview)
199+
}
200+
}
201+
202+
203+
// MARK: - Finite State Machine Management
204+
//
205+
private extension OrderLoaderViewController {
206+
207+
/// Runs whenever the FSM enters a State.
208+
///
209+
func didEnter(state: State) {
210+
switch state {
211+
case .loading:
212+
startSpinner()
213+
case .success(let order):
214+
presentOrderDetails(for: order)
215+
case .failure:
216+
displayFailureOverlay()
217+
}
218+
}
219+
220+
/// Runs whenever the FSM leaves a State.
221+
///
222+
func didLeave(state: State) {
223+
switch state {
224+
case .loading:
225+
stopSpinner()
226+
case .success(_):
227+
detachChildrenViewControllers()
228+
case .failure:
229+
removeAllOverlays()
230+
}
231+
}
232+
}
233+
234+
235+
// MARK: - OrderLoader Possible Status(es)
236+
//
237+
private enum State {
238+
case loading
239+
case success(order: Order)
240+
case failure
241+
}

0 commit comments

Comments
 (0)