From 00f41224375cb9a2772b0e26cdd94578fdf05487 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Fri, 17 Oct 2025 10:14:01 -0400 Subject: [PATCH 1/8] Add initial Status & Visibility integration --- .../Post/PostSettings/PostSettingsView.swift | 10 ++++-- .../PostSettings/PostSettingsViewModel.swift | 33 ++++++++++++++++++- .../Views/PostSettingsPublishDatePicker.swift | 2 +- .../Post/Views/AbstractPostMenuHelper.swift | 2 +- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift index bf1d0a6d0536..a6954a222e93 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift @@ -154,8 +154,8 @@ struct PostSettingsFormContentView: View { private var publishingOptionsSection: some View { Section { BlogListSiteView(site: .init(blog: viewModel.post.blog)) + statusRow publishDateRow - visibilityRow } header: { SectionHeader(Strings.readyToPublish) } @@ -269,7 +269,7 @@ struct PostSettingsFormContentView: View { NavigationLink { PostStatusView(settings: $viewModel.settings, timeZone: viewModel.timeZone) } label: { - SettingsRow(Strings.status) { + SettingsRow(viewModel.context == .publishing ? Strings.statusAndVisibility : Strings.status) { HStack(alignment: .center, spacing: 2) { ScaledImage(viewModel.settings.status.image, height: 23) VStack(alignment: .leading, spacing: 2) { @@ -696,4 +696,10 @@ private enum Strings { value: "Status", comment: "Label for the status field in Post Settings" ) + + static let statusAndVisibility = NSLocalizedString( + "postSettings.statusAndVisibility.label", + value: "Status & Visibility", + comment: "Label for the Status & Visibility field in Post Settings (should be short)" + ) } diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsViewModel.swift index d6bdd270be90..ae584e795448 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsViewModel.swift @@ -157,7 +157,11 @@ final class PostSettingsViewModel: NSObject, ObservableObject { self.preferences = preferences // Initialize settings from the post - let initialSettings = PostSettings(from: post) + var initialSettings = PostSettings(from: post) + if context == .publishing { + initialSettings.status = .publish + initialSettings.updateStatusForPublishDate() + } self.settings = initialSettings self.originalSettings = initialSettings @@ -359,6 +363,13 @@ final class PostSettingsViewModel: NSObject, ObservableObject { settings.password = selection.password.isEmpty ? nil : selection.password } + func didSelectPublshDate(_ date: Date?) { + settings.publishDate = date + if context == .publishing { + settings.updateStatusForPublishDate() + } + } + func didSelectSuggestedTag(_ tag: String) { suggestedTags.removeAll(where: { $0 == tag }) settings.tags.append(",\(tag)") @@ -558,6 +569,26 @@ extension PostSettingsViewModel: @MainActor PrepublishingSocialAccountsDelegate } } +private extension PostSettings { + mutating func updateStatusForPublishDate() { + switch status { + case .publish: + if let date = publishDate, date > .now, status == .publish { + status = .scheduled + } + case .scheduled: + if let date = publishDate, date <= .now { + status = .publish + } else if publishDate == nil { + status = .publish + } + default: + break // Do nothing + + } + } +} + // MARK: - Localized Strings private enum Strings { diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostSettingsPublishDatePicker.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostSettingsPublishDatePicker.swift index bc0974a33e4b..50cbedbac1a9 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostSettingsPublishDatePicker.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostSettingsPublishDatePicker.swift @@ -10,7 +10,7 @@ struct PostSettingsPublishDatePicker: View { isRequired: !viewModel.isDraftOrPending, timeZone: viewModel.timeZone, updated: { date in - viewModel.settings.publishDate = date + viewModel.didSelectPublshDate(date) } )) } diff --git a/WordPress/Classes/ViewRelated/Post/Views/AbstractPostMenuHelper.swift b/WordPress/Classes/ViewRelated/Post/Views/AbstractPostMenuHelper.swift index 4071a4a99dc8..3c4f07ff4471 100644 --- a/WordPress/Classes/ViewRelated/Post/Views/AbstractPostMenuHelper.swift +++ b/WordPress/Classes/ViewRelated/Post/Views/AbstractPostMenuHelper.swift @@ -94,7 +94,7 @@ extension AbstractPostButton: AbstractPostMenuAction { var icon: UIImage? { switch self { case .view: return UIImage(systemName: "safari") - case .publish: return UIImage(systemName: "tray.and.arrow.up") + case .publish: return UIImage(systemName: "paperplane") case .stats: return UIImage(systemName: "chart.line.uptrend.xyaxis") case .duplicate: return UIImage(systemName: "doc.on.doc") case .moveToDraft: return UIImage(systemName: "pencil.line") From 3d7ed8afcedabecceb571b17e9fe6255d8b71ccc Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Fri, 17 Oct 2025 10:36:15 -0400 Subject: [PATCH 2/8] Remove draft status from publishing sheet --- .../ViewRelated/Post/PostSettings/PostSettingsView.swift | 6 +++++- .../Post/PostSettings/Views/PostStatusView.swift | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift index a6954a222e93..3da3e9b6afa2 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift @@ -267,7 +267,11 @@ struct PostSettingsFormContentView: View { private var statusRow: some View { NavigationLink { - PostStatusView(settings: $viewModel.settings, timeZone: viewModel.timeZone) + PostStatusView( + settings: $viewModel.settings, + timeZone: viewModel.timeZone, + isPublishing: viewModel.context == .publishing + ) } label: { SettingsRow(viewModel.context == .publishing ? Strings.statusAndVisibility : Strings.status) { HStack(alignment: .center, spacing: 2) { diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostStatusView.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostStatusView.swift index f3cbc7fa974c..0064dae0cf34 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostStatusView.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostStatusView.swift @@ -5,6 +5,7 @@ import WordPressData struct PostStatusView: View { @Binding var settings: PostSettings let timeZone: TimeZone + var isPublishing = false @State private var isShowingPublishDatePicker = false @State private var isShowingPasswordEntry = false @@ -12,7 +13,13 @@ struct PostStatusView: View { @ScaledMetric private var statusRowLeadingInset: CGFloat = PostStatusRow.leadingInset - private let statuses = [BasePost.Status.draft, .pending, .publishPrivate, .scheduled, .publish] + private var statuses: [BasePost.Status] { + var all = [.draft, .pending, .publishPrivate, .scheduled, .publish] + if isPublishing, let index = all.firstIndex(of: .draft) { + all.remove(at: index) + } + return all + } var body: some View { Form { From 3943a54a43667683038049ea1a361f962cdb88ef Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Fri, 17 Oct 2025 10:39:44 -0400 Subject: [PATCH 3/8] Prevent from picking draft --- .../Post/PostSettings/Views/PostStatusView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostStatusView.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostStatusView.swift index 0064dae0cf34..d01025aa4035 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostStatusView.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/Views/PostStatusView.swift @@ -14,11 +14,11 @@ struct PostStatusView: View { private var statusRowLeadingInset: CGFloat = PostStatusRow.leadingInset private var statuses: [BasePost.Status] { - var all = [.draft, .pending, .publishPrivate, .scheduled, .publish] - if isPublishing, let index = all.firstIndex(of: .draft) { - all.remove(at: index) + var statuses: [BasePost.Status] = [.draft, .pending, .publishPrivate, .scheduled, .publish] + if isPublishing { + statuses.removeAll { $0 == .draft } } - return all + return statuses } var body: some View { From 8df89ab90e4a0738d6ba46f3ba8e241af7f5d2b8 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Fri, 17 Oct 2025 10:42:59 -0400 Subject: [PATCH 4/8] Cahnge button to Submit --- .../Post/Publishing/PublishPostViewController.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift b/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift index a242bfd21040..48a74eb27b72 100644 --- a/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Publishing/PublishPostViewController.swift @@ -174,8 +174,12 @@ struct PublishPostView: View { private extension PostSettingsViewModel { var publishButtonTitle: String { - let isScheduled = settings.publishDate.map { $0 > .now } ?? false - return isScheduled ? Strings.schedule : Strings.publish + switch settings.status { + case .publish, .publishPrivate: Strings.publish + case .pending: Strings.submit + case .scheduled: Strings.schedule + default: SharedStrings.Button.save + } } } @@ -185,6 +189,7 @@ enum PrepublishingSheetStrings { static let title = NSLocalizedString("prepublishing.title", value: "Publishing", comment: "Navigation title") static let publishingTo = NSLocalizedString("prepublishing.publishingTo", value: "Publishing to", comment: "Label in the header in the pre-publishing sheet") static let publish = NSLocalizedString("prepublishing.publish", value: "Publish", comment: "Primary button label in the pre-publishing sheet") + static let submit = NSLocalizedString("prepublishing.submitForReview", value: "Submit", comment: "Primary button label in the pre-publishing sheet (must be short)") static let schedule = NSLocalizedString("prepublishing.schedule", value: "Schedule", comment: "Primary button label in the pre-publishing shee") static let publishDate = NSLocalizedString("prepublishing.publishDate", value: "Publish Date", comment: "Label for a cell in the pre-publishing sheet") static let visibility = NSLocalizedString("prepublishing.visibility", value: "Visibility", comment: "Label for a cell in the pre-publishing sheet") From 75fecf3f4a46ac0904af94feb4605b0eebc08a36 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Fri, 17 Oct 2025 10:59:28 -0400 Subject: [PATCH 5/8] Remove visibilityRow --- .../Post/PostSettings/PostSettingsView.swift | 20 ------------------ .../PostSettings/PostSettingsViewModel.swift | 21 ------------------- 2 files changed, 41 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift index 3da3e9b6afa2..0cc79d6e7018 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift @@ -321,20 +321,6 @@ struct PostSettingsFormContentView: View { } } - private var visibilityRow: some View { - NavigationLink { - PostVisibilityPicker( - selection: PostVisibilityPicker.Selection(post: viewModel.post), - dismissOnSelection: true, - onSubmit: { selection in - viewModel.updateVisibility(selection) - } - ) - } label: { - SettingsRow(Strings.visibilityLabel, value: viewModel.visibilityText) - } - } - // MARK: - "Social Sharing" Section @ViewBuilder @@ -522,12 +508,6 @@ private enum Strings { comment: "Label for the publish date field in Post Settings" ) - static let visibilityLabel = NSLocalizedString( - "postSettings.visibility.label", - value: "Visibility", - comment: "Label for the visibility field in Post Settings" - ) - static let pendingReviewLabel = NSLocalizedString( "postSettings.pendingReview.label", value: "Pending Review", diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsViewModel.swift index ae584e795448..580c22f895a8 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsViewModel.swift @@ -69,11 +69,6 @@ final class PostSettingsViewModel: NSObject, ObservableObject { return formatter.string(from: date) } - var visibilityText: String { - PostVisibility(status: settings.status, password: settings.password) - .localizedTitle - } - var slugText: String { settings.slug.isEmpty ? (post.suggested_slug ?? "") : settings.slug } @@ -347,22 +342,6 @@ final class PostSettingsViewModel: NSObject, ObservableObject { trackChanges(from: originalSettings, to: settings) } - func updateVisibility(_ selection: PostVisibilityPicker.Selection) { - track(.editorPostVisibilityChanged) - - switch selection.type { - case .public, .protected: - if post.original().status == .scheduled { - // Keep it scheduled - } else { - settings.status = .publish - } - case .private: - settings.status = .publishPrivate - } - settings.password = selection.password.isEmpty ? nil : selection.password - } - func didSelectPublshDate(_ date: Date?) { settings.publishDate = date if context == .publishing { From 4feea90040384d9afc56d2441c35cceb0330a567 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Fri, 17 Oct 2025 10:59:51 -0400 Subject: [PATCH 6/8] Remove PostVisibilityPicker --- .../Post/PostVisibilityPicker.swift | 167 ------------------ 1 file changed, 167 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift diff --git a/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift b/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift deleted file mode 100644 index d982356afb8c..000000000000 --- a/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift +++ /dev/null @@ -1,167 +0,0 @@ -import SwiftUI -import WordPressData -import WordPressUI - -struct PostVisibilityPicker: View { - @State private var selection: Selection - @State private var previousSelection: Selection - @State private var isDismissing = false - @FocusState private var isPasswordFieldFocused: Bool - @Environment(\.dismiss) private var dismiss - - struct Selection { - var type: PostVisibility - var password = "" - - init(post: AbstractPost) { - self.type = PostVisibility(post: post) - self.password = post.password ?? "" - } - } - - private let onSubmit: (Selection) -> Void - private let dismissOnSelection: Bool - - static var title: String { Strings.title } - - init(selection: Selection, dismissOnSelection: Bool = false, onSubmit: @escaping (Selection) -> Void) { - self._selection = State(initialValue: selection) - self._previousSelection = State(initialValue: selection) - self.dismissOnSelection = dismissOnSelection - self.onSubmit = onSubmit - } - - var body: some View { - Form { - ForEach(PostVisibility.allCases, content: makeRow) - } - .disabled(isDismissing) - .navigationTitle(Strings.title) - .navigationBarTitleDisplayMode(.inline) - .navigationBarBackButtonHidden(isPasswordFieldFocused) - .toolbar { - if isPasswordFieldFocused { - ToolbarItem(placement: .cancellationAction) { - Button(SharedStrings.Button.cancel) { - withAnimation { - selection = previousSelection - } - } - } - ToolbarItem(placement: .confirmationAction) { - Button(SharedStrings.Button.done) { - buttonSavePasswordTapped() - } - .disabled(selection.password.trimmingCharacters(in: .whitespaces).isEmpty) - } - } - } - } - - @ViewBuilder - private func makeRow(for visibility: PostVisibility) -> some View { - Button(action: { didSelectVisibility(visibility) }) { - HStack { - VStack(alignment: .leading) { - Text(visibility.localizedTitle) - Text(visibility.localizedDetails) - .font(.footnote) - .foregroundStyle(.secondary) - .opacity(visibility != .protected && isPasswordFieldFocused ? 0.4 : 1) - } - Spacer() - Image(systemName: "checkmark") - .tint(Color(uiColor: UIAppColor.primary)) - .opacity((selection.type == visibility && !isPasswordFieldFocused) ? 1 : 0) - } - } - .tint(.primary) - .disabled(isPasswordFieldFocused && visibility != .protected) - - if visibility == .protected, selection.type == .protected { - enterPasswordRows - } - } - - private func didSelectVisibility(_ visibility: PostVisibility) { - withAnimation { - previousSelection = selection - selection.type = visibility - selection.password = "" - if visibility == .protected { - isPasswordFieldFocused = true - } else { - onSubmit(selection) - if dismissOnSelection { - dismiss() - } - } - } - } - - @ViewBuilder - private var enterPasswordRows: some View { - PasswordField(password: $selection.password, isFocused: isPasswordFieldFocused) - .focused($isPasswordFieldFocused) - .onSubmit(buttonSavePasswordTapped) - } - - private func buttonSavePasswordTapped() { - withAnimation { - isPasswordFieldFocused = false - selection.password = selection.password.trimmingCharacters(in: .whitespaces) - - if !selection.password.isEmpty { - isDismissing = true - // Let the keyboard dismiss first to avoid janky animation - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(550)) { - onSubmit(selection) - if dismissOnSelection { - dismiss() - } - } - } else { - selection = previousSelection - } - } - } -} - -private struct PasswordField: View { - @Binding var password: String - @State var isSecure = true - let isFocused: Bool - - var body: some View { - HStack { - textField - if isFocused && !password.isEmpty { - Button(action: { password = "" }) { - Image(systemName: "xmark.circle") - .foregroundStyle(.secondary) - }.padding(.trailing, 4) - } - Button(action: { isSecure.toggle() }) { - Image(systemName: isSecure ? "eye" : "eye.slash") - .foregroundStyle(.secondary) - } - } - .buttonStyle(.plain) - } - - @ViewBuilder - private var textField: some View { - if isSecure { - SecureField(Strings.password, text: $password) - } else { - TextField(Strings.password, text: $password) - } - } -} - -private enum Strings { - static let title = NSLocalizedString("postVisibilityPicker.navigationTitle", value: "Visibility", comment: "Navigation bar title for the Post Visibility picker") - static let cancel = NSLocalizedString("postVisibilityPicker.cancel", value: "Cancel", comment: "Button cancel") - static let save = NSLocalizedString("postVisibilityPicker.save", value: "Save", comment: "Button save") - static let password = NSLocalizedString("postVisibilityPicker.password", value: "Password", comment: "Password placeholder text") -} From ec159572897792f7deb6aa45ec92f4eaa10117da Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Fri, 17 Oct 2025 11:01:40 -0400 Subject: [PATCH 7/8] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 38e799701166..6907e82ca8b9 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -9,6 +9,7 @@ * [*] Add "Email to Subscribers" row to "Publishing" sheet [#24946] * [*] Add permalink preview in the slug editor and make other improvements [#24949] * [*] Update "Categories" picker to indicate multiple selection [#24952] +* [*] Replace "Visibility" with "Status & Visibility" in the "Publishing" Sheet [#24950] 26.4 ----- From e77efde2816ac6c99b1d74f96f972af070945bcc Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Mon, 20 Oct 2025 10:35:56 -0400 Subject: [PATCH 8/8] Rename to just Status --- RELEASE-NOTES.txt | 2 +- .../ViewRelated/Post/PostSettings/PostSettingsView.swift | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 6907e82ca8b9..70f48cdd143b 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -9,7 +9,7 @@ * [*] Add "Email to Subscribers" row to "Publishing" sheet [#24946] * [*] Add permalink preview in the slug editor and make other improvements [#24949] * [*] Update "Categories" picker to indicate multiple selection [#24952] -* [*] Replace "Visibility" with "Status & Visibility" in the "Publishing" Sheet [#24950] +* [*] Replace "Visibility" with "Status" in the "Publishing" Sheet [#24950] 26.4 ----- diff --git a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift index 0cc79d6e7018..2b78057e4eb6 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettings/PostSettingsView.swift @@ -273,7 +273,7 @@ struct PostSettingsFormContentView: View { isPublishing: viewModel.context == .publishing ) } label: { - SettingsRow(viewModel.context == .publishing ? Strings.statusAndVisibility : Strings.status) { + SettingsRow(Strings.status) { HStack(alignment: .center, spacing: 2) { ScaledImage(viewModel.settings.status.image, height: 23) VStack(alignment: .leading, spacing: 2) { @@ -680,10 +680,4 @@ private enum Strings { value: "Status", comment: "Label for the status field in Post Settings" ) - - static let statusAndVisibility = NSLocalizedString( - "postSettings.statusAndVisibility.label", - value: "Status & Visibility", - comment: "Label for the Status & Visibility field in Post Settings (should be short)" - ) }