Skip to content

Commit 31d925a

Browse files
authored
Merge pull request #2142 from woocommerce/issue/2121-edit-product-purchase-note
Product Settings: edit the Purchase Note
2 parents 8492a88 + b241b44 commit 31d925a

File tree

15 files changed

+439
-48
lines changed

15 files changed

+439
-48
lines changed

Networking/Networking/Model/Product/Product.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ public struct Product: Codable {
460460
try container.encode(featured, forKey: .featured)
461461
try container.encode(catalogVisibilityKey, forKey: .catalogVisibilityKey)
462462
try container.encode(slug, forKey: .slug)
463+
try container.encode(purchaseNote, forKey: .purchaseNote)
463464
}
464465
}
465466

WooCommerce/Classes/ViewRelated/Orders/Order Details/Order Notes Section/Add Order Note/NewNoteViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ private extension NewNoteViewController {
140140
NSLocalizedString("Private note",
141141
comment: "Spoken accessibility label for an icon image that indicates it's a private note and is not seen by the customer.")
142142

143-
cell.onTextChange = { [weak self] (text) in
143+
cell.noteTextView.onTextChange = { [weak self] (text) in
144144
self?.navigationItem.rightBarButtonItem?.isEnabled = !text.isEmpty
145145
self?.noteText = text
146146
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,34 @@ enum ProductSettingsRows {
124124

125125
let cellTypes: [UITableViewCell.Type] = [SettingTitleAndValueTableViewCell.self]
126126
}
127+
128+
struct PurchaseNote: ProductSettingsRowMediator {
129+
private let settings: ProductSettings
130+
131+
init(_ settings: ProductSettings) {
132+
self.settings = settings
133+
}
134+
135+
func configure(cell: UITableViewCell) {
136+
guard let cell = cell as? SettingTitleAndValueTableViewCell else {
137+
return
138+
}
139+
140+
let titleView = NSLocalizedString("Purchase Note", comment: "Purchase note label in Product Settings")
141+
cell.updateUI(title: titleView, value: settings.purchaseNote?.strippedHTML)
142+
cell.accessoryType = .disclosureIndicator
143+
}
144+
145+
func handleTap(sourceViewController: UIViewController, onCompletion: @escaping (ProductSettings) -> Void) {
146+
let viewController = ProductPurchaseNoteViewController(settings: settings) { (productSettings) in
147+
self.settings.purchaseNote = productSettings.purchaseNote
148+
onCompletion(self.settings)
149+
}
150+
sourceViewController.navigationController?.pushViewController(viewController, animated: true)
151+
}
152+
153+
let reuseIdentifier: String = SettingTitleAndValueTableViewCell.reuseIdentifier
154+
155+
let cellTypes: [UITableViewCell.Type] = [SettingTitleAndValueTableViewCell.self]
156+
}
127157
}

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)]
35+
rows = [ProductSettingsRows.Slug(settings), ProductSettingsRows.PurchaseNote(settings)]
3636
}
3737
}
3838
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import UIKit
2+
import Yosemite
3+
4+
final class ProductPurchaseNoteViewController: UIViewController {
5+
6+
@IBOutlet private weak 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+
private lazy var keyboardFrameObserver: KeyboardFrameObserver = {
18+
let keyboardFrameObserver = KeyboardFrameObserver { [weak self] keyboardFrame in
19+
self?.handleKeyboardFrameUpdate(keyboardFrame: keyboardFrame)
20+
}
21+
return keyboardFrameObserver
22+
}()
23+
24+
/// Init
25+
///
26+
init(settings: ProductSettings, completion: @escaping Completion) {
27+
productSettings = settings
28+
let footerText = NSLocalizedString("An optional note to send the customer after purchase",
29+
comment: "Footer text in Product Purchase Note screen")
30+
sections = [Section(footer: footerText, rows: [.purchaseNote])]
31+
onCompletion = completion
32+
super.init(nibName: nil, bundle: nil)
33+
}
34+
35+
required init?(coder: NSCoder) {
36+
fatalError("init(coder:) has not been implemented")
37+
}
38+
39+
override func viewDidLoad() {
40+
super.viewDidLoad()
41+
configureNavigationBar()
42+
configureMainView()
43+
configureTableView()
44+
startListeningToNotifications()
45+
}
46+
47+
override func viewWillDisappear(_ animated: Bool) {
48+
super.viewWillDisappear(animated)
49+
onCompletion(productSettings)
50+
}
51+
52+
}
53+
54+
// MARK: - View Configuration
55+
//
56+
private extension ProductPurchaseNoteViewController {
57+
58+
func configureNavigationBar() {
59+
title = NSLocalizedString("Purchase Note", comment: "Product Note navigation title")
60+
61+
removeNavigationBackBarButtonText()
62+
}
63+
64+
func configureMainView() {
65+
view.backgroundColor = .listBackground
66+
}
67+
68+
func configureTableView() {
69+
tableView.register(TextViewTableViewCell.loadNib(), forCellReuseIdentifier: TextViewTableViewCell.reuseIdentifier)
70+
71+
tableView.dataSource = self
72+
tableView.delegate = self
73+
74+
tableView.backgroundColor = .listBackground
75+
tableView.removeLastCellSeparator()
76+
}
77+
}
78+
79+
// MARK: - UITableViewDataSource Conformance
80+
//
81+
extension ProductPurchaseNoteViewController: UITableViewDataSource {
82+
83+
func numberOfSections(in tableView: UITableView) -> Int {
84+
return sections.count
85+
}
86+
87+
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
88+
return sections[section].rows.count
89+
}
90+
91+
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
92+
let row = sections[indexPath.section].rows[indexPath.row]
93+
94+
let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier, for: indexPath)
95+
configure(cell, for: row, at: indexPath)
96+
97+
return cell
98+
}
99+
}
100+
101+
// MARK: - UITableViewDelegate Conformance
102+
//
103+
extension ProductPurchaseNoteViewController: UITableViewDelegate {
104+
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
105+
tableView.deselectRow(at: indexPath, animated: true)
106+
}
107+
108+
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
109+
return UITableView.automaticDimension
110+
}
111+
112+
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
113+
return UITableView.automaticDimension
114+
}
115+
116+
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
117+
return sections[section].footer
118+
}
119+
}
120+
121+
// MARK: - Support for UITableViewDataSource
122+
//
123+
private extension ProductPurchaseNoteViewController {
124+
/// Configure cellForRowAtIndexPath:
125+
///
126+
func configure(_ cell: UITableViewCell, for row: Row, at indexPath: IndexPath) {
127+
switch cell {
128+
case let cell as TextViewTableViewCell:
129+
configurePurchaseNote(cell: cell)
130+
default:
131+
fatalError("Unidentified product purchase note row type")
132+
}
133+
}
134+
135+
func configurePurchaseNote(cell: TextViewTableViewCell) {
136+
cell.iconImage = nil
137+
cell.noteTextView.placeholder = NSLocalizedString("Add a purchase note...", comment: "Placeholder text in Product Purchase Note screen")
138+
cell.noteTextView.text = productSettings.purchaseNote?.strippedHTML
139+
cell.noteTextView.onTextChange = { [weak self] (text) in
140+
self?.productSettings.purchaseNote = text
141+
}
142+
}
143+
}
144+
145+
// MARK: - Keyboard management
146+
//
147+
private extension ProductPurchaseNoteViewController {
148+
/// Registers for all of the related Notifications
149+
///
150+
func startListeningToNotifications() {
151+
keyboardFrameObserver.startObservingKeyboardFrame()
152+
}
153+
}
154+
155+
extension ProductPurchaseNoteViewController: KeyboardScrollable {
156+
var scrollable: UIScrollView {
157+
return tableView
158+
}
159+
}
160+
161+
// MARK: - Constants
162+
//
163+
private extension ProductPurchaseNoteViewController {
164+
165+
/// Table Rows
166+
///
167+
enum Row {
168+
/// Listed in the order they appear on screen
169+
case purchaseNote
170+
171+
var reuseIdentifier: String {
172+
switch self {
173+
case .purchaseNote:
174+
return TextViewTableViewCell.reuseIdentifier
175+
}
176+
}
177+
}
178+
179+
/// Table Sections
180+
///
181+
struct Section {
182+
let footer: String?
183+
let rows: [Row]
184+
185+
init(footer: String? = nil, rows: [Row]) {
186+
self.footer = footer
187+
self.rows = rows
188+
}
189+
}
190+
}
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="ProductPurchaseNoteViewController" customModule="WooCommerce" customModuleProvider="target">
12+
<connections>
13+
<outlet property="tableView" destination="S3x-N3-wbe" id="3rj-CH-DeB"/>
14+
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
15+
</connections>
16+
</placeholder>
17+
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
18+
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
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="S3x-N3-wbe">
23+
<rect key="frame" x="0.0" y="44" width="414" height="818"/>
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="S3x-N3-wbe" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="H1C-V5-w8C"/>
30+
<constraint firstItem="S3x-N3-wbe" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="fAu-IQ-sXB"/>
31+
<constraint firstItem="S3x-N3-wbe" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="gXQ-hS-6R0"/>
32+
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="S3x-N3-wbe" secondAttribute="bottom" id="kQp-Tx-em8"/>
33+
</constraints>
34+
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
35+
<point key="canvasLocation" x="132" y="153"/>
36+
</view>
37+
</objects>
38+
</document>

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
@@ -120,7 +120,7 @@ private extension ProductSlugViewController {
120120
case let cell as TextFieldTableViewCell:
121121
configureSlug(cell: cell)
122122
default:
123-
fatalError("Unidentified product catalog visibility row type")
123+
fatalError("Unidentified product slug row type")
124124
}
125125
}
126126

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import UIKit
2+
3+
/// A text view which support the placeholder label.
4+
///
5+
final class EnhancedTextView: UITextView {
6+
7+
var onTextChange: ((String) -> Void)?
8+
9+
var placeholder: String? {
10+
didSet {
11+
placeholderLabel?.text = placeholder
12+
placeholderLabel?.sizeToFit()
13+
}
14+
}
15+
private var placeholderLabel: UILabel?
16+
17+
override var text: String! {
18+
didSet {
19+
if text.isEmpty {
20+
animatePlaceholder()
21+
}
22+
else {
23+
hidePlaceholder()
24+
}
25+
}
26+
}
27+
28+
override func awakeFromNib() {
29+
super.awakeFromNib()
30+
delegate = self
31+
configurePlaceholderLabel()
32+
}
33+
34+
private func animatePlaceholder() {
35+
UIView.animate(withDuration: Constants.animationDuration) { [weak self] in
36+
guard let self = self else {
37+
return
38+
}
39+
self.placeholderLabel?.alpha = self.text.isEmpty && !self.isFirstResponder ? 1 : 0
40+
}
41+
}
42+
43+
private func hidePlaceholder() {
44+
UIView.animate(withDuration: Constants.animationDuration) { [weak self] in
45+
guard let self = self else {
46+
return
47+
}
48+
self.placeholderLabel?.alpha = 0
49+
}
50+
}
51+
52+
}
53+
54+
55+
// MARK: Configurations
56+
//
57+
private extension EnhancedTextView {
58+
func configurePlaceholderLabel() {
59+
placeholderLabel = UILabel(frame: bounds)
60+
if let unwrappedLabel = placeholderLabel {
61+
addSubview(unwrappedLabel)
62+
}
63+
placeholderLabel?.numberOfLines = 0
64+
placeholderLabel?.applyBodyStyle()
65+
placeholderLabel?.textColor = .textSubtle
66+
}
67+
}
68+
69+
// MARK: UITextViewDelegate conformance
70+
//
71+
extension EnhancedTextView: UITextViewDelegate {
72+
73+
func textViewDidBeginEditing(_ textView: UITextView) {
74+
hidePlaceholder()
75+
}
76+
77+
func textViewDidEndEditing(_ textView: UITextView) {
78+
animatePlaceholder()
79+
}
80+
81+
func textViewDidChange(_ textView: UITextView) {
82+
animatePlaceholder()
83+
onTextChange?(textView.text)
84+
}
85+
}
86+
87+
// MARK: - Constants!
88+
//
89+
private extension EnhancedTextView {
90+
91+
enum Constants {
92+
static let animationDuration = 0.2
93+
}
94+
}

0 commit comments

Comments
 (0)