Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve visibility picker and integrate it in Post Settings #23154

Merged
merged 9 commits into from
May 7, 2024
10 changes: 8 additions & 2 deletions WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ extension AbstractPost {
static let publicLabel = NSLocalizedString("Public", comment: "Privacy setting for posts set to 'Public' (default). Should be the same as in core WP.")

/// A title describing the status. Ie.: "Public" or "Private" or "Password protected"
///
/// - warning: deprecated (kahu-offline-mode) (use ``PostVisibility``)
@objc var titleForVisibility: String {
guard FeatureFlag.syncPublishing.enabled else {
return _titleForVisibility
}
return PostVisibility(post: self).localizedTitle
}

/// - warning: deprecated (kahu-offline-mode) (use ``PostVisibility``)
@objc private var _titleForVisibility: String {
if password != nil {
return AbstractPost.passwordProtectedLabel
} else if status == .publishPrivate {
Expand Down
17 changes: 11 additions & 6 deletions WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,10 @@ class PostCardStatusViewModel: NSObject, AbstractPostMenuViewModel {
return .warning
}
switch post.status ?? .draft {
case .pending:
return .success
case .scheduled:
return .primary(.shade40)
case .trash:
return .error
default:
return .neutral(.shade70)
return .secondaryLabel
}
}

Expand Down Expand Up @@ -277,9 +273,18 @@ class PostCardStatusViewModel: NSObject, AbstractPostMenuViewModel {
func statusAndBadges(separatedBy separator: String) -> String {
let sticky = post.isStickyPost ? Constants.stickyLabel : ""
let pending = (post.status == .pending && isSyncPublishingEnabled) ? Constants.pendingReview : ""
let visibility: String = {
let visibility = PostVisibility(post: post)
switch visibility {
case .public:
return ""
case .private, .protected:
return visibility.localizedTitle
}
}()
let status = self.status ?? ""

return [status, pending, sticky].filter { !$0.isEmpty }.joined(separator: separator)
return [status, visibility, pending, sticky].filter { !$0.isEmpty }.joined(separator: separator)
}

/// Determine what the failed status message should be and return it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import UIKit
import CoreData
import Combine
import WordPressKit
import SwiftUI

extension PostSettingsViewController {
static func make(for post: AbstractPost) -> PostSettingsViewController {
Expand Down Expand Up @@ -178,6 +179,47 @@ extension PostSettingsViewController: UIAdaptivePresentationControllerDelegate {
}
}

// MARK: - PostSettingsViewController (Visibility)

extension PostSettingsViewController {
@objc func showUpdatedPostVisibilityPicker() {
let view = PostVisibilityPicker(selection: .init(post: apost)) { [weak self] selection in
guard let self else { return }

WPAnalytics.track(.editorPostVisibilityChanged, properties: ["via": "settings"])

switch selection.type {
case .public, .protected:
if self.apost.original().status == .scheduled {
// Keep it scheduled
} else {
self.apost.status = .publish
}
case .private:
if self.apost.original().status == .scheduled {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
self.showWarningPostWillBePublishedAlert()
}
}
self.apost.status = .publishPrivate
}
self.apost.password = selection.password.isEmpty ? nil : selection.password
self.navigationController?.popViewController(animated: true)
self.reloadData()
}
let viewController = UIHostingController(rootView: view)
viewController.title = PostVisibilityPicker.title
viewController.configureDefaultNavigationBarAppearance()
navigationController?.pushViewController(viewController, animated: true)
}

private func showWarningPostWillBePublishedAlert() {
let alert = UIAlertController(title: nil, message: Strings.warningPostWillBePublishedAlertMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("postSettings.ok", value: "OK", comment: "Button OK"), style: .default))
present(alert, animated: true)
}
}

// MARK: - PostSettingsViewController (Page Attributes)

extension PostSettingsViewController {
Expand Down Expand Up @@ -239,4 +281,6 @@ extension PostSettingsViewController {

private enum Strings {
static let errorMessage = NSLocalizedString("postSettings.updateFailedMessage", value: "Failed to update the post settings", comment: "Error message on post/page settings screen")

static let warningPostWillBePublishedAlertMessage = NSLocalizedString("postSettings.warningPostWillBePublishedAlertMessage", value: "By changing the visibility to 'Private', the post will be published immediately", comment: "An alert message explaning that by changing the visibility to private, the post will be published immediately to your site")
}
27 changes: 13 additions & 14 deletions WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ @interface PostSettingsViewController () <UITextFieldDelegate,
@property (nonatomic, strong) UITextField *passwordTextField;
@property (nonatomic, strong) UIButton *passwordVisibilityButton;
@property (nonatomic, strong) NSArray *postMetaSectionRows;
@property (nonatomic, strong) NSArray *visibilityList;
@property (nonatomic, strong) NSArray *formatsList;
@property (nonatomic, strong) UIImage *featuredImage;
@property (nonatomic, strong) NSData *animatedFeaturedImageData;
Expand Down Expand Up @@ -131,10 +130,6 @@ - (void)viewDidLoad
[WPStyleGuide configureColorsForView:self.view andTableView:self.tableView];
[WPStyleGuide configureAutomaticHeightRowsFor:self.tableView];

self.visibilityList = @[NSLocalizedString(@"Public", @"Privacy setting for posts set to 'Public' (default). Should be the same as in core WP."),
NSLocalizedString(@"Password protected", @"Privacy setting for posts set to 'Password protected'. Should be the same as in core WP."),
NSLocalizedString(@"Private", @"Privacy setting for posts set to 'Private'. Should be the same as in core WP.")];

[self setupFormatsList];
[self setupPublicizeConnections];

Expand Down Expand Up @@ -689,9 +684,6 @@ - (void)configureMetaSectionRows
@(PostSettingsRowPublishDate),
@(PostSettingsRowVisibility)
]];
if (self.apost.password) {
[metaRows addObject:@(PostSettingsRowPassword)];
}
}
} else {
[metaRows addObject:@(PostSettingsRowPublishDate)];
Expand Down Expand Up @@ -719,7 +711,7 @@ - (UITableViewCell *)configureMetaPostMetaCellForIndexPath:(NSIndexPath *)indexP
cell.tag = PostSettingsRowAuthor;
} else if (row == PostSettingsRowPublishDate) {
// Publish date
cell = [self getWPTableViewDisclosureCell];
cell = [self getWPTableViewDisclosureCellWithIdentifier:@"PostSettingsRowPublishDate"];
cell.textLabel.text = NSLocalizedString(@"Publish Date", @"Label for the publish date button.");
// Note: it's safe to remove `shouldPublishImmediately` when
// `RemoteFeatureFlagSyncPublishing` is enabled because this cell is not displayed.
Expand Down Expand Up @@ -754,7 +746,7 @@ - (UITableViewCell *)configureMetaPostMetaCellForIndexPath:(NSIndexPath *)indexP

} else if (row == PostSettingsRowVisibility) {
// Visibility
cell = [self getWPTableViewDisclosureCell];
cell = [self getWPTableViewDisclosureCellWithIdentifier:@"PostSettingsRowVisibility"];
cell.textLabel.text = NSLocalizedString(@"Visibility", @"The visibility settings of the post. Should be the same as in core WP.");
cell.detailTextLabel.text = [self.apost titleForVisibility];
cell.tag = PostSettingsRowVisibility;
Expand Down Expand Up @@ -1024,12 +1016,15 @@ - (UITableViewCell *)configureExcerptCellForIndexPath:(NSIndexPath *)indexPath
return cell;
}

- (WPTableViewCell *)getWPTableViewDisclosureCell
- (WPTableViewCell *)getWPTableViewDisclosureCell {
return [self getWPTableViewDisclosureCellWithIdentifier:@"WPTableViewDisclosureCellIdentifier"];
}

- (WPTableViewCell *)getWPTableViewDisclosureCellWithIdentifier:(NSString *)identifier
{
static NSString *WPTableViewDisclosureCellIdentifier = @"WPTableViewDisclosureCellIdentifier";
WPTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:WPTableViewDisclosureCellIdentifier];
WPTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[WPTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:WPTableViewDisclosureCellIdentifier];
cell = [[WPTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[WPStyleGuide configureTableViewCell:cell];
}
Expand Down Expand Up @@ -1119,6 +1114,10 @@ - (void)showPostStatusSelector

- (void)showPostVisibilitySelector
{
if ([Feature enabled:FeatureFlagSyncPublishing]) {
[self showUpdatedPostVisibilityPicker];
return;
}
PostVisibilitySelectorViewController *vc = [[PostVisibilitySelectorViewController alloc] init:self.apost];
__weak PostVisibilitySelectorViewController *weakVc = vc;
vc.completion = ^(NSString *__unused visibility) {
Expand Down
87 changes: 49 additions & 38 deletions WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import SwiftUI

struct PostVisibilityPicker: View {
@State private var selection: PostVisibility = .public
@State private var password = ""
@State private var isEnteringPassword = false
@State private var selection: Selection
@State private var isDismissing = false
@FocusState private var isPasswordFieldFocused: Bool

struct Selection {
var visibility: PostVisibility
var password: String?
var type: PostVisibility
var password = ""

init(post: AbstractPost) {
self.type = PostVisibility(post: post)
self.password = post.password ?? ""
}
}

private let onSubmit: (Selection) -> Void

static var title: String { Strings.title }

init(visibility: PostVisibility, onSubmit: @escaping (Selection) -> Void) {
self._selection = State(initialValue: visibility)
init(selection: Selection, onSubmit: @escaping (Selection) -> Void) {
self._selection = State(initialValue: selection)
self.onSubmit = onSubmit
}

Expand All @@ -33,11 +37,13 @@ struct PostVisibilityPicker: View {
private func makeRow(for visibility: PostVisibility) -> some View {
Button(action: {
withAnimation {
selection.type = visibility
selection.password = ""

if visibility == .protected {
isEnteringPassword = true
isPasswordFieldFocused = true
} else {
selection = visibility
onSubmit(Selection(visibility: visibility, password: nil))
onSubmit(selection)
}
}
}, label: {
Expand All @@ -47,52 +53,55 @@ struct PostVisibilityPicker: View {
Text(visibility.localizedDetails)
.font(.footnote)
.foregroundStyle(.secondary)
.opacity(isEnteringPassword ? 0.5 : 1)
.opacity(visibility != .protected && isPasswordFieldFocused ? 0.4 : 1)
}
Spacer()
Image(systemName: "checkmark")
.tint(Color(uiColor: .primary))
.opacity((selection == visibility && !isEnteringPassword) ? 1 : 0)
.opacity((selection.type == visibility && !isPasswordFieldFocused) ? 1 : 0)
}
})
.tint(.primary)
.disabled(isEnteringPassword && visibility != .protected)
.disabled(isPasswordFieldFocused && visibility != .protected)

if visibility == .protected, isEnteringPassword {
if visibility == .protected, selection.type == .protected {
enterPasswordRows
}
}

@ViewBuilder
private var enterPasswordRows: some View {
PasswordField(password: $password)
.onSubmit(savePassword)
PasswordField(password: $selection.password, isFocused: isPasswordFieldFocused)
.focused($isPasswordFieldFocused)
.onSubmit(buttonSavePasswordTapped)

HStack {
Button(Strings.cancel) {
withAnimation {
password = ""
isEnteringPassword = false
if isPasswordFieldFocused {
HStack {
Button(Strings.cancel) {
withAnimation {
selection.type = .public
selection.password = ""
}
}
.keyboardShortcut(.cancelAction)
Spacer()
Button(Strings.save, action: buttonSavePasswordTapped)
.font(.body.weight(.medium))
.disabled(selection.password.trimmingCharacters(in: .whitespaces).isEmpty)
}
.keyboardShortcut(.cancelAction)
Spacer()
Button(Strings.save, action: savePassword)
.font(.body.weight(.medium))
.disabled(password.isEmpty)
.buttonStyle(.plain)
.foregroundStyle(Color(uiColor: .brand))
}
.buttonStyle(.plain)
.foregroundStyle(Color(uiColor: .brand))
}

private func savePassword() {
private func buttonSavePasswordTapped() {
withAnimation {
selection = .protected
isEnteringPassword = false
isPasswordFieldFocused = false
selection.password = selection.password.trimmingCharacters(in: .whitespaces)
isDismissing = true
// Let the keyboard dismiss first to avoid janky animation
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(550)) {
onSubmit(Selection(visibility: .protected, password: password))
onSubmit(selection)
}
}
}
Expand All @@ -101,13 +110,12 @@ struct PostVisibilityPicker: View {
private struct PasswordField: View {
@Binding var password: String
@State var isSecure = true
@FocusState private var isFocused: Bool
let isFocused: Bool

var body: some View {
HStack {
textField
.focused($isFocused)
if !password.isEmpty {
if isFocused && !password.isEmpty {
Button(action: { password = "" }) {
Image(systemName: "xmark.circle")
.foregroundStyle(.secondary)
Expand All @@ -119,8 +127,8 @@ private struct PasswordField: View {
}
}
.buttonStyle(.plain)
.onAppear { isFocused = true }
}

@ViewBuilder
private var textField: some View {
if isSecure {
Expand All @@ -136,8 +144,12 @@ enum PostVisibility: Identifiable, CaseIterable {
case `private`
case protected

init(post: AbstractPost) {
self.init(status: post.status ?? .draft, password: post.password)
}

init(status: AbstractPost.Status, password: String?) {
if password != nil {
if let password, !password.isEmpty {
self = .protected
} else if status == .publishPrivate {
self = .private
Expand All @@ -163,7 +175,6 @@ enum PostVisibility: Identifiable, CaseIterable {
case .private: NSLocalizedString("postVisibility.private.details", value: "Only visible to site admins and editors", comment: "Details for a 'Private' privacy setting")
}
}

}

private enum Strings {
Expand Down
Loading