diff --git a/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift b/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift new file mode 100644 index 000000000000..0cdfbbf15e91 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift @@ -0,0 +1,155 @@ +import SwiftUI + +class CommentTableHeaderView: UITableViewHeaderFooterView, Reusable { + + enum Subtitle { + /// Subtext for a top-level comment on a post. + case post + + /// Subtext for a reply to a comment. + /// Requires a String describing the replied author's name. + case reply(String) + + /// Subtext for the comment threads. + case commentThread + + fileprivate var stringValue: String { + switch self { + case .post: + return Constants.postCommentSubText + case .reply(let authorName): + return String(format: Constants.replyCommentSubTextFormat, authorName) + case .commentThread: + return Constants.commentThreadSubText + } + } + } + + private let hostingController: UIHostingController + + init(title: String, + subtitle: Subtitle, + showsDisclosureIndicator: Bool = false, + reuseIdentifier: String? = CommentTableHeaderView.defaultReuseID) { + let headerView = CommentHeaderView(title: title, + subtitle: subtitle, + showsDisclosureIndicator: showsDisclosureIndicator) + hostingController = .init(rootView: headerView) + super.init(reuseIdentifier: reuseIdentifier) + configureView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Private methods + +private extension CommentTableHeaderView { + + func configureView() { + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + hostingController.view.backgroundColor = .clear + contentView.addSubview(hostingController.view) + contentView.pinSubviewToAllEdges(hostingController.view) + } + + enum Constants { + static let postCommentSubText = NSLocalizedString( + "comment.header.subText.post", + value: "Comment on", + comment: """ + Provides a hint that the current screen displays a comment on a post. + The title of the post will be displayed below this text. + Example: Comment on \n My First Post + """ + ) + + static let replyCommentSubTextFormat = NSLocalizedString( + "comment.header.subText.reply", + value: "Reply to %1$@", + comment: """ + Provides a hint that the current screen displays a reply to a comment. + %1$@ is a placeholder for the comment author's name that's been replied to. + Example: Reply to Pamela Nguyen + """ + ) + + static let commentThreadSubText = NSLocalizedString( + "comment.header.subText.commentThread", + value: "Comments on", + comment: """ + Sentence fragment. + The full phrase is 'Comments on' followed by the title of the post on a separate line. + """ + ) + } +} + +// MARK: - SwiftUI + +private struct CommentHeaderView: View { + + @State var title = String() + @State var subtitle: CommentTableHeaderView.Subtitle = .post + @State var showsDisclosureIndicator = true + + var body: some View { + if #available(iOS 15.0, *) { + // Material ShapeStyles are only available from iOS 15.0. + content.background(.ultraThinMaterial) + } else { + ZStack { + VisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)) + content + } + } + } + + var content: some View { + HStack { + text + Spacer() + if showsDisclosureIndicator { + disclosureIndicator + } + } + .padding(EdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16)) + } + + var text: some View { + VStack(alignment: .leading) { + Text(subtitle.stringValue) + .lineLimit(1) + .font(.footnote) + .foregroundColor(Color(.secondaryLabel)) + Text(title) + .lineLimit(1) + .font(.subheadline) + .foregroundColor(Color(.text)) + } + } + + var disclosureIndicator: some View { + Image(systemName: "chevron.forward") + .renderingMode(.template) + .foregroundColor(Color(.secondaryLabel)) + .font(.caption.weight(.semibold)) + .imageScale(.large) + } +} + +// MARK: SwiftUI VisualEffect support for iOS 14 + +private struct VisualEffectView: UIViewRepresentable { + var effect: UIVisualEffect + + func makeUIView(context: Context) -> UIVisualEffectView { + return UIVisualEffectView() + } + + func updateUIView(_ uiView: UIVisualEffectView, context: Context) { + uiView.effect = effect + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift index 362f46688bb5..68a2dd5b7d39 100644 --- a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift @@ -33,6 +33,14 @@ extension NSNotification.Name { return .init() } + if FeatureFlag.commentModerationUpdate.enabled { + let headerView = CommentTableHeaderView(title: post.titleForDisplay(), + subtitle: .commentThread, + showsDisclosureIndicator: true) + headerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleHeaderTapped))) + return headerView + } + let cell = CommentHeaderTableViewCell() cell.backgroundColor = .systemBackground cell.configure(for: .thread, subtitle: post.titleForDisplay(), showsDisclosureIndicator: allowsPushingPostDetails) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index a23e67eed617..98867169c23e 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -5531,6 +5531,8 @@ FE4DC5AA293A84F8008F322F /* WordPressExportRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4DC5A6293A79F1008F322F /* WordPressExportRoute.swift */; }; FE5096592A17A69F00DDD071 /* TwitterDeprecationTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5096582A17A69F00DDD071 /* TwitterDeprecationTableFooterView.swift */; }; FE50965A2A17A69F00DDD071 /* TwitterDeprecationTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5096582A17A69F00DDD071 /* TwitterDeprecationTableFooterView.swift */; }; + FE50965C2A20D0F300DDD071 /* CommentTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE50965B2A20D0F300DDD071 /* CommentTableHeaderView.swift */; }; + FE50965D2A20D0F300DDD071 /* CommentTableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE50965B2A20D0F300DDD071 /* CommentTableHeaderView.swift */; }; FE6BB143293227AC001E5F7A /* ContentMigrationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE6BB142293227AC001E5F7A /* ContentMigrationCoordinator.swift */; }; FE6BB144293227AC001E5F7A /* ContentMigrationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE6BB142293227AC001E5F7A /* ContentMigrationCoordinator.swift */; }; FE6BB1462932289B001E5F7A /* ContentMigrationCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE6BB1452932289B001E5F7A /* ContentMigrationCoordinatorTests.swift */; }; @@ -9314,6 +9316,7 @@ FE4DC5A6293A79F1008F322F /* WordPressExportRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressExportRoute.swift; sourceTree = ""; }; FE5096572A13D5BA00DDD071 /* WordPress 149.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 149.xcdatamodel"; sourceTree = ""; }; FE5096582A17A69F00DDD071 /* TwitterDeprecationTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterDeprecationTableFooterView.swift; sourceTree = ""; }; + FE50965B2A20D0F300DDD071 /* CommentTableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentTableHeaderView.swift; sourceTree = ""; }; FE59DA9527D1FD0700624D26 /* WordPress 138.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 138.xcdatamodel"; sourceTree = ""; }; FE6BB142293227AC001E5F7A /* ContentMigrationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentMigrationCoordinator.swift; sourceTree = ""; }; FE6BB1452932289B001E5F7A /* ContentMigrationCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentMigrationCoordinatorTests.swift; sourceTree = ""; }; @@ -18083,6 +18086,9 @@ FE43DAAD26DFAD1C00CFF595 /* CommentContentTableViewCell.swift */, FE43DAAE26DFAD1C00CFF595 /* CommentContentTableViewCell.xib */, FEA7948C26DD136700CEC520 /* CommentHeaderTableViewCell.swift */, + 9839CEB926FAA0510097406E /* CommentModerationBar.swift */, + 98F9FB2D270282C100ADF552 /* CommentModerationBar.xib */, + FE50965B2A20D0F300DDD071 /* CommentTableHeaderView.swift */, ); name = Detail; sourceTree = ""; @@ -21969,6 +21975,7 @@ 46C984682527863E00988BB9 /* LayoutPickerAnalyticsEvent.swift in Sources */, 7E442FCD20F6AB9C00DEACA5 /* ActivityRange.swift in Sources */, 9AF9551821A1D7970057827C /* DiffAbstractValue+Attributes.swift in Sources */, + FE50965C2A20D0F300DDD071 /* CommentTableHeaderView.swift in Sources */, 8BD66ED42787530C00CCD95A /* PostsCardViewModel.swift in Sources */, 80EF928D280E83110064A971 /* QuickStartToursCollection.swift in Sources */, 7E58879A20FE8D9300DB6F80 /* Environment.swift in Sources */, @@ -25240,6 +25247,7 @@ FABB25282602FC2C00C8785C /* PostStatsTitleCell.swift in Sources */, FABB25292602FC2C00C8785C /* CommentService.m in Sources */, 0880BADD29ED6FF3002D3AB0 /* UIColor+DesignSystem.swift in Sources */, + FE50965D2A20D0F300DDD071 /* CommentTableHeaderView.swift in Sources */, FABB252A2602FC2C00C8785C /* MediaLibraryStrings.swift in Sources */, FABB252B2602FC2C00C8785C /* StoreFetchingStatus.swift in Sources */, FABB252C2602FC2C00C8785C /* SiteDateFormatters.swift in Sources */,