Skip to content

Commit 4295235

Browse files
authored
Add PostFormatPicker (#24620)
2 parents 0e2ecc0 + 9bdf328 commit 4295235

File tree

9 files changed

+251
-156
lines changed

9 files changed

+251
-156
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import Foundation
2+
import CoreData
3+
import WordPressShared
4+
5+
extension Blog {
6+
7+
// MARK: - Post Formats
8+
9+
/// Returns an array of post format keys sorted with "standard" first, then alphabetically
10+
@objc public var sortedPostFormats: [String] {
11+
guard let postFormats = postFormats as? [String: String], !postFormats.isEmpty else {
12+
return []
13+
}
14+
15+
var sortedFormats: [String] = []
16+
17+
// Add standard format first if it exists
18+
if postFormats[PostFormatStandard] != nil {
19+
sortedFormats.append(PostFormatStandard)
20+
}
21+
22+
// Add remaining formats sorted by their display names
23+
let nonStandardFormats = postFormats
24+
.filter { $0.key != PostFormatStandard }
25+
.sorted { $0.value.localizedCaseInsensitiveCompare($1.value) == .orderedAscending }
26+
.map { $0.key }
27+
28+
sortedFormats.append(contentsOf: nonStandardFormats)
29+
30+
return sortedFormats
31+
}
32+
33+
/// Returns an array of post format display names sorted with "Standard" first, then alphabetically
34+
@objc public var sortedPostFormatNames: [String] {
35+
guard let postFormats = postFormats as? [String: String] else {
36+
return []
37+
}
38+
return sortedPostFormats.compactMap { postFormats[$0] }
39+
}
40+
41+
/// Returns the default post format display text
42+
@objc public var defaultPostFormatText: String? {
43+
postFormatText(fromSlug: settings?.defaultPostFormat)
44+
}
45+
46+
// MARK: - Connections
47+
48+
/// Returns an array of PublicizeConnection objects sorted by service name, then by external name
49+
@objc public var sortedConnections: [PublicizeConnection] {
50+
guard let connections = Array(connections ?? []) as? [PublicizeConnection] else {
51+
return []
52+
}
53+
return connections.sorted { lhs, rhs in
54+
// First sort by service name (case insensitive, localized)
55+
let serviceComparison = lhs.service.localizedCaseInsensitiveCompare(rhs.service)
56+
if serviceComparison != .orderedSame {
57+
return serviceComparison == .orderedAscending
58+
}
59+
// Then sort by external name (case insensitive)
60+
return lhs.externalName.caseInsensitiveCompare(rhs.externalName) == .orderedAscending
61+
}
62+
}
63+
64+
// MARK: - Roles
65+
66+
/// Returns an array of roles sorted by order.
67+
public var sortedRoles: [Role] {
68+
guard let roles = Array(roles ?? []) as? [Role] else {
69+
return []
70+
}
71+
return roles.sorted { lhs, rhs in
72+
(lhs.order?.intValue ?? 0) < (rhs.order?.intValue ?? 0)
73+
}
74+
}
75+
}

Sources/WordPressData/Objective-C/Blog.m

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -281,56 +281,6 @@ - (NSArray *)sortedCategories
281281
return [[self.categories allObjects] sortedArrayUsingDescriptors:sortDescriptors];
282282
}
283283

284-
- (NSArray *)sortedPostFormats
285-
{
286-
if ([self.postFormats count] == 0) {
287-
return @[];
288-
}
289-
290-
NSMutableArray *sortedFormats = [NSMutableArray arrayWithCapacity:[self.postFormats count]];
291-
292-
if (self.postFormats[PostFormatStandard]) {
293-
[sortedFormats addObject:PostFormatStandard];
294-
}
295-
296-
NSArray *sortedNonStandardFormats = [[self.postFormats keysSortedByValueUsingSelector:@selector(localizedCaseInsensitiveCompare:)] wp_filter:^BOOL(id obj) {
297-
return ![obj isEqual:PostFormatStandard];
298-
}];
299-
300-
[sortedFormats addObjectsFromArray:sortedNonStandardFormats];
301-
302-
return [NSArray arrayWithArray:sortedFormats];
303-
}
304-
305-
- (NSArray *)sortedPostFormatNames
306-
{
307-
return [[self sortedPostFormats] wp_map:^id(NSString *key) {
308-
return self.postFormats[key];
309-
}];
310-
}
311-
312-
- (NSArray *)sortedConnections
313-
{
314-
NSSortDescriptor *sortServiceDescriptor = [[NSSortDescriptor alloc] initWithKey:@"service"
315-
ascending:YES
316-
selector:@selector(localizedCaseInsensitiveCompare:)];
317-
NSSortDescriptor *sortExternalNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"externalName"
318-
ascending:YES
319-
selector:@selector(caseInsensitiveCompare:)];
320-
NSArray *sortDescriptors = @[sortServiceDescriptor, sortExternalNameDescriptor];
321-
return [[self.connections allObjects] sortedArrayUsingDescriptors:sortDescriptors];
322-
}
323-
324-
- (NSArray<Role *> *)sortedRoles
325-
{
326-
return [self.roles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"order" ascending:YES]]];
327-
}
328-
329-
- (NSString *)defaultPostFormatText
330-
{
331-
return [self postFormatTextFromSlug:self.settings.defaultPostFormat];
332-
}
333-
334284
- (BOOL)hasMappedDomain {
335285
if (![self isHostedAtWPcom]) {
336286
return NO;

Sources/WordPressData/Objective-C/include/Blog.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,6 @@ typedef NS_ENUM(NSInteger, SiteVisibility) {
202202

203203

204204
// Readonly Properties
205-
@property (nonatomic, weak, readonly, nullable) NSArray *sortedPostFormatNames;
206-
@property (nonatomic, weak, readonly, nullable) NSArray *sortedPostFormats;
207-
@property (nonatomic, weak, readonly, nullable) NSArray *sortedConnections;
208-
@property (nonatomic, readonly, nullable) NSArray<Role *> *sortedRoles;
209205
@property (nonatomic, strong, readonly, nullable) WordPressOrgXMLRPCApi *xmlrpcApi;
210206
@property (nonatomic, strong, readonly, nullable) WordPressOrgRestApi *selfHostedSiteRestApi;
211207
@property (nonatomic, weak, readonly, nullable) NSString *version;

WordPress/Classes/ViewRelated/People/Controllers/PersonViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ final class PersonViewController: UITableViewController {
157157
return
158158
}
159159

160-
roleViewController.roles = blog.sortedRoles?.map({ $0.toUnmanaged() }) ?? []
160+
roleViewController.roles = blog.sortedRoles.map({ $0.toUnmanaged() }) ?? []
161161
roleViewController.selectedRole = person.role
162162
roleViewController.onChange = { [weak self] newRole in
163163
self?.updateUserRole(newRole)

WordPress/Classes/ViewRelated/Post/PostEditor+JetpackSocial.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ import WordPressData
33
extension PostEditor {
44

55
func disableSocialConnectionsIfNecessary() {
6+
let connections = self.post.blog.sortedConnections
67
guard RemoteFeatureFlag.jetpackSocialImprovements.enabled(),
78
let post = self.post as? Post,
89
let remainingShares = self.post.blog.sharingLimit?.remaining,
9-
let connections = self.post.blog.sortedConnections as? [PublicizeConnection],
1010
remainingShares < connections.count else {
1111
return
1212
}
13-
1413
for connection in connections {
1514
post.disablePublicizeConnectionWithKeyringID(connection.keyringConnectionID)
1615
}
1716
}
18-
1917
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import SwiftUI
2+
import WordPressData
3+
import WordPressFlux
4+
import WordPressUI
5+
6+
@MainActor
7+
struct PostFormatPicker: View {
8+
@ObservedObject private var post: Post
9+
@State private var selction: String
10+
@State private var formats: [String]
11+
@State private var isLoading = false
12+
@State private var error: Error?
13+
14+
private let blog: Blog
15+
private let onSubmit: (String) -> Void
16+
17+
static var title: String { Strings.title }
18+
19+
init(post: Post, onSubmit: @escaping (String) -> Void) {
20+
self.post = post
21+
self.blog = post.blog
22+
let formats = post.blog.sortedPostFormatNames
23+
self._formats = State(initialValue: formats)
24+
self._selction = State(initialValue: post.postFormatText() ?? "")
25+
self.onSubmit = onSubmit
26+
}
27+
28+
var body: some View {
29+
Group {
30+
if formats.isEmpty {
31+
if isLoading {
32+
ProgressView()
33+
} else if let error {
34+
EmptyStateView.failure(error: error) {
35+
refreshPostFormats()
36+
}
37+
} else {
38+
emptyStateView
39+
}
40+
} else {
41+
formView
42+
}
43+
}
44+
.navigationTitle(Strings.title)
45+
.navigationBarTitleDisplayMode(.inline)
46+
.refreshable {
47+
await refreshPostFormats()
48+
}
49+
.onAppear {
50+
if formats.isEmpty {
51+
refreshPostFormats()
52+
}
53+
}
54+
}
55+
56+
private func refreshPostFormats() {
57+
Task {
58+
await refreshPostFormats()
59+
}
60+
}
61+
62+
private var formView: some View {
63+
Form {
64+
ForEach(formats, id: \.self) { format in
65+
Button(action: { selectFormat(format) }) {
66+
HStack {
67+
Text(format)
68+
Spacer()
69+
if selction == format {
70+
Image(systemName: "checkmark")
71+
.fontWeight(.medium)
72+
.foregroundColor(Color(UIAppColor.primary))
73+
}
74+
}
75+
}
76+
.foregroundColor(.primary)
77+
}
78+
}
79+
}
80+
81+
private var emptyStateView: some View {
82+
EmptyStateView(
83+
Strings.emptyStateTitle,
84+
systemImage: Strings.emptyStateDescription,
85+
description: "questionmark.folder"
86+
)
87+
}
88+
89+
private func selectFormat(_ format: String) {
90+
selction = format
91+
onSubmit(format)
92+
}
93+
94+
private func refreshPostFormats() async {
95+
isLoading = true
96+
error = nil
97+
98+
let blogService = BlogService(coreDataStack: ContextManager.shared)
99+
do {
100+
try await blogService.syncPostFormats(for: post.blog)
101+
self.formats = post.blog.sortedPostFormatNames
102+
} catch {
103+
self.error = error
104+
if !formats.isEmpty {
105+
Notice(error: error, title: Strings.errorTitle).post()
106+
}
107+
}
108+
109+
isLoading = false
110+
}
111+
}
112+
113+
private extension BlogService {
114+
@MainActor func syncPostFormats(for blog: Blog) async throws {
115+
try await withUnsafeThrowingContinuation { continuation in
116+
syncPostFormats(for: blog, success: {
117+
continuation.resume()
118+
}, failure: { error in
119+
continuation.resume(throwing: error)
120+
})
121+
}
122+
}
123+
}
124+
125+
private enum Strings {
126+
static let title = NSLocalizedString(
127+
"postFormatPicker.navigationTitle",
128+
value: "Post Format",
129+
comment: "Navigation bar title for the Post Format picker"
130+
)
131+
132+
static let emptyStateTitle = NSLocalizedString(
133+
"postFormatPicker.emptyState.title",
134+
value: "No Post Formats Available",
135+
comment: "Empty state title when no post formats are available"
136+
)
137+
138+
static let emptyStateDescription = NSLocalizedString(
139+
"postFormatPicker.emptyState.description",
140+
value: "Post formats haven't been configured for this site.",
141+
comment: "Empty state description when no post formats are available"
142+
)
143+
144+
static let errorTitle = NSLocalizedString(
145+
"postFormatPicker.refreshErrorMessage",
146+
value: "Failed to refresh post formats",
147+
comment: "Error message when post formats refresh fails"
148+
)
149+
}

WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,28 @@ extension PostSettingsViewController {
309309
}
310310
}
311311

312+
// MARK: - PostSettingsViewController (Post Format)
313+
314+
extension PostSettingsViewController {
315+
@objc public func showPostFormatSelector() {
316+
guard let post = apost as? Post else {
317+
return wpAssertionFailure("expected post type")
318+
}
319+
let pickerView = PostFormatPicker(post: post) { [weak self] format in
320+
guard let self else { return }
321+
if post.postFormatText() != format {
322+
WPAnalytics.track(.editorPostFormatChanged, properties: ["via": "settings"])
323+
post.setPostFormatText(format)
324+
}
325+
self.navigationController?.popViewController(animated: true)
326+
self.tableView.reloadData()
327+
}
328+
let pickerVC = UIHostingController(rootView: pickerView)
329+
pickerVC.title = PostFormatPicker.title
330+
navigationController?.pushViewController(pickerVC, animated: true)
331+
}
332+
}
333+
312334
private enum Strings {
313335
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")
314336
}

0 commit comments

Comments
 (0)