Skip to content

Commit 5fc306f

Browse files
authored
Add new "Publishing" sheet (#24855)
* Add empty PublishPostViewController * Extract PostSettingsFormContentView * Show PostSettingsFormContentView * Add initial header view * Add temporary cancel and publish options * Move general section below * Update PostSettigns to support .publishing context * Use primary style * Add publishing section directly to PostSettingsView * Improve the publishing sheet design * Increase SiteIcon corner radius * Add preview button (empty) * Add top icons * Implement publishing * Add PrepublishingSheetResult support * Make it possible to keep changes to the post * Fix confirmation dialog savings * Fix save not showing * Show spinner loading separately * Fix navigation for tags and categories * Update publish button title dynamically * Remove extra navigation view * Add initial social sharing * Show social section separately * Implement setup view * Add initil support for auto sharing view * Rename to PostSocialSharingSettings * Move social sharing settings to PostSettings (struct * Initial integration with sharing settings * Implement apply of sharing settings * Remove preview post * Update based on comments * Extract isPostEligibleForSocialSharing * Fix publish date being non-optional * Further simplify publishing * Add PostSettingsPublishDatePicker * Improve header design * Add shouldPublishImmediatelly support * Move info section * Integrate upload view snackbar * Revert unwanted changes
1 parent 72e304a commit 5fc306f

18 files changed

+792
-126
lines changed

Modules/Sources/WordPressUI/Views/SiteIconView.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ public struct SiteIconView: View {
1313

1414
public var body: some View {
1515
contents
16-
.clipShape(RoundedRectangle(cornerRadius: 6))
16+
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
17+
}
18+
19+
private var cornerRadius: CGFloat {
20+
if #available(iOS 26, *) { 10 } else { 6 }
1721
}
1822

1923
@ViewBuilder

Sources/WordPressData/Swift/PublicizeInfo+CoreDataClass.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class PublicizeInfo: NSManagedObject {
3131
}
3232

3333
/// A value-type representation for Publicize auto-sharing usage.
34-
public struct SharingLimit {
34+
public struct SharingLimit: Hashable {
3535
/// The remaining shares available for use.
3636
public let remaining: Int
3737

WordPress/Classes/Services/PostCoordinator.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,43 @@ class PostCoordinator: NSObject {
128128
}
129129
}
130130

131+
/// Publishes the post according to the current settings and user capabilities.
132+
///
133+
/// - warning: Before publishing, ensure that the media for the post got
134+
/// uploaded. Managing media is not the responsibility of `PostRepository.`
135+
///
136+
/// - parameter changes: The set of changes apply to the post together
137+
/// with the publishing options.
138+
@MainActor
139+
func publish_v2(_ post: AbstractPost, parameters: RemotePostUpdateParameters) async throws {
140+
wpAssert(post.isOriginal())
141+
wpAssert(post.isStatus(in: [.draft, .pending]))
142+
143+
await pauseSyncing(for: post)
144+
defer { resumeSyncing(for: post) }
145+
146+
var parameters = parameters
147+
if parameters.status == nil {
148+
parameters.status = Post.Status.publish.rawValue
149+
}
150+
if parameters.date == nil {
151+
// If the post was previously scheduled for a different date,
152+
// the app has to send a new value to override it.
153+
parameters.date = post.shouldPublishImmediately() ? nil : Date()
154+
}
155+
156+
do {
157+
let repository = PostRepository(coreDataStack: coreDataStack)
158+
try await repository.save(post, changes: parameters)
159+
didPublish(post)
160+
show(PostCoordinator.makeUploadSuccessNotice(for: post))
161+
} catch {
162+
trackError(error, operation: "post-publish", post: post)
163+
handleError(error, for: post)
164+
throw error
165+
}
166+
}
167+
131168
@MainActor
132169
private func didPublish(_ post: AbstractPost) {
133170
if post.status == .scheduled {

WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public enum FeatureFlag: Int, CaseIterable {
2626
case pluginManagementOverhaul
2727
case newsletterSubscribers
2828
case newStats
29+
case newPublishingSheet
2930

3031
/// Returns a boolean indicating if the feature is enabled.
3132
///
@@ -82,6 +83,8 @@ public enum FeatureFlag: Int, CaseIterable {
8283
return true
8384
case .newStats:
8485
return false
86+
case .newPublishingSheet:
87+
return false
8588
}
8689
}
8790

@@ -125,6 +128,7 @@ extension FeatureFlag {
125128
case .readerGutenbergCommentComposer: "Gutenberg Comment Composer"
126129
case .newsletterSubscribers: "Newsletter Subscribers"
127130
case .newStats: "New Stats"
131+
case .newPublishingSheet: "New Publishing Sheet"
128132
}
129133
}
130134
}

WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import WordPressUI
55

66
struct JetpackSocialNoConnectionView: View {
77

8-
private let viewModel: JetpackSocialNoConnectionViewModel
8+
let viewModel: JetpackSocialNoConnectionViewModel
99

1010
var body: some View {
1111
VStack(alignment: .leading, spacing: 12.0) {

WordPress/Classes/ViewRelated/Post/PostSettings/PostSettings.swift

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct PostSettings: Hashable {
2525
// MARK: - Post-specific
2626
var postFormat: String?
2727
var isStickyPost = false
28+
var sharing: PostSocialSharingSettings?
2829

2930
// MARK: - Page-specific
3031
var parentPageID: Int?
@@ -36,7 +37,7 @@ struct PostSettings: Hashable {
3637
excerpt = post.mt_excerpt ?? ""
3738
slug = post.wp_slug ?? ""
3839
status = post.status ?? .draft
39-
publishDate = post.dateCreated
40+
publishDate = post.shouldPublishImmediately() ? nil : post.dateCreated
4041
password = post.password
4142

4243
if let authorID = post.authorID?.intValue, authorID > 0 {
@@ -57,6 +58,7 @@ struct PostSettings: Hashable {
5758
categoryIDs = Set((post.categories ?? []).compactMap {
5859
$0.categoryID?.intValue
5960
})
61+
sharing = PostSocialSharingSettings.make(for: post)
6062
case let page as Page:
6163
parentPageID = page.parentID?.intValue
6264
default:
@@ -129,6 +131,22 @@ struct PostSettings: Hashable {
129131
if post.isStickyPost != isStickyPost {
130132
post.isStickyPost = isStickyPost
131133
}
134+
135+
if let sharing {
136+
for connection in sharing.services.flatMap(\.connections) {
137+
let keyringID = NSNumber(value: connection.keyringID)
138+
if !post.publicizeConnectionDisabledForKeyringID(keyringID) != connection.enabled {
139+
if connection.enabled {
140+
post.enablePublicizeConnectionWithKeyringID(keyringID)
141+
} else {
142+
post.disablePublicizeConnectionWithKeyringID(keyringID)
143+
}
144+
}
145+
}
146+
if post.publicizeMessage != sharing.message {
147+
post.publicizeMessage = sharing.message
148+
}
149+
}
132150
case let page as Page:
133151
if page.parentID?.intValue != parentPageID {
134152
page.parentID = parentPageID.map { NSNumber(value: $0) }
@@ -186,3 +204,70 @@ extension PostSettings {
186204
.map { $0.stringByDecodingXMLCharacters() }
187205
}
188206
}
207+
208+
/// A value-type representation of `PublicizeService` for the current blog that's simplified for the auto-sharing flow.
209+
struct PostSocialSharingSettings: Hashable {
210+
var services: [Service]
211+
var message: String
212+
var sharingLimit: PublicizeInfo.SharingLimit?
213+
214+
struct Service: Hashable {
215+
let name: PublicizeService.ServiceName
216+
var connections: [Connection]
217+
}
218+
219+
struct Connection: Hashable {
220+
let account: String
221+
let keyringID: Int
222+
var enabled: Bool
223+
}
224+
225+
static func make(for post: Post) -> PostSocialSharingSettings? {
226+
guard let context = post.managedObjectContext else {
227+
wpAssertionFailure("missing moc")
228+
return nil
229+
}
230+
231+
let connections = post.blog.sortedConnections
232+
233+
// first, build a dictionary to categorize the connections.
234+
var connectionsMap = [PublicizeService.ServiceName: [PublicizeConnection]]()
235+
connections.filter { !$0.requiresUserAction() }.forEach { connection in
236+
let name = PublicizeService.ServiceName(rawValue: connection.service) ?? .unknown
237+
var serviceConnections = connectionsMap[name] ?? []
238+
serviceConnections.append(connection)
239+
connectionsMap[name] = serviceConnections
240+
}
241+
242+
let publicizeServices: [PublicizeService]
243+
do {
244+
publicizeServices = try PublicizeService.allPublicizeServices(in: context)
245+
} catch {
246+
wpAssertionFailure("failed to fetch services", userInfo: ["error": error.localizedDescription])
247+
return nil
248+
}
249+
250+
let services = publicizeServices.compactMap { service -> PostSocialSharingSettings.Service? in
251+
// skip services without connections.
252+
guard let serviceConnections = connectionsMap[service.name],
253+
!serviceConnections.isEmpty else {
254+
return nil
255+
}
256+
257+
return PostSocialSharingSettings.Service(
258+
name: service.name,
259+
connections: serviceConnections.map {
260+
.init(account: $0.externalDisplay,
261+
keyringID: $0.keyringConnectionID.intValue,
262+
enabled: !post.publicizeConnectionDisabledForKeyringID($0.keyringConnectionID))
263+
}
264+
)
265+
}
266+
267+
return PostSocialSharingSettings(
268+
services: services,
269+
message: post.publicizeMessage ?? post.titleForDisplay(),
270+
sharingLimit: post.blog.sharingLimit
271+
)
272+
}
273+
}

0 commit comments

Comments
 (0)