-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add PostFormatPicker #24620
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
Add PostFormatPicker #24620
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import Foundation | ||
| import CoreData | ||
| import WordPressShared | ||
|
|
||
| extension Blog { | ||
|
|
||
| // MARK: - Post Formats | ||
|
|
||
| /// Returns an array of post format keys sorted with "standard" first, then alphabetically | ||
| @objc public var sortedPostFormats: [String] { | ||
| guard let postFormats = postFormats as? [String: String], !postFormats.isEmpty else { | ||
| return [] | ||
| } | ||
|
|
||
| var sortedFormats: [String] = [] | ||
|
|
||
| // Add standard format first if it exists | ||
| if postFormats[PostFormatStandard] != nil { | ||
| sortedFormats.append(PostFormatStandard) | ||
| } | ||
|
|
||
| // Add remaining formats sorted by their display names | ||
| let nonStandardFormats = postFormats | ||
| .filter { $0.key != PostFormatStandard } | ||
| .sorted { $0.value.localizedCaseInsensitiveCompare($1.value) == .orderedAscending } | ||
| .map { $0.key } | ||
|
|
||
| sortedFormats.append(contentsOf: nonStandardFormats) | ||
|
|
||
| return sortedFormats | ||
| } | ||
|
|
||
| /// Returns an array of post format display names sorted with "Standard" first, then alphabetically | ||
| @objc public var sortedPostFormatNames: [String] { | ||
| guard let postFormats = postFormats as? [String: String] else { | ||
| return [] | ||
| } | ||
| return sortedPostFormats.compactMap { postFormats[$0] } | ||
| } | ||
|
|
||
| /// Returns the default post format display text | ||
| @objc public var defaultPostFormatText: String? { | ||
| postFormatText(fromSlug: settings?.defaultPostFormat) | ||
| } | ||
|
|
||
| // MARK: - Connections | ||
|
|
||
| /// Returns an array of PublicizeConnection objects sorted by service name, then by external name | ||
| @objc public var sortedConnections: [PublicizeConnection] { | ||
| guard let connections = Array(connections ?? []) as? [PublicizeConnection] else { | ||
| return [] | ||
| } | ||
| return connections.sorted { lhs, rhs in | ||
| // First sort by service name (case insensitive, localized) | ||
| let serviceComparison = lhs.service.localizedCaseInsensitiveCompare(rhs.service) | ||
| if serviceComparison != .orderedSame { | ||
| return serviceComparison == .orderedAscending | ||
| } | ||
| // Then sort by external name (case insensitive) | ||
| return lhs.externalName.caseInsensitiveCompare(rhs.externalName) == .orderedAscending | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Roles | ||
|
|
||
| /// Returns an array of roles sorted by order. | ||
| public var sortedRoles: [Role] { | ||
| guard let roles = Array(roles ?? []) as? [Role] else { | ||
| return [] | ||
| } | ||
| return roles.sorted { lhs, rhs in | ||
| (lhs.order?.intValue ?? 0) < (rhs.order?.intValue ?? 0) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import SwiftUI | ||
| import WordPressData | ||
| import WordPressFlux | ||
| import WordPressUI | ||
|
|
||
| @MainActor | ||
| struct PostFormatPicker: View { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there will be more similar use cases, I'll look into generalizing this. |
||
| @ObservedObject private var post: Post | ||
| @State private var selction: String | ||
| @State private var formats: [String] | ||
| @State private var isLoading = false | ||
| @State private var error: Error? | ||
|
|
||
| private let blog: Blog | ||
| private let onSubmit: (String) -> Void | ||
|
|
||
| static var title: String { Strings.title } | ||
|
|
||
| init(post: Post, onSubmit: @escaping (String) -> Void) { | ||
| self.post = post | ||
| self.blog = post.blog | ||
| let formats = post.blog.sortedPostFormatNames | ||
| self._formats = State(initialValue: formats) | ||
| self._selction = State(initialValue: post.postFormatText() ?? "") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going to suggest moving this to |
||
| self.onSubmit = onSubmit | ||
| } | ||
|
|
||
| var body: some View { | ||
| Group { | ||
| if formats.isEmpty { | ||
| if isLoading { | ||
| ProgressView() | ||
| } else if let error { | ||
| EmptyStateView.failure(error: error) { | ||
| refreshPostFormats() | ||
| } | ||
| } else { | ||
| emptyStateView | ||
| } | ||
| } else { | ||
| formView | ||
| } | ||
| } | ||
| .navigationTitle(Strings.title) | ||
| .navigationBarTitleDisplayMode(.inline) | ||
| .refreshable { | ||
| await refreshPostFormats() | ||
| } | ||
| .onAppear { | ||
| if formats.isEmpty { | ||
| refreshPostFormats() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private func refreshPostFormats() { | ||
| Task { | ||
| await refreshPostFormats() | ||
| } | ||
| } | ||
|
|
||
| private var formView: some View { | ||
| Form { | ||
| ForEach(formats, id: \.self) { format in | ||
| Button(action: { selectFormat(format) }) { | ||
| HStack { | ||
| Text(format) | ||
| Spacer() | ||
| if selction == format { | ||
| Image(systemName: "checkmark") | ||
| .fontWeight(.medium) | ||
| .foregroundColor(Color(UIAppColor.primary)) | ||
| } | ||
| } | ||
| } | ||
| .foregroundColor(.primary) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private var emptyStateView: some View { | ||
| EmptyStateView( | ||
| Strings.emptyStateTitle, | ||
| systemImage: Strings.emptyStateDescription, | ||
| description: "questionmark.folder" | ||
| ) | ||
| } | ||
|
|
||
| private func selectFormat(_ format: String) { | ||
| selction = format | ||
| onSubmit(format) | ||
| } | ||
|
|
||
| private func refreshPostFormats() async { | ||
| isLoading = true | ||
| error = nil | ||
|
|
||
| let blogService = BlogService(coreDataStack: ContextManager.shared) | ||
| do { | ||
| try await blogService.syncPostFormats(for: post.blog) | ||
| self.formats = post.blog.sortedPostFormatNames | ||
| } catch { | ||
| self.error = error | ||
| if !formats.isEmpty { | ||
| Notice(error: error, title: Strings.errorTitle).post() | ||
| } | ||
| } | ||
|
|
||
| isLoading = false | ||
| } | ||
| } | ||
|
|
||
| private extension BlogService { | ||
| @MainActor func syncPostFormats(for blog: Blog) async throws { | ||
| try await withUnsafeThrowingContinuation { continuation in | ||
| syncPostFormats(for: blog, success: { | ||
| continuation.resume() | ||
| }, failure: { error in | ||
| continuation.resume(throwing: error) | ||
| }) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private enum Strings { | ||
| static let title = NSLocalizedString( | ||
| "postFormatPicker.navigationTitle", | ||
| value: "Post Format", | ||
| comment: "Navigation bar title for the Post Format picker" | ||
| ) | ||
|
|
||
| static let emptyStateTitle = NSLocalizedString( | ||
| "postFormatPicker.emptyState.title", | ||
| value: "No Post Formats Available", | ||
| comment: "Empty state title when no post formats are available" | ||
| ) | ||
|
|
||
| static let emptyStateDescription = NSLocalizedString( | ||
| "postFormatPicker.emptyState.description", | ||
| value: "Post formats haven't been configured for this site.", | ||
| comment: "Empty state description when no post formats are available" | ||
| ) | ||
|
|
||
| static let errorTitle = NSLocalizedString( | ||
| "postFormatPicker.refreshErrorMessage", | ||
| value: "Failed to refresh post formats", | ||
| comment: "Error message when post formats refresh fails" | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude rewrote these too, so I went with it.