Skip to content

Commit 4298cb7

Browse files
committed
Merge branch 'trunk' into hack/lockscreen-widgets-app-link
# Conflicts: # Experiments/Experiments/DefaultFeatureFlagService.swift # Experiments/Experiments/FeatureFlag.swift
2 parents 6f8fe22 + 7e4f77b commit 4298cb7

File tree

8 files changed

+93
-9
lines changed

8 files changed

+93
-9
lines changed

Experiments/Experiments/DefaultFeatureFlagService.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
3939
return buildConfig == .localDeveloper || buildConfig == .alpha
4040
case .lockscreenWidgets:
4141
return buildConfig == .localDeveloper || buildConfig == .alpha
42+
case .replyToProductReviews:
43+
return buildConfig == .localDeveloper || buildConfig == .alpha
4244
default:
4345
return true
4446
}

Experiments/Experiments/FeatureFlag.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,8 @@ public enum FeatureFlag: Int {
8181
/// Enables lock screen widgets
8282
///
8383
case lockscreenWidgets
84+
85+
/// Enables replying to product reviews
86+
///
87+
case replyToProductReviews
8488
}

WooCommerce/Classes/Extensions/UIImage+Woo.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,12 @@ extension UIImage {
804804
.imageFlippedForRightToLeftLayoutDirection()
805805
}
806806

807+
/// Reply Icon
808+
///
809+
static var replyImage: UIImage {
810+
return UIImage.gridicon(.reply)
811+
}
812+
807813
/// Search Icon - used in `UIBarButtonItem`
808814
///
809815
static var searchBarButtonItemImage: UIImage {

WooCommerce/Classes/ViewRelated/Products/Variations/Bulk Update/BulkUpdateViewController.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,21 @@ final class BulkUpdateViewController: UIViewController, GhostableViewController
128128
noticePresenter.enqueue(notice: notice)
129129
}
130130

131+
/// Displays the success price update `Notice`.
132+
///
133+
private func displayPriceUpdatedNotice() {
134+
let title = Localization.pricesUpdated
135+
let notice = Notice(title: title, feedbackType: .success)
136+
noticePresenter.enqueue(notice: notice)
137+
}
138+
131139
/// Called when the price option is selected
132140
///
133141
private func navigateToEditPriceSettings() {
134142
let bulkUpdatePriceSettingsViewModel = viewModel.viewModelForBulkUpdatePriceOfType(.regular, priceUpdateDidFinish: { [weak self] in
135143
guard let self = self else { return }
136144
self.navigationController?.popToViewController(self, animated: true)
145+
self.displayPriceUpdatedNotice()
137146
})
138147
let viewController = BulkUpdatePriceViewController(viewModel: bulkUpdatePriceSettingsViewModel)
139148
show(viewController, sender: nil)
@@ -256,6 +265,8 @@ private extension BulkUpdateViewController {
256265
static let noticeUnableToSyncVariations = NSLocalizedString("Unable to retrieve variations",
257266
comment: "Unable to retrieve variations for bulk update screen")
258267
static let noticeRetryAction = NSLocalizedString("Retry", comment: "Retry Action")
268+
static let pricesUpdated = NSLocalizedString("Prices updated successfully.",
269+
comment: "Notice title when updating the price via the bulk variation screen")
259270
}
260271
}
261272

WooCommerce/Classes/ViewRelated/Reviews/Cells/NoteDetailsCommentTableViewCell.swift

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ final class NoteDetailsCommentTableViewCell: UITableViewCell {
5656
///
5757
@IBOutlet private var approvalButton: UIButton!
5858

59+
/// Button: Reply
60+
///
61+
@IBOutlet var replyButton: UIButton!
62+
5963
/// Custom UIView: Rating star view
6064
///
6165
@IBOutlet private var starRatingView: RatingView!
@@ -76,6 +80,10 @@ final class NoteDetailsCommentTableViewCell: UITableViewCell {
7680
///
7781
var onUnapprove: (() -> Void)?
7882

83+
/// Closure to be executed whenever the Reply Button is pressed.
84+
///
85+
var onReply: (() -> Void)?
86+
7987

8088
/// Indicates if the Spam Button is enabled (or not!)
8189
///
@@ -123,6 +131,17 @@ final class NoteDetailsCommentTableViewCell: UITableViewCell {
123131
}
124132
}
125133

134+
/// Indicates if the Reply Button is enabled (or not!)
135+
///
136+
var isReplyEnabled: Bool {
137+
get {
138+
return replyButton.isHidden
139+
}
140+
set {
141+
replyButton.isHidden = !newValue
142+
}
143+
}
144+
126145
/// Title: Usually displays the Author's Name.
127146
///
128147
var titleText: String? {
@@ -222,6 +241,10 @@ private extension NoteDetailsCommentTableViewCell {
222241
approvalButton.accessibilityLabel = Approve.normalLabel
223242
approvalButton.accessibilityIdentifier = "single-review-approval-button"
224243

244+
replyButton.applyNoteDetailsActionStyle(icon: .replyImage)
245+
replyButton.setTitle(Reply.normalTitle, for: .normal)
246+
replyButton.accessibilityLabel = Reply.normalLabel
247+
replyButton.accessibilityIdentifier = "single-review-reply-button"
225248
}
226249

227250
func configureTitleLabel() {
@@ -240,7 +263,7 @@ private extension NoteDetailsCommentTableViewCell {
240263
/// Setup: Default Action(s) Style
241264
///
242265
func configureDefaultAppearance() {
243-
let buttons = [spamButton, trashButton, approvalButton].compactMap { $0 }
266+
let buttons = [spamButton, trashButton, approvalButton, replyButton].compactMap { $0 }
244267

245268
for button in buttons {
246269
refreshAppearance(button: button)
@@ -299,6 +322,13 @@ private extension NoteDetailsCommentTableViewCell {
299322

300323
onClick?()
301324
}
325+
326+
/// Reply Button Callback
327+
///
328+
@IBAction func replyWasPressed(_ sender: UIButton) {
329+
sender.animateImageOverlay(style: .explosion)
330+
onReply?()
331+
}
302332
}
303333

304334

@@ -328,6 +358,14 @@ private struct Approve {
328358
}
329359

330360

361+
// MARK: - Reply Button: Strings!
362+
//
363+
private struct Reply {
364+
static let normalTitle = NSLocalizedString("Reply", comment: "Reply to a comment")
365+
static let normalLabel = NSLocalizedString("Opens a text view to reply to the comment", comment: "Reply to a comment. Spoken Hint.")
366+
}
367+
368+
331369
// MARK: - Star View: Defaults
332370
//
333371
private struct Star {

WooCommerce/Classes/ViewRelated/Reviews/Cells/NoteDetailsCommentTableViewCell.xib

Lines changed: 17 additions & 7 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="17701" 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="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
33
<device id="retina4_7" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
77
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
88
</dependencies>
99
<objects>
@@ -49,13 +49,13 @@
4949
<rect key="frame" x="56" y="0.0" width="232" height="40"/>
5050
<subviews>
5151
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="249" horizontalCompressionResistancePriority="250" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MB2-nk-Fuo">
52-
<rect key="frame" x="0.0" y="0.0" width="232" height="24"/>
52+
<rect key="frame" x="0.0" y="0.0" width="232" height="25.5"/>
5353
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
5454
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
5555
<nil key="highlightedColor"/>
5656
</label>
5757
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" horizontalCompressionResistancePriority="250" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6d4-9R-dcK">
58-
<rect key="frame" x="0.0" y="24" width="232" height="16"/>
58+
<rect key="frame" x="0.0" y="25.5" width="232" height="14.5"/>
5959
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
6060
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
6161
<nil key="highlightedColor"/>
@@ -98,15 +98,15 @@
9898
<rect key="frame" x="0.0" y="160" width="288" height="52"/>
9999
<subviews>
100100
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qUJ-uA-W4N" customClass="VerticalButton" customModule="WooCommerce" customModuleProvider="target">
101-
<rect key="frame" x="0.0" y="0.0" width="89.5" height="52"/>
101+
<rect key="frame" x="0.0" y="0.0" width="64.5" height="52"/>
102102
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
103103
<state key="normal" title="Spam"/>
104104
<connections>
105105
<action selector="spamWasPressed:" destination="ba1-rz-4HV" eventType="touchUpInside" id="QoL-XB-ViB"/>
106106
</connections>
107107
</button>
108108
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6As-Iw-caM" customClass="VerticalButton" customModule="WooCommerce" customModuleProvider="target">
109-
<rect key="frame" x="99.5" y="0.0" width="89" height="52"/>
109+
<rect key="frame" x="74.5" y="0.0" width="64.5" height="52"/>
110110
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
111111
<state key="normal" title="Trash">
112112
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -116,13 +116,22 @@
116116
</connections>
117117
</button>
118118
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uc0-Kk-Npt" customClass="VerticalButton" customModule="WooCommerce" customModuleProvider="target">
119-
<rect key="frame" x="198.5" y="0.0" width="89.5" height="52"/>
119+
<rect key="frame" x="149" y="0.0" width="64.5" height="52"/>
120120
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
121121
<state key="normal" title="Approve"/>
122122
<connections>
123123
<action selector="approveWasPressed:" destination="ba1-rz-4HV" eventType="touchUpInside" id="XrG-F9-l5a"/>
124124
</connections>
125125
</button>
126+
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jZy-9J-G3f" userLabel="Reply Button" customClass="VerticalButton" customModule="WooCommerce" customModuleProvider="target">
127+
<rect key="frame" x="223.5" y="0.0" width="64.5" height="52"/>
128+
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
129+
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
130+
<state key="normal" title="Reply"/>
131+
<connections>
132+
<action selector="replyWasPressed:" destination="ba1-rz-4HV" eventType="touchUpInside" id="jZu-dE-X4p"/>
133+
</connections>
134+
</button>
126135
</subviews>
127136
<constraints>
128137
<constraint firstAttribute="height" constant="52" id="gD3-cL-zcL"/>
@@ -143,6 +152,7 @@
143152
<outlet property="approvalButton" destination="uc0-Kk-Npt" id="tZL-iM-Q2H"/>
144153
<outlet property="detailsLabel" destination="6d4-9R-dcK" id="pCn-M7-He8"/>
145154
<outlet property="gravatarImageView" destination="qcp-iw-3SL" id="e8j-K5-3V4"/>
155+
<outlet property="replyButton" destination="jZy-9J-G3f" id="uvH-7W-9fm"/>
146156
<outlet property="spamButton" destination="qUJ-uA-W4N" id="Gbe-6K-KhN"/>
147157
<outlet property="starRatingView" destination="1Iy-55-tos" id="bcq-5i-2Gp"/>
148158
<outlet property="textView" destination="FpQ-yp-a6e" id="qCC-ZC-o7p"/>

WooCommerce/Classes/ViewRelated/Reviews/ReviewDetailsViewController.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import UIKit
22
import Yosemite
33
import Gridicons
44
import SafariServices
5+
import Experiments
56

67

78
// MARK: - ReviewDetailsViewController
@@ -44,13 +45,16 @@ final class ReviewDetailsViewController: UIViewController {
4445
///
4546
private var rows = [Row]()
4647

48+
private let featureFlagService: FeatureFlagService
49+
4750
/// Designated Initializer
4851
///
49-
init(productReview: ProductReview, product: Product?, notification: Note?) {
52+
init(productReview: ProductReview, product: Product?, notification: Note?, featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) {
5053
self.productReview = productReview
5154
self.siteID = productReview.siteID
5255
self.product = product
5356
self.notification = notification
57+
self.featureFlagService = featureFlagService
5458
super.init(nibName: nil, bundle: nil)
5559
}
5660

@@ -332,6 +336,7 @@ private extension ReviewDetailsViewController {
332336
commentCell.isTrashEnabled = true
333337
commentCell.isSpamEnabled = true
334338
commentCell.isApproveSelected = productReview.status == .approved
339+
commentCell.isReplyEnabled = featureFlagService.isFeatureFlagEnabled(.replyToProductReviews)
335340

336341
let reviewID = productReview.reviewID
337342
let reviewSiteID = productReview.siteID
@@ -378,6 +383,10 @@ private extension ReviewDetailsViewController {
378383

379384
self.moderateReview(siteID: reviewSiteID, reviewID: reviewID, doneStatus: .spam, undoStatus: .unspam)
380385
}
386+
387+
commentCell.onReply = {
388+
// TODO: Open a text view to send a comment reply to the product review
389+
}
381390
}
382391
}
383392

WooCommerce/WooCommerceTests/Extensions/IconsTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,4 +668,8 @@ final class IconsTests: XCTestCase {
668668
func test_lock_icon_is_not_nil() {
669669
XCTAssertNotNil(UIImage.lockImage)
670670
}
671+
672+
func test_reply_icon_is_not_nil() {
673+
XCTAssertNotNil(UIImage.replyImage)
674+
}
671675
}

0 commit comments

Comments
 (0)