Skip to content

Commit 269cee6

Browse files
authored
Merge pull request #2189 from woocommerce/issue/2122-product-settings-menu-order
Product Settings: edit the Menu order
2 parents cd1cbe1 + 0f7c341 commit 269cee6

File tree

17 files changed

+340
-28
lines changed

17 files changed

+340
-28
lines changed

Networking/Networking/Model/Product/Product.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ public struct Product: Codable {
461461
try container.encode(catalogVisibilityKey, forKey: .catalogVisibilityKey)
462462
try container.encode(slug, forKey: .slug)
463463
try container.encode(purchaseNote, forKey: .purchaseNote)
464+
try container.encode(menuOrder, forKey: .menuOrder)
464465
}
465466
}
466467

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
import Foundation
22

3-
/// `UnitInputFormatter` implementation for integer number input.
3+
/// `UnitInputFormatter` implementation for integer number input (positive, negative, or zero)
44
///
55
struct IntegerInputFormatter: UnitInputFormatter {
6+
private let numberFormatter: NumberFormatter = {
7+
let numberFormatter = NumberFormatter()
8+
numberFormatter.allowsFloats = false
9+
return numberFormatter
10+
}()
11+
612
func isValid(input: String) -> Bool {
713
guard input.isEmpty == false else {
814
// Allows empty input to be replaced by 0.
915
return true
1016
}
11-
return Int(input) != nil
17+
return numberFormatter.number(from: input) != nil
1218
}
1319

1420
func format(input text: String?) -> String {
1521
guard let text = text, text.isEmpty == false else {
1622
return "0"
1723
}
1824

19-
let formattedText = text
20-
// Removes leading 0s before the digits.
21-
.replacingOccurrences(of: "^0+([1-9]+)", with: "$1", options: .regularExpression)
22-
// Maximum one leading 0.
23-
.replacingOccurrences(of: "^(0+)", with: "0", options: .regularExpression)
25+
let formattedText = numberFormatter.number(from: text)?.stringValue ?? "0"
2426
return formattedText
2527
}
2628
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import UIKit
2+
import Yosemite
3+
4+
final class ProductMenuOrderViewController: UIViewController {
5+
6+
@IBOutlet weak private var tableView: UITableView!
7+
8+
// Completion callback
9+
//
10+
typealias Completion = (_ productSettings: ProductSettings) -> Void
11+
private let onCompletion: Completion
12+
13+
private let productSettings: ProductSettings
14+
15+
private let sections: [Section]
16+
17+
/// Init
18+
///
19+
init(settings: ProductSettings, completion: @escaping Completion) {
20+
productSettings = settings
21+
let footerText = NSLocalizedString("Determines the products positioning in the catalog."
22+
+ " The lower the value of the number, the higher the item will be on the product list. You can also use negative values",
23+
comment: "Footer text in Product Menu order screen")
24+
sections = [Section(footer: footerText, rows: [.menuOrder])]
25+
onCompletion = completion
26+
super.init(nibName: nil, bundle: nil)
27+
}
28+
29+
required init?(coder: NSCoder) {
30+
fatalError("init(coder:) has not been implemented")
31+
}
32+
33+
override func viewDidLoad() {
34+
super.viewDidLoad()
35+
configureNavigationBar()
36+
configureMainView()
37+
configureTableView()
38+
}
39+
40+
override func viewWillDisappear(_ animated: Bool) {
41+
super.viewWillDisappear(animated)
42+
if isMovingFromParent {
43+
onCompletion(productSettings)
44+
}
45+
}
46+
47+
override func viewDidAppear(_ animated: Bool) {
48+
super.viewDidAppear(animated)
49+
configureFirstTextFieldAsFirstResponder()
50+
}
51+
}
52+
53+
// MARK: - View Configuration
54+
//
55+
private extension ProductMenuOrderViewController {
56+
57+
func configureNavigationBar() {
58+
title = NSLocalizedString("Menu Order", comment: "Product Menu Order navigation title")
59+
removeNavigationBackBarButtonText()
60+
}
61+
62+
func configureMainView() {
63+
view.backgroundColor = .listBackground
64+
}
65+
66+
func configureTableView() {
67+
tableView.register(TextFieldTableViewCell.loadNib(), forCellReuseIdentifier: TextFieldTableViewCell.reuseIdentifier)
68+
69+
tableView.dataSource = self
70+
tableView.delegate = self
71+
72+
tableView.backgroundColor = .listBackground
73+
tableView.removeLastCellSeparator()
74+
75+
tableView.sectionHeaderHeight = UITableView.automaticDimension
76+
tableView.sectionFooterHeight = UITableView.automaticDimension
77+
78+
tableView.allowsSelection = false
79+
}
80+
81+
/// Since there is only a text field in this view, the text field become the first responder immediately when the view did appear
82+
///
83+
func configureFirstTextFieldAsFirstResponder() {
84+
if let indexPath = sections.indexPathForRow(.menuOrder) {
85+
let cell = tableView.cellForRow(at: indexPath) as? TextFieldTableViewCell
86+
cell?.textField.becomeFirstResponder()
87+
}
88+
}
89+
}
90+
91+
// MARK: - UITableViewDataSource Conformance
92+
//
93+
extension ProductMenuOrderViewController: UITableViewDataSource {
94+
95+
func numberOfSections(in tableView: UITableView) -> Int {
96+
return sections.count
97+
}
98+
99+
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
100+
return sections[section].rows.count
101+
}
102+
103+
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
104+
let row = sections[indexPath.section].rows[indexPath.row]
105+
106+
let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier, for: indexPath)
107+
configure(cell, for: row, at: indexPath)
108+
109+
return cell
110+
}
111+
}
112+
113+
// MARK: - UITableViewDelegate Conformance
114+
//
115+
extension ProductMenuOrderViewController: UITableViewDelegate {
116+
117+
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
118+
return sections[section].footer
119+
}
120+
}
121+
122+
// MARK: - Support for UITableViewDataSource
123+
//
124+
private extension ProductMenuOrderViewController {
125+
/// Configure cellForRowAtIndexPath:
126+
///
127+
func configure(_ cell: UITableViewCell, for row: Row, at indexPath: IndexPath) {
128+
switch cell {
129+
case let cell as TextFieldTableViewCell:
130+
configureMenuOrder(cell: cell)
131+
default:
132+
fatalError("Unidentified product menu order row type")
133+
}
134+
}
135+
136+
func configureMenuOrder(cell: TextFieldTableViewCell) {
137+
cell.accessoryType = .none
138+
139+
let placeholder = NSLocalizedString("Menu order", comment: "Placeholder in the Product Menu Order row on Edit Product Menu Order screen.")
140+
141+
let viewModel = TextFieldTableViewCell.ViewModel(text: String(productSettings.menuOrder),
142+
placeholder: placeholder,
143+
onTextChange: { [weak self] menuOrder in
144+
if let newMenuOrder = Int(menuOrder ?? "0") {
145+
self?.productSettings.menuOrder = newMenuOrder
146+
}
147+
}, onTextDidBeginEditing: {
148+
//TODO: Add analytics track
149+
}, inputFormatter: IntegerInputFormatter())
150+
cell.configure(viewModel: viewModel)
151+
cell.textField.applyBodyStyle()
152+
cell.textField.keyboardType = .numbersAndPunctuation
153+
}
154+
}
155+
156+
// MARK: - Constants
157+
//
158+
private extension ProductMenuOrderViewController {
159+
160+
/// Table Rows
161+
///
162+
enum Row {
163+
/// Listed in the order they appear on screen
164+
case menuOrder
165+
166+
var reuseIdentifier: String {
167+
switch self {
168+
case .menuOrder:
169+
return TextFieldTableViewCell.reuseIdentifier
170+
}
171+
}
172+
}
173+
174+
/// Table Sections
175+
///
176+
struct Section: RowIterable {
177+
let footer: String?
178+
let rows: [Row]
179+
180+
init(footer: String? = nil, rows: [Row]) {
181+
self.footer = footer
182+
self.rows = rows
183+
}
184+
}
185+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+
<device id="retina6_1" orientation="portrait" appearance="light"/>
4+
<dependencies>
5+
<deployment identifier="iOS"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
7+
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+
</dependencies>
10+
<objects>
11+
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ProductMenuOrderViewController" customModule="WooCommerce" customModuleProvider="target">
12+
<connections>
13+
<outlet property="tableView" destination="JYX-ph-feD" id="lvy-s7-NCl"/>
14+
<outlet property="view" destination="1WG-jm-qSJ" id="JEY-Ml-9u5"/>
15+
</connections>
16+
</placeholder>
17+
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
18+
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="1WG-jm-qSJ">
19+
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
20+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
21+
<subviews>
22+
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="JYX-ph-feD">
23+
<rect key="frame" x="0.0" y="0.0" width="414" height="862"/>
24+
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
25+
</tableView>
26+
</subviews>
27+
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
28+
<constraints>
29+
<constraint firstItem="lyj-b5-wwJ" firstAttribute="trailing" secondItem="JYX-ph-feD" secondAttribute="trailing" id="OP9-yX-CJq"/>
30+
<constraint firstItem="JYX-ph-feD" firstAttribute="top" secondItem="1WG-jm-qSJ" secondAttribute="top" id="OqY-do-8zE"/>
31+
<constraint firstItem="lyj-b5-wwJ" firstAttribute="bottom" secondItem="JYX-ph-feD" secondAttribute="bottom" id="VIY-IS-Xpc"/>
32+
<constraint firstItem="JYX-ph-feD" firstAttribute="leading" secondItem="1WG-jm-qSJ" secondAttribute="leading" id="ttm-uH-pnW"/>
33+
</constraints>
34+
<viewLayoutGuide key="safeArea" id="lyj-b5-wwJ"/>
35+
<point key="canvasLocation" x="404" y="129"/>
36+
</view>
37+
</objects>
38+
</document>

WooCommerce/Classes/ViewRelated/Products/Edit Product/Product Settings/ProductSettingsRows.swift

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ enum ProductSettingsRows {
7373
return
7474
}
7575

76-
let titleView = NSLocalizedString("Catalog Visibility", comment: "Catalog Visibility label in Product Settings")
77-
cell.updateUI(title: titleView, value: settings.catalogVisibility.description)
76+
let title = NSLocalizedString("Catalog Visibility", comment: "Catalog Visibility label in Product Settings")
77+
cell.updateUI(title: title, value: settings.catalogVisibility.description)
7878
cell.accessoryType = .disclosureIndicator
7979
}
8080

@@ -104,8 +104,8 @@ enum ProductSettingsRows {
104104
return
105105
}
106106

107-
let titleView = NSLocalizedString("Slug", comment: "Slug label in Product Settings")
108-
cell.updateUI(title: titleView, value: settings.slug)
107+
let title = NSLocalizedString("Slug", comment: "Slug label in Product Settings")
108+
cell.updateUI(title: title, value: settings.slug)
109109
cell.accessoryType = .disclosureIndicator
110110
}
111111

@@ -134,8 +134,8 @@ enum ProductSettingsRows {
134134
return
135135
}
136136

137-
let titleView = NSLocalizedString("Purchase Note", comment: "Purchase note label in Product Settings")
138-
cell.updateUI(title: titleView, value: settings.purchaseNote?.strippedHTML)
137+
let title = NSLocalizedString("Purchase Note", comment: "Purchase note label in Product Settings")
138+
cell.updateUI(title: title, value: settings.purchaseNote?.strippedHTML)
139139
cell.accessoryType = .disclosureIndicator
140140
}
141141

@@ -151,4 +151,34 @@ enum ProductSettingsRows {
151151

152152
let cellTypes: [UITableViewCell.Type] = [SettingTitleAndValueTableViewCell.self]
153153
}
154+
155+
struct MenuOrder: ProductSettingsRowMediator {
156+
private let settings: ProductSettings
157+
158+
init(_ settings: ProductSettings) {
159+
self.settings = settings
160+
}
161+
162+
func configure(cell: UITableViewCell) {
163+
guard let cell = cell as? SettingTitleAndValueTableViewCell else {
164+
return
165+
}
166+
167+
let title = NSLocalizedString("Menu Order", comment: "Menu order label in Product Settings")
168+
cell.updateUI(title: title, value: String(settings.menuOrder))
169+
cell.accessoryType = .disclosureIndicator
170+
}
171+
172+
func handleTap(sourceViewController: UIViewController, onCompletion: @escaping (ProductSettings) -> Void) {
173+
let viewController = ProductMenuOrderViewController(settings: settings) { (productSettings) in
174+
self.settings.menuOrder = productSettings.menuOrder
175+
onCompletion(self.settings)
176+
}
177+
sourceViewController.navigationController?.pushViewController(viewController, animated: true)
178+
}
179+
180+
let reuseIdentifier: String = SettingTitleAndValueTableViewCell.reuseIdentifier
181+
182+
let cellTypes: [UITableViewCell.Type] = [SettingTitleAndValueTableViewCell.self]
183+
}
154184
}

WooCommerce/Classes/ViewRelated/Products/Edit Product/Product Settings/ProductSettingsSections.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ enum ProductSettingsSections {
3232
let rows: [ProductSettingsRowMediator]
3333

3434
init(_ settings: ProductSettings) {
35-
rows = [ProductSettingsRows.Slug(settings), ProductSettingsRows.PurchaseNote(settings)]
35+
rows = [ProductSettingsRows.Slug(settings), ProductSettingsRows.PurchaseNote(settings), ProductSettingsRows.MenuOrder(settings)]
3636
}
3737
}
3838
}

WooCommerce/Classes/ViewRelated/Products/Edit Product/Product Settings/ProductSettingsViewController.xib

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
33
<device id="retina6_1" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
77
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
88
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
99
</dependencies>
@@ -19,7 +19,7 @@
1919
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
2020
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
2121
<subviews>
22-
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="saX-iw-raM">
22+
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="saX-iw-raM">
2323
<rect key="frame" x="0.0" y="44" width="414" height="818"/>
2424
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
2525
</tableView>

WooCommerce/Classes/ViewRelated/Products/Edit Product/Product Settings/Slug/ProductSlugViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ private extension ProductSlugViewController {
148148
}
149149
}, onTextDidBeginEditing: {
150150
//TODO: Add analytics track
151-
})
151+
}, inputFormatter: nil)
152152
cell.configure(viewModel: viewModel)
153153
cell.textField.applyBodyStyle()
154154
}

WooCommerce/Classes/ViewRelated/Products/Edit Product/ProductFormTableViewDataSource.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ private extension ProductFormTableViewDataSource {
128128
self?.onNameChange?(newName)
129129
}, onTextDidBeginEditing: {
130130
ServiceLocator.analytics.track(.productDetailViewProductNameTapped)
131-
})
131+
}, inputFormatter: nil)
132132
cell.configure(viewModel: viewModel)
133133
}
134134

0 commit comments

Comments
 (0)