Skip to content

Commit ffc1234

Browse files
authored
Merge pull request #7788 from woocommerce/issue/7777-reply-to-reviews-ui
[Review Replies] Add reply button to product review detail
2 parents ee6d687 + 07d517d commit ffc1234

File tree

7 files changed

+82
-9
lines changed

7 files changed

+82
-9
lines changed

Experiments/Experiments/DefaultFeatureFlagService.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
3737
return true
3838
case .orderCreationSearchCustomers:
3939
return buildConfig == .localDeveloper || buildConfig == .alpha
40+
case .replyToProductReviews:
41+
return buildConfig == .localDeveloper || buildConfig == .alpha
4042
default:
4143
return true
4244
}

Experiments/Experiments/FeatureFlag.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,8 @@ public enum FeatureFlag: Int {
7777
/// Enables the Search Customers functionality in the Order Creation screen
7878
///
7979
case orderCreationSearchCustomers
80+
81+
/// Enables replying to product reviews
82+
///
83+
case replyToProductReviews
8084
}

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/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)