diff --git a/Modules/Package.resolved b/Modules/Package.resolved index b0266cb038a9..67f396fc5d67 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "290c677a1f25cc1f2edde128a8aeed0e05518150573f3f2a100ff9682e7528e5", + "originHash" : "99d98c14b91770de283a7383acb622184d4770999b658b05b7be89012876c82c", "pins" : [ { "identity" : "alamofire", @@ -336,8 +336,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/automattic/wordpress-rs", "state" : { - "revision" : "ce52fb236c0e9acef1ae60e8d4fba0f5fbd11b7d", - "version" : "0.7.0" + "revision" : "a9cf67e4895c0bcfad75c6f1e07206c22bfaf1bb", + "version" : "0.8.0" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 157fc629184e..8b79a1fb8816 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -66,7 +66,7 @@ let package = Package( .package(url: "https://github.com/wordpress-mobile/GutenbergKit", from: "0.19.0"), .package( url: "https://github.com/automattic/wordpress-rs", - exact: "0.7.0" + exact: "0.8.0" ), .package( url: "https://github.com/Automattic/color-studio", diff --git a/Modules/Sources/WordPressComments/Models/CommentDetail.swift b/Modules/Sources/WordPressComments/Models/CommentDetail.swift index 129b9054905d..a9e12ad959b1 100644 --- a/Modules/Sources/WordPressComments/Models/CommentDetail.swift +++ b/Modules/Sources/WordPressComments/Models/CommentDetail.swift @@ -16,7 +16,7 @@ struct CommentDetail: Equatable, Sendable { let parentID: Int64? // nil when the wire value is 0 (top-level) let contentHTML: String let link: URL? - let date: Date + let date: Date? var status: CommentListItem.Status /// False when the fetch fell back to view context (no email/IP; M3 edit /// needs content.raw, also unavailable). @@ -69,7 +69,7 @@ struct CommentDetail: Equatable, Sendable { parentID: Int64, contentHTML: String, link: String, - date: Date, + date: Date?, status: CommentListItem.Status, hasEditContext: Bool ) { diff --git a/Modules/Sources/WordPressComments/Models/CommentListItem.swift b/Modules/Sources/WordPressComments/Models/CommentListItem.swift index 11329227dbe2..bda56781b00a 100644 --- a/Modules/Sources/WordPressComments/Models/CommentListItem.swift +++ b/Modules/Sources/WordPressComments/Models/CommentListItem.swift @@ -19,7 +19,7 @@ struct CommentListItem: Identifiable, Equatable, Sendable { let avatarURL: URL? let postID: Int64 let snippet: String - let date: Date + let date: Date? var status: Status init( @@ -28,7 +28,7 @@ struct CommentListItem: Identifiable, Equatable, Sendable { avatarURL: URL?, postID: Int64, snippet: String, - date: Date, + date: Date?, status: Status ) { self.id = id diff --git a/Modules/Sources/WordPressComments/ViewModels/CommentDetailViewModel.swift b/Modules/Sources/WordPressComments/ViewModels/CommentDetailViewModel.swift index 5a1ede3f41ae..bb0be5109352 100644 --- a/Modules/Sources/WordPressComments/ViewModels/CommentDetailViewModel.swift +++ b/Modules/Sources/WordPressComments/ViewModels/CommentDetailViewModel.swift @@ -28,7 +28,7 @@ final class CommentDetailViewModel: ObservableObject { let authorName: String let avatarURL: URL? let postID: Int64 - let date: Date + let date: Date? let status: CommentListItem.Status } diff --git a/Modules/Sources/WordPressComments/Views/CommentRowView.swift b/Modules/Sources/WordPressComments/Views/CommentRowView.swift index 3fce673585f6..34cc266de872 100644 --- a/Modules/Sources/WordPressComments/Views/CommentRowView.swift +++ b/Modules/Sources/WordPressComments/Views/CommentRowView.swift @@ -25,9 +25,11 @@ struct CommentRowView: View { .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(2) - Text(item.date, format: .relative(presentation: .named)) - .font(.caption) - .foregroundStyle(.secondary) + if let date = item.date { + Text(date, format: .relative(presentation: .named)) + .font(.caption) + .foregroundStyle(.secondary) + } } } .accessibilityElement(children: .combine) diff --git a/Modules/Sources/WordPressComments/Views/Detail/CommentAuthorHeader.swift b/Modules/Sources/WordPressComments/Views/Detail/CommentAuthorHeader.swift index fa112bc50239..b8fa720e9aa8 100644 --- a/Modules/Sources/WordPressComments/Views/Detail/CommentAuthorHeader.swift +++ b/Modules/Sources/WordPressComments/Views/Detail/CommentAuthorHeader.swift @@ -31,9 +31,11 @@ struct CommentAuthorHeader: View { Text(header.authorName) .font(.subheadline.weight(.semibold)) postLine - Text(header.date, format: .relative(presentation: .named)) - .font(.caption) - .foregroundStyle(.secondary) + if let date = header.date { + Text(date, format: .relative(presentation: .named)) + .font(.caption) + .foregroundStyle(.secondary) + } } Spacer(minLength: 0) Image(systemName: "info.circle") @@ -73,7 +75,9 @@ private struct CommentAuthorInfoSheet: View { var body: some View { NavigationStack { List { - LabeledContent(Strings.infoDateLabel, value: header.date.formatted(.dateTime)) + if let date = header.date { + LabeledContent(Strings.infoDateLabel, value: date.formatted(.dateTime)) + } if let url = detail?.authorURL { Link(destination: url) { LabeledContent(Strings.infoWebsiteLabel, value: url.absoluteString) diff --git a/Modules/Tests/WordPressCommentsTests/CommentDetailTests.swift b/Modules/Tests/WordPressCommentsTests/CommentDetailTests.swift index 2c77e3519586..13f56b1294d1 100644 --- a/Modules/Tests/WordPressCommentsTests/CommentDetailTests.swift +++ b/Modules/Tests/WordPressCommentsTests/CommentDetailTests.swift @@ -28,4 +28,9 @@ struct CommentDetailTests { let detail = CommentDetail(comment: .detailBuilder(authorName: "")) #expect(detail.authorName == Strings.anonymousAuthor) } + + @Test func missingDateMapsToNil() { + let detail = CommentDetail(comment: .detailBuilder(date: nil)) + #expect(detail.date == nil) + } } diff --git a/Modules/Tests/WordPressCommentsTests/CommentListItemTests.swift b/Modules/Tests/WordPressCommentsTests/CommentListItemTests.swift index 99628662deaf..ca34c72b3d08 100644 --- a/Modules/Tests/WordPressCommentsTests/CommentListItemTests.swift +++ b/Modules/Tests/WordPressCommentsTests/CommentListItemTests.swift @@ -30,6 +30,11 @@ struct CommentListItemTests { #expect(item.avatarURL == nil) } + @Test func missingDateMapsToNil() { + let item = CommentListItem(comment: makeComment(date: nil)) + #expect(item.date == nil) + } + @Test func statusMapping() { #expect(CommentListItem(comment: makeComment(status: .approved)).status == .approved) #expect(CommentListItem(comment: makeComment(status: .spam)).status == .spam) diff --git a/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift b/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift index b83b5e214cba..1e0c00fd0df0 100644 --- a/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift +++ b/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift @@ -10,7 +10,7 @@ func makeComment( content: String = "
Hello world
", post: Int64 = 10, status: CommentStatus = .approved, - date: Date = Date(timeIntervalSince1970: 1_700_000_000) + date: Date? = Date(timeIntervalSince1970: 1_700_000_000) ) -> CommentWithViewContext { CommentWithViewContext( id: id, @@ -18,7 +18,7 @@ func makeComment( authorName: authorName, authorUrl: "", content: CommentContentWithViewContext(rendered: content), - date: "2023-11-14T22:13:20", + date: WpDateString(value: "2023-11-14T22:13:20"), dateGmt: date, link: "https://example.com/?p=\(post)#comment-\(id)", parent: 0, @@ -49,7 +49,7 @@ extension CommentWithViewContext { post: Int64 = 10, parent: Int64 = 0, status: CommentStatus = .approved, - date: Date = Date(timeIntervalSince1970: 1_700_000_000) + date: Date? = Date(timeIntervalSince1970: 1_700_000_000) ) -> CommentWithViewContext { CommentWithViewContext( id: id, @@ -57,7 +57,7 @@ extension CommentWithViewContext { authorName: authorName, authorUrl: authorUrl, content: CommentContentWithViewContext(rendered: content), - date: "2023-11-14T22:13:20", + date: WpDateString(value: "2023-11-14T22:13:20"), dateGmt: date, link: "https://example.com/?p=\(post)#comment-\(id)", parent: parent, @@ -82,7 +82,7 @@ extension CommentWithEditContext { post: Int64 = 10, parent: Int64 = 0, status: CommentStatus = .approved, - date: Date = Date(timeIntervalSince1970: 1_700_000_000) + date: Date? = Date(timeIntervalSince1970: 1_700_000_000) ) -> CommentWithEditContext { CommentWithEditContext( id: id, @@ -93,7 +93,7 @@ extension CommentWithEditContext { authorUrl: authorUrl, authorUserAgent: "", content: CommentContentWithEditContext(raw: content, rendered: content), - date: "2023-11-14T22:13:20", + date: WpDateString(value: "2023-11-14T22:13:20"), dateGmt: date, link: "https://example.com/?p=\(post)#comment-\(id)", parent: parent, diff --git a/Modules/Tests/WordPressCoreTests/MockWordPressClientAPI.swift b/Modules/Tests/WordPressCoreTests/MockWordPressClientAPI.swift index 894985eea66b..d81050f9e555 100644 --- a/Modules/Tests/WordPressCoreTests/MockWordPressClientAPI.swift +++ b/Modules/Tests/WordPressCoreTests/MockWordPressClientAPI.swift @@ -137,7 +137,8 @@ final class MockUsersRequestExecutor: UsersRequestExecutor, @unchecked Sendable locale: "en_US", nickname: "testuser", slug: "testuser", - registeredDate: "2024-01-01T00:00:00", + registeredDate: Date(timeIntervalSince1970: 1_704_067_200), // 2024-01-01 + roles: [], capabilities: UserCapabilitiesMap(map: [:]), extraCapabilities: UserCapabilitiesMap(map: [:]), diff --git a/Package.resolved b/Package.resolved index 0e31d78f2a9a..959ce71fb8b4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -336,8 +336,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/wordpress-rs", "state" : { - "revision" : "ce52fb236c0e9acef1ae60e8d4fba0f5fbd11b7d", - "version" : "0.7.0" + "revision" : "a9cf67e4895c0bcfad75c6f1e07206c22bfaf1bb", + "version" : "0.8.0" } }, { diff --git a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift index 5277be0bc4c2..a4050541e664 100644 --- a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift @@ -358,11 +358,11 @@ private func makeRemotePost( ) -> AnyPostWithEditContext { AnyPostWithEditContext( id: PostId(1), - date: "2025-01-01T00:00:00", + date: WpDateString(value: "2025-01-01T00:00:00"), dateGmt: Date(timeIntervalSince1970: 0), guid: PostGuidWithEditContext(raw: nil, rendered: ""), link: "https://example.com", - modified: "2025-01-01T00:00:00", + modified: WpDateString(value: "2025-01-01T00:00:00"), modifiedGmt: Date(timeIntervalSince1970: 0), slug: "test-post", status: .draft, diff --git a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostSettingsViewModelTests.swift b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostSettingsViewModelTests.swift index cbf93ccfa1db..55eb9f14bb03 100644 --- a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostSettingsViewModelTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostSettingsViewModelTests.swift @@ -247,11 +247,11 @@ private func makePostWithDisabledConnection( let additionalFields = try WpAdditionalFields.fromJsonString(json: json) return AnyPostWithEditContext( id: PostId(1), - date: "2025-01-01T00:00:00", + date: WpDateString(value: "2025-01-01T00:00:00"), dateGmt: Date(timeIntervalSince1970: 0), guid: PostGuidWithEditContext(raw: nil, rendered: ""), link: "https://example.com", - modified: "2025-01-01T00:00:00", + modified: WpDateString(value: "2025-01-01T00:00:00"), modifiedGmt: Date(timeIntervalSince1970: 0), slug: "test-post", status: status, diff --git a/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift b/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift index b5b795fd5a8b..948187ca1737 100644 --- a/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift @@ -1484,11 +1484,11 @@ private func makeRemotePost( ) -> AnyPostWithEditContext { AnyPostWithEditContext( id: PostId(1), - date: "2025-01-01T00:00:00", + date: WpDateString(value: "2025-01-01T00:00:00"), dateGmt: Date(timeIntervalSince1970: 0), guid: PostGuidWithEditContext(raw: nil, rendered: ""), link: "https://example.com", - modified: "2025-01-01T00:00:00", + modified: WpDateString(value: "2025-01-01T00:00:00"), modifiedGmt: Date(timeIntervalSince1970: 0), slug: "test-post", status: .draft, diff --git a/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift b/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift index 2fbc6010f3b6..13b303b97ad8 100644 --- a/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift +++ b/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift @@ -678,7 +678,7 @@ private extension SelfHostedSiteAuthenticator { } } -private final class EmptyAppNotifier: WpAppNotifier { +final class EmptyAppNotifier: WpAppNotifier { func requestedWithInvalidAuthentication(requestUrl: String) async { // Do nothing. } diff --git a/WordPress/Classes/Networking/JetpackSocialFactory.swift b/WordPress/Classes/Networking/JetpackSocialFactory.swift index 4d4d34411644..172c4672366a 100644 --- a/WordPress/Classes/Networking/JetpackSocialFactory.swift +++ b/WordPress/Classes/Networking/JetpackSocialFactory.swift @@ -34,8 +34,13 @@ public final class JetpackSocialFactory: Sendable { } let service = SiteSocialConnectionsService( client: WPComApiClient( - urlSession: URLSession(configuration: .ephemeral), - authentication: configuration.authentication + delegate: WpApiClientDelegate( + authProvider: .staticWithAuth(auth: configuration.authentication), + requestExecutor: WpRequestExecutor(urlSession: URLSession(configuration: .ephemeral)), + middlewarePipeline: .default, + appNotifier: EmptyAppNotifier(), + languageProvider: WPComDeviceLanguageProvider() + ) ), siteId: configuration.siteId, canMarkAsShared: canMarkAsShared diff --git a/WordPress/Classes/Networking/WordPressDotComClient.swift b/WordPress/Classes/Networking/WordPressDotComClient.swift index 8fa6f662235d..7c892fd93e1d 100644 --- a/WordPress/Classes/Networking/WordPressDotComClient.swift +++ b/WordPress/Classes/Networking/WordPressDotComClient.swift @@ -21,7 +21,8 @@ actor WordPressDotComClient: MediaHostProtocol { middlewarePipeline: WpApiMiddlewarePipeline(middlewares: [ WpComTrafficDebugger() ]), - appNotifier: WpComNotifier() + appNotifier: WpComNotifier(), + languageProvider: WPComDeviceLanguageProvider() ) self.api = WPComApiClient(delegate: delegate) @@ -173,3 +174,19 @@ extension RequestMethod: @retroactive CustomStringConvertible { } } } + +/// Asks WordPress.com to localize responses to the first of the user's preferred languages that +/// WordPress.com supports. +/// +/// Maps through the wordpress-rs language table, so regional variants such as `en-gb` resolve. +/// Sends no locale when none of the preferred languages is supported. +final class WPComDeviceLanguageProvider: WpComLanguageProvider { + func currentLanguage() -> WpComLanguage? { + for identifier in Locale.preferredLanguages { + if let language = WpComLanguage(locale: Locale(identifier: identifier)) { + return language + } + } + return nil + } +} diff --git a/WordPress/Classes/Services/ApplicationPasswordService.swift b/WordPress/Classes/Services/ApplicationPasswordService.swift index 009361a448d2..a389a8233235 100644 --- a/WordPress/Classes/Services/ApplicationPasswordService.swift +++ b/WordPress/Classes/Services/ApplicationPasswordService.swift @@ -29,7 +29,8 @@ extension ApplicationPasswordService: ApplicationTokenListDataProvider { } if self.currentApplicationPasswordUUID == nil { - self.currentApplicationPasswordUUID = try? await apiClient.api.applicationPasswords.retrieveCurrentWithViewContext().data.uuid.uuid + self.currentApplicationPasswordUUID = try? await apiClient.api.applicationPasswords + .retrieveCurrentWithViewContext().data.uuid.uuid } return try await fetchTokens(forUserId: userId) @@ -47,21 +48,17 @@ extension ApplicationPasswordService: ApplicationTokenListDataProvider { extension ApplicationTokenItem { init?(_ rawToken: ApplicationPasswordWithEditContext) { - guard - let uuid = UUID(uuidString: rawToken.uuid.uuid), - let createdAt = Date.fromWordPressDate(rawToken.created) - else { + guard let uuid = UUID(uuidString: rawToken.uuid.uuid) else { return nil } - let lastUsed = rawToken.lastUsed.flatMap(Date.fromWordPressDate(_:)) - + // wordpress-rs 0.8.0 delivers these as parsed `Date`s, not strings. self = ApplicationTokenItem( name: rawToken.name, uuid: uuid, appId: rawToken.appId.appId, - createdAt: createdAt, - lastUsed: lastUsed, + createdAt: rawToken.created, + lastUsed: rawToken.lastUsed, lastIpAddress: rawToken.lastIp?.value ) }