diff --git a/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift b/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift index d2308bef2a56..e90449a4563a 100644 --- a/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift +++ b/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift @@ -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 { diff --git a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift index ab5efecccb9a..08c88342ff85 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift @@ -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 } } @@ -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. diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift index 93af5cf6eb91..336f8481866b 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift @@ -2,6 +2,7 @@ import UIKit import CoreData import Combine import WordPressKit +import SwiftUI extension PostSettingsViewController { static func make(for post: AbstractPost) -> PostSettingsViewController { @@ -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 { @@ -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") } diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m index 3b25081a3f3f..efcf319e846f 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m @@ -67,7 +67,6 @@ @interface PostSettingsViewController () 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 } @@ -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: { @@ -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) } } } @@ -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) @@ -119,8 +127,8 @@ private struct PasswordField: View { } } .buttonStyle(.plain) - .onAppear { isFocused = true } } + @ViewBuilder private var textField: some View { if isSecure { @@ -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 @@ -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 { diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift index 4f4a1a37a0f9..93269aaebe3f 100644 --- a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift @@ -370,18 +370,17 @@ final class PrepublishingViewController: UIViewController, UITableViewDataSource // MARK: - Visibility private func configureVisibilityCell(_ cell: WPTableViewCell) { - cell.detailTextLabel?.text = viewModel.visibility.localizedTitle + cell.detailTextLabel?.text = viewModel.visibility.type.localizedTitle } private func didTapVisibilityCell() { - let view = PostVisibilityPicker(visibility: viewModel.visibility) { [weak self] selection in + let view = PostVisibilityPicker(selection: viewModel.visibility) { [weak self] selection in guard let self else { return } - self.viewModel.visibility = selection.visibility - if selection.visibility == .private { + self.viewModel.visibility = selection + if selection.type == .private { self.viewModel.publishDate = nil self.updatePublishButtonLabel() } - self.viewModel.password = selection.password self.reloadData() self.navigationController?.popViewController(animated: true) } @@ -401,7 +400,7 @@ final class PrepublishingViewController: UIViewController, UITableViewDataSource } else { cell.detailTextLabel?.text = Strings.immediately } - viewModel.visibility == .private ? cell.disable() : cell.enable() + viewModel.visibility.type == .private ? cell.disable() : cell.enable() } func didTapSchedule(_ indexPath: IndexPath) { @@ -543,8 +542,7 @@ extension PrepublishingOption { private final class PrepublishingViewModel { private let post: AbstractPost - var visibility: PostVisibility - var password: String? + var visibility: PostVisibilityPicker.Selection var publishDate: Date? var publishButtonTitle: String { @@ -557,8 +555,7 @@ private final class PrepublishingViewModel { init(post: AbstractPost) { self.post = post - self.visibility = PostVisibility(status: post.status ?? .draft, password: post.password) - self.password = post.password + self.visibility = .init(post: post) // Ask the user to provide the date every time (ignore the obscure WP dateCreated/dateModified logic) self.publishDate = nil } @@ -568,8 +565,8 @@ private final class PrepublishingViewModel { wpAssert(post.isRevision()) try await coordinator._publish(post.original(), options: .init( - visibility: visibility, - password: password, + visibility: visibility.type, + password: visibility.password, publishDate: publishDate )) } diff --git a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift index 677e7ebf0e9c..430916f4ae63 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift @@ -71,7 +71,7 @@ final class SiteCreator { // MARK: - Helper Extensions -extension String { +private extension String { var subdomain: String { return components(separatedBy: ".").first ?? "" }