Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions WordPress/Classes/Services/CommentService.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ extern NSUInteger const WPTopLevelHierarchicalCommentsPerPage;
// Replies
- (void)replyToPost:(ReaderPost *)post
content:(NSString *)content
success:(void (^ _Nullable)(void))success
success:(void (^ _Nullable)(Comment * _Nullable comment))success
failure:(void (^ _Nullable)(NSError * _Nullable error))failure;

- (void)replyToHierarchicalCommentWithID:(NSNumber *)commentID
post:(ReaderPost *)post
content:(NSString *)content
success:(void (^ _Nullable)(void))success
success:(void (^ _Nullable)(Comment * _Nullable comment))success
failure:(void (^ _Nullable)(NSError * _Nullable error))failure;

- (void)replyToCommentWithID:(NSNumber *)commentID
Expand Down
16 changes: 12 additions & 4 deletions WordPress/Classes/Services/CommentService.m
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ - (void)updateCommentWithID:(NSNumber *)commentID
// Replies
- (void)replyToPost:(ReaderPost *)post
content:(NSString *)content
success:(void (^)(void))success
success:(void (^)(Comment * _Nullable comment))success
failure:(void (^)(NSError *error))failure
{
// Create and optimistically save a comment, based on the current wpcom acct
Expand All @@ -737,7 +737,11 @@ - (void)replyToPost:(ReaderPost *)post
remoteComment.content = [self sanitizeCommentContent:remoteComment.content isPrivateSite:isPrivateSite];

[self updateHierarchicalComment:comment withRemoteComment:remoteComment];
} completion:success onQueue:dispatch_get_main_queue()];
} completion:^{
if (success) {
success([self.coreDataStack.mainContext existingObjectWithID:commentID error:nil]);
}
} onQueue:dispatch_get_main_queue()];
};

void (^failureBlock)(NSError *error) = ^void(NSError *error) {
Expand Down Expand Up @@ -765,7 +769,7 @@ - (void)replyToPost:(ReaderPost *)post
- (void)replyToHierarchicalCommentWithID:(NSNumber *)commentID
post:(ReaderPost *)post
content:(NSString *)content
success:(void (^)(void))success
success:(void (^)(Comment * _Nullable comment))success
failure:(void (^)(NSError *error))failure
{
// Create and optimistically save a comment, based on the current wpcom acct
Expand All @@ -791,7 +795,11 @@ - (void)replyToHierarchicalCommentWithID:(NSNumber *)commentID
remoteComment.content = [self sanitizeCommentContent:remoteComment.content isPrivateSite:isPrivateSite];

[self updateHierarchicalComment:comment withRemoteComment:remoteComment];
} completion:success onQueue:dispatch_get_main_queue()];
} completion:^{
if (success) {
success([self.coreDataStack.mainContext existingObjectWithID:commentObjectID error:nil]);
}
} onQueue:dispatch_get_main_queue()];
};

void (^failureBlock)(NSError *error) = ^void(NSError *error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -993,53 +993,62 @@ private extension CommentDetailViewController {

@objc func buttonAddCommentTapped() {
let viewModel = CommentCreateViewModel(replyingTo: comment) { [weak self] in
try await self?.createReply(content: $0)
try await self?.createReply(content: $0) ?? false
}
let composerVC = CommentCreateViewController(viewModel: viewModel)
let navigationVC = UINavigationController(rootViewController: composerVC)
present(navigationVC, animated: true)
}

/// - returns: `true` if the comment is pending moderation (not immediately approved).
@MainActor
func createReply(content: String) async throws {
isNotificationComment ? WPAppAnalytics.track(.notificationsCommentRepliedTo) :
CommentAnalytics.trackCommentRepliedTo(comment: comment)
func createReply(content: String) async throws -> Bool {
isNotificationComment
? WPAppAnalytics.track(.notificationsCommentRepliedTo)
: CommentAnalytics.trackCommentRepliedTo(comment: comment)

// If there is no Blog, try with the Post.
guard comment.blog != nil else {
try await createPostCommentReply(content: content)
return
return try await createPostCommentReply(content: content)
}

try await withUnsafeThrowingContinuation { continuation in
return try await withUnsafeThrowingContinuation { continuation in
commentService.createReply(for: comment, content: content) { reply in
self.commentService.uploadComment(reply, success: { [weak self] in
self?.refreshCommentReplyIfNeeded()
continuation.resume()
}, failure: { error in
DDLogError("Failed uploading comment reply: \(String(describing: error))")
continuation.resume(throwing: error ?? URLError(.unknown))
})
self.commentService.uploadComment(
reply,
success: { [weak self] in
self?.refreshCommentReplyIfNeeded()
continuation.resume(returning: reply.isApproved() == false)
},
failure: { error in
DDLogError("Failed uploading comment reply: \(String(describing: error))")
continuation.resume(throwing: error ?? URLError(.unknown))
}
)
}
}
}

/// - returns: `true` if the comment is pending moderation (not immediately approved).
@MainActor
func createPostCommentReply(content: String) async throws {
func createPostCommentReply(content: String) async throws -> Bool {
guard let post = comment.post as? ReaderPost else {
return
return false
}
try await withUnsafeThrowingContinuation { continuation in
commentService.replyToHierarchicalComment(withID: NSNumber(value: comment.commentID),
post: post,
content: content,
success: { [weak self] in
self?.refreshCommentReplyIfNeeded()
continuation.resume()
}, failure: { error in
DDLogError("Failed creating post comment reply: \(String(describing: error))")
continuation.resume(throwing: error ?? URLError(.unknown))
})
return try await withUnsafeThrowingContinuation { continuation in
commentService.replyToHierarchicalComment(
withID: NSNumber(value: comment.commentID),
post: post,
content: content,
success: { [weak self] newComment in
self?.refreshCommentReplyIfNeeded()
continuation.resume(returning: newComment?.isApproved() == false)
},
failure: { error in
DDLogError("Failed creating post comment reply: \(String(describing: error))")
continuation.resume(throwing: error ?? URLError(.unknown))
}
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,22 @@ final class CommentCreateViewController: UIViewController {
Task { @MainActor in
do {
let text = await editorVC.text
try await viewModel.save(content: text)
let isPendingModeration = try await viewModel.save(content: text)
UINotificationFeedbackGenerator().notificationOccurred(.success)
NotificationCenter.default.post(name: .ReaderCommentModifiedNotification, object: nil)
presentingViewController?.dismiss(animated: true)
presentingViewController?
.dismiss(animated: true) {
guard isPendingModeration else { return }
Notice(
title: Strings.commentHeldForModeration,
style: InAppUpdateNoticeStyle(
icon: UIImage(systemName: "checkmark.seal.fill"),
iconColor: UIAppColor.success,
title: Strings.commentHeldForModeration
)
)
.post()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This notice does not apply to all sites, right? Some site you can just publish without being reviewed. Like, if you are member of another site, your comments on that site should go straight to approved?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok will update according to that 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated Code, now toast will show only when comment is unapproved.

The core problem: the composer had no way to know whether a comment was actually held for moderation. I traced the real status through the whole call chain and made it flow back to the UI.

} catch {
setLoading(false)
UINotificationFeedbackGenerator().notificationOccurred(.error)
Expand Down Expand Up @@ -105,9 +117,10 @@ final class CommentCreateViewController: UIViewController {
if viewModel.canSaveDraft {
alert.addActionWithTitle(Strings.closeConfirmationAlertSaveDraft, style: .default) { [weak self] _ in
self?.viewModel.saveDraft(content)
self?.presentingViewController?.dismiss(animated: true) {
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
self?.presentingViewController?
.dismiss(animated: true) {
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
}
}
alert.popoverPresentationController?.barButtonItem = navigationItem.leftBarButtonItem
Expand Down Expand Up @@ -147,6 +160,7 @@ extension CommentCreateViewController: CommentEditorViewControllerDelegate {
private enum Strings {
static let send = NSLocalizedString("commentCreate.send", value: "Send", comment: "Navigation bar button title")
static let failedToSend = NSLocalizedString("commentCreate.failedToSentComment", value: "Failed to send comment", comment: "Error title")
static let commentHeldForModeration = NSLocalizedString("commentCreate.commentHeldForModeration", value: "Comment is awaiting review", comment: "Toast title shown after successfully submitting a comment")
static let closeConfirmationAlertCancel = NSLocalizedString("commentCreate.closeConfirmationAlert.keepEditing", value: "Keep Editing", comment: "Button to keep the changes in an alert confirming discaring changes")
static let closeConfirmationAlertDelete = NSLocalizedString("commentCreate.closeConfirmationAlert.deleteDraft", value: "Delete Draft", comment: "Button in an alert confirming discaring a new draft")
static let closeConfirmationAlertSaveDraft = NSLocalizedString("commentCreate.closeConfirmationAlert.saveDraft", value: "Save Draft", comment: "Button in an alert confirming saving a new draft")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ final class CommentCreateViewModel {

/// - note: It's a temporary solution until the respective save logic
/// can be moved from the view controllers.
private var _save: (String) async throws -> Void = { _ in
/// - returns: `true` if the comment is pending moderation (not immediately approved).
private var _save: (String) async throws -> Bool = { _ in
wpAssertionFailure("Not implemented")
return false
}

var isGutenbergEnabled: Bool {
Expand All @@ -49,12 +51,12 @@ final class CommentCreateViewModel {
}

self._save = { [weak self] in
try await self?.sendComment($0, post: post, replyingTo: comment)
try await self?.sendComment($0, post: post, replyingTo: comment) ?? false
}
}

/// Create a reply to the given comment (from notifications)
init(replyingTo comment: Comment, save: @escaping (String) async throws -> Void) {
init(replyingTo comment: Comment, save: @escaping (String) async throws -> Bool) {
let siteID = comment.associatedSiteID ?? 0

self.siteID = siteID
Expand All @@ -72,27 +74,37 @@ final class CommentCreateViewModel {
Strings.leaveComment
}

func save(content: String) async throws {
try await _save(content)
/// - returns: `true` if the comment is pending moderation (not immediately approved).
func save(content: String) async throws -> Bool {
let isPendingModeration = try await _save(content)
deleteDraft()
return isPendingModeration
}

// MARK: Reader

private func sendComment(_ content: String, post: ReaderPost, replyingTo comment: Comment? = nil) async throws {
private func sendComment(
_ content: String,
post: ReaderPost,
replyingTo comment: Comment? = nil
) async throws -> Bool {
try await withUnsafeThrowingContinuation { [weak self] continuation in
let service = CommentService(coreDataStack: ContextManager.shared)
if let comment {
service.replyToHierarchicalComment(withID: comment.commentID as NSNumber, post: post, content: content) {
service.replyToHierarchicalComment(
withID: comment.commentID as NSNumber,
post: post,
content: content
) { newComment in
self?.trackReply(isReplyingToComment: true, post: post)
continuation.resume()
continuation.resume(returning: newComment?.isApproved() == false)
} failure: {
continuation.resume(throwing: $0 ?? URLError(.unknown))
}
} else {
service.reply(to: post, content: content) {
service.reply(to: post, content: content) { newComment in
self?.trackReply(isReplyingToComment: true, post: post)
continuation.resume()
continuation.resume(returning: newComment?.isApproved() == false)
} failure: {
continuation.resume(throwing: $0 ?? URLError(.unknown))
}
Expand Down
45 changes: 41 additions & 4 deletions WordPress/Classes/ViewRelated/System/Notices/NoticeStyle.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import UIKit
import WordPressShared
import DesignSystem

public enum NoticeAnimationStyle {
case moveIn
Expand Down Expand Up @@ -73,9 +74,46 @@ public struct NormalNoticeStyle: NoticeStyle {

public struct InAppUpdateNoticeStyle: NoticeStyle {
public let attributedMessage: NSAttributedString?

init(attributedMessage: NSAttributedString? = nil) {
self.attributedMessage = attributedMessage
public let isDismissable: Bool

/// - Parameters:
/// - icon: An optional SF Symbol rendered inline before `title`, e.g. a checkmark seal to indicate success.
/// - iconColor: The tint color applied to `icon`.
/// - title: When provided (with or without `icon`), builds `attributedMessage` from it and makes the
/// Notice auto-dismiss after a few seconds. When `nil`, the Notice falls back to its own `title`/`message`
/// and stays on screen until the user dismisses it, matching the original in-app-update banner behavior.
init(icon: UIImage? = nil, iconColor: UIColor = .invertedLabel, title: String? = nil) {
guard let title else {
self.attributedMessage = nil
self.isDismissable = false
return
}

self.isDismissable = true

let font = UIFont.boldSystemFont(ofSize: 14.0)
let message = NSMutableAttributedString()

if let icon = icon?.withTintColor(iconColor, renderingMode: .alwaysOriginal) {
let attachment = NSTextAttachment(image: icon)
attachment.accessibilityLabel = "" // Decorative; the title text conveys the meaning.
let iconHeight = font.lineHeight
let ratio = icon.size.width / icon.size.height
attachment.bounds = CGRect(
x: 0,
y: (font.capHeight - iconHeight) / 2,
width: iconHeight * ratio,
height: iconHeight
)
message.append(NSAttributedString(attachment: attachment))
message.append(NSAttributedString(string: " "))
}

message.append(
NSAttributedString(string: title, attributes: [.font: font, .foregroundColor: UIColor.invertedLabel])
)

self.attributedMessage = message
}

// Return new UIFont instance everytime in order to be responsive to accessibility font size changes
Expand All @@ -85,7 +123,6 @@ public struct InAppUpdateNoticeStyle: NoticeStyle {

public let directionalLayoutMargins = NSDirectionalEdgeInsets(top: 13.0, leading: 16.0, bottom: 13.0, trailing: 16.0)

public var isDismissable = false
public let showNextArrow = false

public let animationStyle = NoticeAnimationStyle.moveIn
Expand Down