diff --git a/Gemfile b/Gemfile index d58115a4a854..da39ac08c724 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'fastlane-plugin-sentry' # gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', ref: '' gem 'fastlane-plugin-wpmreleasetoolkit', '~> 12.4' gem 'rake' -gem 'rubocop', '~> 1.69' +gem 'rubocop', '~> 1.71' gem 'rubocop-rake', '~> 0.6' gem 'xcpretty-travis-formatter' diff --git a/Gemfile.lock b/Gemfile.lock index 120a6044c025..c5f8d3c8a3a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -208,7 +208,7 @@ GEM xcpretty (~> 0.4.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-appcenter (2.1.2) - fastlane-plugin-sentry (1.25.1) + fastlane-plugin-sentry (1.27.1) os (~> 1.1, >= 1.1.4) fastlane-plugin-wpmreleasetoolkit (12.4.0) activesupport (>= 6.1.7.1) @@ -280,14 +280,14 @@ GEM concurrent-ruby (~> 1.0) java-properties (0.3.0) jmespath (1.6.2) - json (2.9.0) + json (2.9.1) jwt (2.9.3) base64 kramdown (2.5.1) rexml (>= 3.3.9) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - language_server-protocol (3.17.0.3) + language_server-protocol (3.17.0.4) logger (1.6.3) mini_magick (4.13.2) mini_mime (1.1.5) @@ -312,7 +312,7 @@ GEM optparse (0.6.0) os (1.1.4) parallel (1.26.3) - parser (3.3.6.0) + parser (3.3.7.0) ast (~> 2.4.1) racc pkg-config (1.5.7) @@ -328,7 +328,7 @@ GEM rake-compiler (1.2.8) rake rchardet (1.8.0) - regexp_parser (2.9.3) + regexp_parser (2.10.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -338,7 +338,7 @@ GEM rmagick (5.3.0) pkg-config (~> 1.4) rouge (3.28.0) - rubocop (1.69.2) + rubocop (1.71.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -348,7 +348,7 @@ GEM rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.37.0) + rubocop-ast (1.38.0) parser (>= 3.3.1.0) rubocop-rake (0.6.0) rubocop (~> 1.0) @@ -410,7 +410,7 @@ DEPENDENCIES fastlane-plugin-wpmreleasetoolkit (~> 12.4) rake rmagick (~> 5.3.0) - rubocop (~> 1.69) + rubocop (~> 1.71) rubocop-rake (~> 0.6) xcpretty-travis-formatter diff --git a/Modules/Package.swift b/Modules/Package.swift index 6b8975df1bbe..1b42f405890f 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -47,8 +47,8 @@ let package = Package( .package(url: "https://github.com/wordpress-mobile/WordPressKit-iOS", branch: "wpios-edition"), .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. - .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20241116"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "f8c5c417c789c8d052093838e622828ae4ec076f"), + .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20250127"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "fb31301ea6a94376237947afb4242f75c074f43c"), .package(url: "https://github.com/Automattic/color-studio", branch: "trunk"), ], targets: XcodeSupport.targets + [ @@ -63,6 +63,7 @@ let package = Package( .product(name: "XCUITestHelpers", package: "XCUITestHelpers"), ], swiftSettings: [.swiftLanguageMode(.v5)]), .target(name: "WordPressFlux", swiftSettings: [.swiftLanguageMode(.v5)]), + .target(name: "WordPressCore", dependencies: [.target(name: "WordPressShared"), .product(name: "WordPressAPI", package: "wordpress-rs")]), .target(name: "WordPressSharedObjC", resources: [.process("Resources")], swiftSettings: [.swiftLanguageMode(.v5)]), .target(name: "WordPressShared", dependencies: [.target(name: "WordPressSharedObjC")], resources: [.process("Resources")], swiftSettings: [.swiftLanguageMode(.v5)]), .target(name: "WordPressTesting", resources: [.process("Resources")]), @@ -85,7 +86,8 @@ let package = Package( ]), .testTarget(name: "WordPressSharedTests", dependencies: [.target(name: "WordPressShared")], swiftSettings: [.swiftLanguageMode(.v5)]), .testTarget(name: "WordPressSharedObjCTests", dependencies: [.target(name: "WordPressShared"), .target(name: "WordPressTesting")], swiftSettings: [.swiftLanguageMode(.v5)]), - .testTarget(name: "WordPressUITests", dependencies: [.target(name: "WordPressUI")], swiftSettings: [.swiftLanguageMode(.v5)]), + .testTarget(name: "WordPressUIUnitTests", dependencies: [.target(name: "WordPressUI")], swiftSettings: [.swiftLanguageMode(.v5)]), + .testTarget(name: "WordPressCoreTests", dependencies: [.target(name: "WordPressCore")]), ] ) @@ -156,6 +158,7 @@ enum XcodeSupport { "WordPressShared", "AsyncImageKit", "WordPressUI", + "WordPressCore", .product(name: "Alamofire", package: "Alamofire"), .product(name: "AutomatticAbout", package: "AutomatticAbout-swift"), .product(name: "AutomatticTracks", package: "Automattic-Tracks-iOS"), diff --git a/WordPress/Classes/Utility/DataStore/DataStore.swift b/Modules/Sources/WordPressCore/DataStore/DataStore.swift similarity index 94% rename from WordPress/Classes/Utility/DataStore/DataStore.swift rename to Modules/Sources/WordPressCore/DataStore/DataStore.swift index a20823f1c529..297b99678d8b 100644 --- a/WordPress/Classes/Utility/DataStore/DataStore.swift +++ b/Modules/Sources/WordPressCore/DataStore/DataStore.swift @@ -3,7 +3,7 @@ import Foundation /// An abstraction of local data storage, with CRUD operations. public protocol DataStore: Actor { associatedtype T: Identifiable & Sendable - associatedtype Query + associatedtype Query: Sendable func list(query: Query) async throws -> [T] func delete(query: Query) async throws diff --git a/WordPress/Classes/Utility/DataStore/InMemoryDataStore.swift b/Modules/Sources/WordPressCore/DataStore/InMemoryDataStore.swift similarity index 87% rename from WordPress/Classes/Utility/DataStore/InMemoryDataStore.swift rename to Modules/Sources/WordPressCore/DataStore/InMemoryDataStore.swift index 0ad8071ca0f1..4533bc770e00 100644 --- a/WordPress/Classes/Utility/DataStore/InMemoryDataStore.swift +++ b/Modules/Sources/WordPressCore/DataStore/InMemoryDataStore.swift @@ -1,5 +1,5 @@ import Foundation -import Combine +@preconcurrency import Combine /// A `DataStore` type that stores data in memory. public protocol InMemoryDataStore: DataStore { @@ -16,11 +16,11 @@ public protocol InMemoryDataStore: DataStore { public extension InMemoryDataStore { func delete(query: Query) async throws { - var updated = Set() let result = try await list(query: query) - result.forEach { - if storage.removeValue(forKey: $0.id) != nil { - updated.insert($0.id) + var updated = Set() + for item in result { + if storage.removeValue(forKey: item.id) != nil { + updated.insert(item.id) } } @@ -31,9 +31,9 @@ public extension InMemoryDataStore { func store(_ data: [T]) async throws { var updated = Set() - data.forEach { - updated.insert($0.id) - self.storage[$0.id] = $0 + for item in data { + updated.insert(item.id) + self.storage[item.id] = item } if !updated.isEmpty { diff --git a/WordPress/Classes/Users/ViewModel/DisplayUser.swift b/Modules/Sources/WordPressCore/Users/DisplayUser.swift similarity index 92% rename from WordPress/Classes/Users/ViewModel/DisplayUser.swift rename to Modules/Sources/WordPressCore/Users/DisplayUser.swift index 23fc80a143c5..eec70b5a4d7e 100644 --- a/WordPress/Classes/Users/ViewModel/DisplayUser.swift +++ b/Modules/Sources/WordPressCore/Users/DisplayUser.swift @@ -1,8 +1,7 @@ import Foundation -import WordPressShared -public struct DisplayUser: Identifiable, Codable, Hashable { - public let id: Int32 +public struct DisplayUser: Identifiable, Codable, Hashable, Sendable { + public let id: Int64 public let handle: String public let username: String public let firstName: String @@ -17,7 +16,7 @@ public struct DisplayUser: Identifiable, Codable, Hashable { public let biography: String? public init( - id: Int32, + id: Int64, handle: String, username: String, firstName: String, @@ -42,7 +41,7 @@ public struct DisplayUser: Identifiable, Codable, Hashable { self.biography = biography } - static let MockUser = DisplayUser( + public static let mockUser = DisplayUser( id: 16, handle: "@person", username: "example", diff --git a/WordPress/Classes/Users/InMemoryUserDataStore.swift b/Modules/Sources/WordPressCore/Users/UserDataStore.swift similarity index 76% rename from WordPress/Classes/Users/InMemoryUserDataStore.swift rename to Modules/Sources/WordPressCore/Users/UserDataStore.swift index 3f60f41e1023..039bb249816f 100644 --- a/WordPress/Classes/Users/InMemoryUserDataStore.swift +++ b/Modules/Sources/WordPressCore/Users/UserDataStore.swift @@ -1,5 +1,15 @@ import Foundation -import Combine +@preconcurrency import Combine +import WordPressShared + +public protocol UserDataStore: DataStore where T == DisplayUser, Query == UserDataStoreQuery { +} + +public enum UserDataStoreQuery: Equatable, Sendable { + case all + case id(Set) + case search(String) +} public actor InMemoryUserDataStore: UserDataStore, InMemoryDataStore { public typealias T = DisplayUser @@ -11,6 +21,8 @@ public actor InMemoryUserDataStore: UserDataStore, InMemoryDataStore { updates.send(completion: .finished) } + public init() {} + public func list(query: Query) throws -> [T] { switch query { case .all: diff --git a/WordPress/Classes/Services/UserService.swift b/Modules/Sources/WordPressCore/Users/UserService.swift similarity index 61% rename from WordPress/Classes/Services/UserService.swift rename to Modules/Sources/WordPressCore/Users/UserService.swift index 5a9b2dbea872..5b2f977e249f 100644 --- a/WordPress/Classes/Services/UserService.swift +++ b/Modules/Sources/WordPressCore/Users/UserService.swift @@ -1,15 +1,12 @@ import Foundation import Combine import WordPressAPI -import WordPressUI /// UserService is responsible for fetching user acounts via the .org REST API – it's the replacement for `UsersService` (the XMLRPC-based approach) /// -actor UserService: UserServiceProtocol, UserDataStoreProvider { +public actor UserService: UserServiceProtocol { private let client: WordPressClient - - private let _dataStore: InMemoryUserDataStore = .init() - var userDataStore: any UserDataStore { _dataStore } + private let userDataStore: InMemoryUserDataStore = .init() private var _currentUser: UserWithEditContext? private var currentUser: UserWithEditContext? { @@ -21,29 +18,29 @@ actor UserService: UserServiceProtocol, UserDataStoreProvider { } } - init(client: WordPressClient) { + public init(client: WordPressClient) { self.client = client } - func fetchUsers() async throws { + public func fetchUsers() async throws { let sequence = await client.api.users.sequenceWithEditContext(params: .init(perPage: 100)) var started = false for try await users in sequence { if !started { - try await _dataStore.delete(query: .all) + try await userDataStore.delete(query: .all) } - try await _dataStore.store(users.compactMap { DisplayUser(user: $0) }) + try await userDataStore.store(users.compactMap { DisplayUser(user: $0) }) started = true } } - func isCurrentUserCapableOf(_ capability: String) async -> Bool { + public func isCurrentUserCapableOf(_ capability: String) async -> Bool { await currentUser?.capabilities.keys.contains(capability) == true } - func deleteUser(id: Int32, reassigningPostsTo newUserId: Int32) async throws { + public func deleteUser(id: Int64, reassigningPostsTo newUserId: Int64) async throws { let result = try await client.api.users.delete( userId: id, params: UserDeleteParams(reassign: newUserId) @@ -51,17 +48,29 @@ actor UserService: UserServiceProtocol, UserDataStoreProvider { // Remove the deleted user from the cached users list. if result.deleted { - try await _dataStore.delete(query: .id([id])) + try await userDataStore.delete(query: .id([id])) } } - func setNewPassword(id: Int32, newPassword: String) async throws { + public func setNewPassword(id: Int64, newPassword: String) async throws { _ = try await client.api.users.update( - userId: Int32(id), + userId: id, params: UserUpdateParams(password: newPassword) ) } + public func allUsers() async throws -> [DisplayUser] { + try await userDataStore.list(query: .all) + } + + public func streamSearchResult(input: String) async -> AsyncStream> { + await userDataStore.listStream(query: .search(input)) + } + + public func streamAll() async -> AsyncStream> { + await userDataStore.listStream(query: .all) + } + } private extension DisplayUser { @@ -86,12 +95,9 @@ private extension DisplayUser { } static func profilePhotoUrl(for user: UserWithEditContext) -> URL? { - // The key is the size of the avatar. Get the largetst one, which is 96x96px. - // https://github.com/WordPress/wordpress-develop/blob/6.6.2/src/wp-includes/rest-api.php#L1253-L1260 - guard let url = user.avatarUrls? - .max(by: { $0.key.compare($1.key, options: .numeric) == .orderedAscending } )? - .value - else { return nil } + guard let url = user.avatarUrls?[.size96] ?? user.avatarUrls?[.size48] ?? user.avatarUrls?[.size24], let url else { + return nil + } return URL(string: url) } diff --git a/Modules/Sources/WordPressCore/Users/UserServiceProtocol.swift b/Modules/Sources/WordPressCore/Users/UserServiceProtocol.swift new file mode 100644 index 000000000000..bc1fd7b1891e --- /dev/null +++ b/Modules/Sources/WordPressCore/Users/UserServiceProtocol.swift @@ -0,0 +1,18 @@ +import Foundation +import WordPressAPI + +public protocol UserServiceProtocol: Actor { + func fetchUsers() async throws + + func isCurrentUserCapableOf(_ capability: String) async -> Bool + + func setNewPassword(id: UserId, newPassword: String) async throws + + func deleteUser(id: UserId, reassigningPostsTo newUserId: UserId) async throws + + func allUsers() async throws -> [DisplayUser] + + func streamSearchResult(input: String) async -> AsyncStream> + + func streamAll() async -> AsyncStream> +} diff --git a/Modules/Sources/WordPressCore/WordPressClient.swift b/Modules/Sources/WordPressCore/WordPressClient.swift new file mode 100644 index 000000000000..df89a6094ad5 --- /dev/null +++ b/Modules/Sources/WordPressCore/WordPressClient.swift @@ -0,0 +1,14 @@ +import Foundation +import WordPressAPI + +public actor WordPressClient { + + public let api: WordPressAPI + private let rootUrl: String + + public init(api: WordPressAPI, rootUrl: ParsedUrl) { + self.api = api + self.rootUrl = rootUrl.url() + } + +} diff --git a/Modules/Tests/AsyncImageKitTests/ImageDownloaderTests.swift b/Modules/Tests/AsyncImageKitTests/ImageDownloaderTests.swift index 95e967a166c2..9e652065e430 100644 --- a/Modules/Tests/AsyncImageKitTests/ImageDownloaderTests.swift +++ b/Modules/Tests/AsyncImageKitTests/ImageDownloaderTests.swift @@ -11,7 +11,7 @@ import OHHTTPStubsSwift private let cache = MockMemoryCache() init() async throws { - sut = ImageDownloader(cache: cache, authenticator: nil) + sut = ImageDownloader(cache: cache) } deinit { diff --git a/WordPress/WordPressTest/DataStoreTests.swift b/Modules/Tests/WordPressCoreTests/DataStoreTests.swift similarity index 94% rename from WordPress/WordPressTest/DataStoreTests.swift rename to Modules/Tests/WordPressCoreTests/DataStoreTests.swift index 3305a61b5b37..26346783c5a6 100644 --- a/WordPress/WordPressTest/DataStoreTests.swift +++ b/Modules/Tests/WordPressCoreTests/DataStoreTests.swift @@ -1,7 +1,6 @@ import Foundation import Testing - -@testable import WordPress +import WordPressCore @Suite(.timeLimit(.minutes(1))) struct InMemoryDataStoreTests { @@ -25,7 +24,7 @@ struct InMemoryDataStoreTests { Task.detached { try await Task.sleep(for: .milliseconds(50)) - try await store.store([.MockUser]) + try await store.store([.mockUser]) } await confirmation("The stream produces an update", expectedCount: 2) { confirmation in @@ -38,7 +37,7 @@ struct InMemoryDataStoreTests { @Test func testUpdatesAfterDelete() async throws { let store: InMemoryUserDataStore = InMemoryUserDataStore() - try await store.store([.MockUser]) + try await store.store([.mockUser]) let stream = await store.listStream(query: .all) diff --git a/Modules/Tests/WordPressUITests/Extensions/ResourcesBundleTests.swift b/Modules/Tests/WordPressUIUnitTests/Extensions/ResourcesBundleTests.swift similarity index 100% rename from Modules/Tests/WordPressUITests/Extensions/ResourcesBundleTests.swift rename to Modules/Tests/WordPressUIUnitTests/Extensions/ResourcesBundleTests.swift diff --git a/Modules/Tests/WordPressUITests/Extensions/UIColorHelpersTests.swift b/Modules/Tests/WordPressUIUnitTests/Extensions/UIColorHelpersTests.swift similarity index 100% rename from Modules/Tests/WordPressUITests/Extensions/UIColorHelpersTests.swift rename to Modules/Tests/WordPressUIUnitTests/Extensions/UIColorHelpersTests.swift diff --git a/Modules/Tests/WordPressUITests/Extensions/UIImage+CropTests.swift b/Modules/Tests/WordPressUIUnitTests/Extensions/UIImage+CropTests.swift similarity index 100% rename from Modules/Tests/WordPressUITests/Extensions/UIImage+CropTests.swift rename to Modules/Tests/WordPressUIUnitTests/Extensions/UIImage+CropTests.swift diff --git a/Modules/Tests/WordPressUITests/Extensions/UIImage+ScaleTests.swift b/Modules/Tests/WordPressUIUnitTests/Extensions/UIImage+ScaleTests.swift similarity index 100% rename from Modules/Tests/WordPressUITests/Extensions/UIImage+ScaleTests.swift rename to Modules/Tests/WordPressUIUnitTests/Extensions/UIImage+ScaleTests.swift diff --git a/Modules/Tests/WordPressUITests/Extensions/UIView+AutoLayoutHelperTests.swift b/Modules/Tests/WordPressUIUnitTests/Extensions/UIView+AutoLayoutHelperTests.swift similarity index 100% rename from Modules/Tests/WordPressUITests/Extensions/UIView+AutoLayoutHelperTests.swift rename to Modules/Tests/WordPressUIUnitTests/Extensions/UIView+AutoLayoutHelperTests.swift diff --git a/Modules/Tests/WordPressUITests/Extensions/UIViewControllerHelperTest.swift b/Modules/Tests/WordPressUIUnitTests/Extensions/UIViewControllerHelperTest.swift similarity index 100% rename from Modules/Tests/WordPressUITests/Extensions/UIViewControllerHelperTest.swift rename to Modules/Tests/WordPressUIUnitTests/Extensions/UIViewControllerHelperTest.swift diff --git a/Modules/Tests/WordPressUITests/Ghosts/UITableView+GhostTests.swift b/Modules/Tests/WordPressUIUnitTests/Ghosts/UITableView+GhostTests.swift similarity index 100% rename from Modules/Tests/WordPressUITests/Ghosts/UITableView+GhostTests.swift rename to Modules/Tests/WordPressUIUnitTests/Ghosts/UITableView+GhostTests.swift diff --git a/Modules/Tests/WordPressUITests/Ghosts/UIView+GhostTests.swift b/Modules/Tests/WordPressUIUnitTests/Ghosts/UIView+GhostTests.swift similarity index 100% rename from Modules/Tests/WordPressUITests/Ghosts/UIView+GhostTests.swift rename to Modules/Tests/WordPressUIUnitTests/Ghosts/UIView+GhostTests.swift diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 90679c5d71ea..3421eccae034 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,8 @@ 25.8 ----- * [*] Enable dismissing the virtual keyboard in the experimental editor [#23988] +* [*] Remove Submit Feedback form from the Me menu [#24020] + 25.7 ----- diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index 232f54c755aa..c15aa4e70a9d 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "c4be8fc173b9003411c920de212402441562c42ae4c4903315bcf75399fbb899", + "originHash" : "6b3c7bab8241a2a687e3889dbb08f2b2b5f2035adf279e8a8b88fd75f8c545e4", "pins" : [ { "identity" : "alamofire", @@ -141,7 +141,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "f8c5c417c789c8d052093838e622828ae4ec076f" + "revision" : "fb31301ea6a94376237947afb4242f75c074f43c" } }, { @@ -373,8 +373,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/wordpress-rs", "state" : { - "branch" : "alpha-20241116", - "revision" : "1249ae77fcea2e836b7878a1b1cffdf6c1080256" + "branch" : "alpha-20250127", + "revision" : "1b4eeb2c3209d522805a859c8fff3e7c77e73665" } }, { diff --git a/WordPress/Classes/Extensions/WpApiError+Localized.swift b/WordPress/Classes/Extensions/WpApiError+Localized.swift index b6c29edb4ea5..3c7466a62312 100644 --- a/WordPress/Classes/Extensions/WpApiError+Localized.swift +++ b/WordPress/Classes/Extensions/WpApiError+Localized.swift @@ -15,6 +15,8 @@ extension WpApiError { case let .WpError(_, errorMessage, _, _): let format = NSLocalizedString("generic.error.rest-api-error", value: "Your site sent an error response: %@", comment: "Error message format when REST API returns an error response. The first argument is error message.") return String(format: format, errorMessage) + case .MediaFileNotFound: + return NSLocalizedString("wordpress.api.upload.media.fileNotFound", value: "Can't locate the media file on the device", comment: "Error message when failing to find selected media file on the user's device.") } } } diff --git a/WordPress/Classes/Login/LoginWithUrlView.swift b/WordPress/Classes/Login/LoginWithUrlView.swift index 004a472c732f..f852d0688990 100644 --- a/WordPress/Classes/Login/LoginWithUrlView.swift +++ b/WordPress/Classes/Login/LoginWithUrlView.swift @@ -123,7 +123,7 @@ private extension SelfHostedSiteAuthenticator.SignInError { } -extension WordPressLoginClient.Error { +extension WordPressLoginClientError { var errorMessage: String? { switch self { diff --git a/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift b/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift index aa2bffaa8a6c..c244de7ff051 100644 --- a/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift +++ b/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift @@ -9,7 +9,7 @@ import WordPressAuthenticator final actor SelfHostedSiteAuthenticator { enum SignInError: Error { - case authentication(WordPressLoginClient.Error) + case authentication(WordPressLoginClientError) case loadingSiteInfoFailure case savingSiteFailure } @@ -62,7 +62,7 @@ final actor SelfHostedSiteAuthenticator { } @MainActor - func authentication(site: String, from anchor: ASPresentationAnchor?) async throws(WordPressLoginClient.Error) -> WpApiApplicationPasswordDetails { + func authentication(site: String, from anchor: ASPresentationAnchor?) async throws(WordPressLoginClientError) -> WpApiApplicationPasswordDetails { let appId: WpUuid let appName: String @@ -78,14 +78,11 @@ final actor SelfHostedSiteAuthenticator { let timestamp = ISO8601DateFormatter.string(from: .now, timeZone: .current, formatOptions: .withInternetDateTime) let appNameValue = "\(appName) - \(deviceName) (\(timestamp))" - let result = await internalClient.login( + return try await internalClient.login( site: site, appName: appNameValue, - appId: appId, - contextProvider: WebAuthenticationPresentationAnchorProvider(anchor: anchor ?? ASPresentationAnchor()) + appId: appId ) - - return try result.get() } private func handleSuccess(_ success: WpApiApplicationPasswordDetails) async throws(SignInError) -> WordPressOrgCredentials { diff --git a/WordPress/Classes/Networking/WordPressClient.swift b/WordPress/Classes/Networking/WordPressClient.swift index 03d3c8c4c6f3..a16e9af80941 100644 --- a/WordPress/Classes/Networking/WordPressClient.swift +++ b/WordPress/Classes/Networking/WordPressClient.swift @@ -1,6 +1,6 @@ import Foundation import WordPressAPI -import Network +import WordPressCore struct WordPressSite { enum SiteType { @@ -29,21 +29,7 @@ struct WordPressSite { } } -actor WordPressClient { - - enum ReachabilityStatus { - case unknown - case available(path: NWPath) - case unavailable(reason: NWPath.UnsatisfiedReason) - } - - let api: WordPressAPI - private let rootUrl: String - - init(api: WordPressAPI, rootUrl: ParsedUrl) { - self.api = api - self.rootUrl = rootUrl.url() - } +extension WordPressClient { init(site: WordPressSite) { // `site.barUrl` is a legal HTTP URL, which should be convertable to the `ParsedUrl` type. diff --git a/WordPress/Classes/Services/ApplicationPasswordService.swift b/WordPress/Classes/Services/ApplicationPasswordService.swift index 7289367b2fb7..cd5f70dcf88f 100644 --- a/WordPress/Classes/Services/ApplicationPasswordService.swift +++ b/WordPress/Classes/Services/ApplicationPasswordService.swift @@ -1,5 +1,6 @@ import Foundation import WordPressAPI +import WordPressCore @objc class ApplicationPasswordService: NSObject { @@ -11,14 +12,14 @@ import WordPressAPI self.currentUserId = currentUserId } - private func fetchTokens(forUserId userId: Int32) async throws -> [ApplicationPasswordWithEditContext] { + private func fetchTokens(forUserId userId: UserId) async throws -> [ApplicationPasswordWithEditContext] { try await apiClient.api.applicationPasswords.listWithEditContext(userId: userId).data } } extension ApplicationPasswordService: ApplicationTokenListDataProvider { func loadApplicationTokens() async throws -> [ApplicationTokenItem] { - try await fetchTokens(forUserId: Int32(currentUserId)) + try await fetchTokens(forUserId: UserId(currentUserId)) .compactMap(ApplicationTokenItem.init) } } diff --git a/WordPress/Classes/Users/Components/UserListItem.swift b/WordPress/Classes/Users/Components/UserListItem.swift index 42b34c71f996..39e8081e2bcf 100644 --- a/WordPress/Classes/Users/Components/UserListItem.swift +++ b/WordPress/Classes/Users/Components/UserListItem.swift @@ -1,4 +1,5 @@ import SwiftUI +import WordPressCore struct UserListItem: View { @@ -31,5 +32,5 @@ struct UserListItem: View { } #Preview { - UserListItem(user: DisplayUser.MockUser, isCurrentUser: true, userService: MockUserProvider(), applicationTokenListDataProvider: StaticTokenProvider(tokens: .success(.testTokens))) + UserListItem(user: DisplayUser.mockUser, isCurrentUser: true, userService: MockUserProvider(), applicationTokenListDataProvider: StaticTokenProvider(tokens: .success(.testTokens))) } diff --git a/WordPress/Classes/Users/UserProvider.swift b/WordPress/Classes/Users/UserProvider.swift index 4afeee6957dc..ba259bd2fae3 100644 --- a/WordPress/Classes/Users/UserProvider.swift +++ b/WordPress/Classes/Users/UserProvider.swift @@ -1,50 +1,7 @@ -import Foundation -import Combine +import WordPressAPI +import WordPressCore -public protocol UserDataStore: DataStore where T == DisplayUser, Query == UserDataStoreQuery { -} - -public enum UserDataStoreQuery: Equatable { - case all - case id(Set) - case search(String) -} - -public protocol UserServiceProtocol: Actor { - func fetchUsers() async throws - - func isCurrentUserCapableOf(_ capability: String) async -> Bool - - func setNewPassword(id: Int32, newPassword: String) async throws - - func deleteUser(id: Int32, reassigningPostsTo newUserId: Int32) async throws - - func allUsers() async throws -> [DisplayUser] - - func streamSearchResult(input: String) async -> AsyncStream> - - func streamAll() async -> AsyncStream> -} - -protocol UserDataStoreProvider: Actor { - var userDataStore: any UserDataStore { get } -} - -extension UserServiceProtocol where Self: UserDataStoreProvider { - func allUsers() async throws -> [DisplayUser] { - try await userDataStore.list(query: .all) - } - - func streamSearchResult(input: String) async -> AsyncStream> { - await userDataStore.listStream(query: .search(input)) - } - - func streamAll() async -> AsyncStream> { - await userDataStore.listStream(query: .all) - } -} - -actor MockUserProvider: UserServiceProtocol, UserDataStoreProvider { +actor MockUserProvider: UserServiceProtocol { enum Scenario { case infinitLoading @@ -54,8 +11,7 @@ actor MockUserProvider: UserServiceProtocol, UserDataStoreProvider { var scenario: Scenario - private let _dataStore: InMemoryUserDataStore = .init() - var userDataStore: any UserDataStore { _dataStore } + private let userDataStore: InMemoryUserDataStore = .init() nonisolated let usersUpdates: AsyncStream<[DisplayUser]> private let usersUpdatesContinuation: AsyncStream<[DisplayUser]>.Continuation @@ -89,15 +45,27 @@ actor MockUserProvider: UserServiceProtocol, UserDataStoreProvider { } } + func allUsers() async throws -> [DisplayUser] { + try await userDataStore.list(query: .all) + } + + func streamSearchResult(input: String) async -> AsyncStream> { + await userDataStore.listStream(query: .search(input)) + } + + func streamAll() async -> AsyncStream> { + await userDataStore.listStream(query: .all) + } + func isCurrentUserCapableOf(_ capability: String) async -> Bool { true } - func setNewPassword(id: Int32, newPassword: String) async throws { + func setNewPassword(id: UserId, newPassword: String) async throws { // Not used in Preview } - func deleteUser(id: Int32, reassigningPostsTo newUserId: Int32) async throws { + func deleteUser(id: UserId, reassigningPostsTo newUserId: UserId) async throws { // Not used in Preview } } diff --git a/WordPress/Classes/Users/ViewModel/UserDeleteViewModel.swift b/WordPress/Classes/Users/ViewModel/UserDeleteViewModel.swift index 78866f1ce64a..ce9a41fbf8ca 100644 --- a/WordPress/Classes/Users/ViewModel/UserDeleteViewModel.swift +++ b/WordPress/Classes/Users/ViewModel/UserDeleteViewModel.swift @@ -1,4 +1,5 @@ import SwiftUI +import WordPressCore @MainActor public class UserDeleteViewModel: ObservableObject { diff --git a/WordPress/Classes/Users/ViewModel/UserDetailViewModel.swift b/WordPress/Classes/Users/ViewModel/UserDetailViewModel.swift index 064404230afb..2f9884ce350a 100644 --- a/WordPress/Classes/Users/ViewModel/UserDetailViewModel.swift +++ b/WordPress/Classes/Users/ViewModel/UserDetailViewModel.swift @@ -1,4 +1,5 @@ import SwiftUI +import WordPressCore @MainActor class UserDetailViewModel: ObservableObject { diff --git a/WordPress/Classes/Users/ViewModel/UserListViewModel.swift b/WordPress/Classes/Users/ViewModel/UserListViewModel.swift index 132dbd90f54c..fbf7a09abe21 100644 --- a/WordPress/Classes/Users/ViewModel/UserListViewModel.swift +++ b/WordPress/Classes/Users/ViewModel/UserListViewModel.swift @@ -2,6 +2,7 @@ import SwiftUI import Combine import WordPressShared import WordPressAPI +import WordPressCore @MainActor class UserListViewModel: ObservableObject { diff --git a/WordPress/Classes/Users/Views/DeleteUserConfirmationSheet.swift b/WordPress/Classes/Users/Views/DeleteUserConfirmationSheet.swift index ff1943785d59..149c987c6da8 100644 --- a/WordPress/Classes/Users/Views/DeleteUserConfirmationSheet.swift +++ b/WordPress/Classes/Users/Views/DeleteUserConfirmationSheet.swift @@ -1,5 +1,6 @@ import Foundation import SwiftUI +import WordPressCore struct DeleteUserConfirmationSheet: View { let user: DisplayUser @@ -100,5 +101,5 @@ struct DeleteUserConfirmationSheet: View { } #Preview { - DeleteUserConfirmationSheet(user: .MockUser, deleteUserViewModel: .init(user: .MockUser, userService: MockUserProvider()), didTapDeleteButton: { }) + DeleteUserConfirmationSheet(user: .mockUser, deleteUserViewModel: .init(user: .mockUser, userService: MockUserProvider()), didTapDeleteButton: { }) } diff --git a/WordPress/Classes/Users/Views/UserDetailsView.swift b/WordPress/Classes/Users/Views/UserDetailsView.swift index 2785de5fd482..8e37ca99b1cd 100644 --- a/WordPress/Classes/Users/Views/UserDetailsView.swift +++ b/WordPress/Classes/Users/Views/UserDetailsView.swift @@ -1,5 +1,6 @@ import SwiftUI import WordPressAPI +import WordPressCore struct UserDetailsView: View { @@ -312,6 +313,6 @@ private extension String { #Preview { NavigationStack { - UserDetailsView(user: DisplayUser.MockUser, isCurrentUser: true, userService: MockUserProvider(), applicationTokenListDataProvider: StaticTokenProvider(tokens: .success(.testTokens))) + UserDetailsView(user: .mockUser, isCurrentUser: true, userService: MockUserProvider(), applicationTokenListDataProvider: StaticTokenProvider(tokens: .success(.testTokens))) } } diff --git a/WordPress/Classes/Users/Views/UserListView.swift b/WordPress/Classes/Users/Views/UserListView.swift index 5ffa35c7eb79..a3160a203aa3 100644 --- a/WordPress/Classes/Users/Views/UserListView.swift +++ b/WordPress/Classes/Users/Views/UserListView.swift @@ -1,5 +1,6 @@ import SwiftUI import WordPressUI +import WordPressCore public struct UserListView: View { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift index ea6aba82ffe2..c83e8b9bd9a8 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import CoreData import WordPressKit +import WordPressCore enum DashboardSection: Int, CaseIterable, Sendable { case migrationSuccess diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+SectionHelpers.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+SectionHelpers.swift index 3f0dceb66f76..25bed1e26195 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+SectionHelpers.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+SectionHelpers.swift @@ -3,6 +3,7 @@ import UIKit import SwiftUI import WordPressUI import WordPressAPI +import WordPressCore extension Array where Element: BlogDetailsSection { fileprivate func findSectionIndex(of category: BlogDetailsSectionCategory) -> Int? { @@ -199,7 +200,7 @@ struct ApplicationPasswordRequiredView: View { // Modify the `site` variable to display the intended feature. self.site = try .init(baseUrl: ParsedUrl.parse(input: success.siteUrl), type: .selfHosted(username: success.userLogin, authToken: success.password)) - } catch let error as WordPressLoginClient.Error { + } catch let error as WordPressLoginClientError { if let message = error.errorMessage { Notice(title: message).post() } diff --git a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift index 88031b7a3a9d..443d24e74aee 100644 --- a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift @@ -206,11 +206,6 @@ class MeViewController: UITableViewController { isLoading: sharePresenter.isLoading, action: displayShareFlow() ), - ButtonRow( - title: Strings.submitFeedback, - textAlignment: .left, - action: showFeedbackView() - ), ButtonRow( title: RowTitles.about, textAlignment: .left, @@ -312,15 +307,6 @@ class MeViewController: UITableViewController { } } - func showFeedbackView() -> ImmuTableAction { - return { [weak self] row in - defer { - self?.tableView.deselectSelectedRowWithAnimation(true) - } - self?.present(SubmitFeedbackViewController(source: "me_menu"), animated: true) - } - } - func displayShareFlow() -> ImmuTableAction { return { [unowned self] row in defer { diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 466d07ece773..1d8ff3ab9d6a 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -292,6 +292,12 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor func showFeedbackView() { self.present(SubmitFeedbackViewController(source: "gutenberg_kit", feedbackPrefix: "Editor"), animated: true) } + + func logException(_ exception: GutenbergJSException, with callback: @escaping () -> Void) { + DispatchQueue.main.async { + WordPressAppDelegate.crashLogging?.logJavaScriptException(exception, callback: callback) + } + } } extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate { @@ -331,6 +337,12 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate gutenbergDidRequestToggleUndoButton(!state.hasUndo) } + func editor(_ viewController: GutenbergKit.EditorViewController, didLogException error: GutenbergKit.GutenbergJSException) { + logException(error) { + // Do nothing + } + } + func editor(_ viewController: GutenbergKit.EditorViewController, performRequest: GutenbergKit.EditorNetworkRequest) async throws -> GutenbergKit.EditorNetworkResponse { throw URLError(.unknown) } @@ -807,3 +819,7 @@ extension NewGutenbergViewController { }) } } + +// Extend Gutenberg JavaScript exception struct to conform the protocol defined in the Crash Logging service +extension GutenbergJSException.StacktraceLine: @retroactive AutomatticTracks.JSStacktraceLine {} +extension GutenbergJSException: @retroactive AutomatticTracks.JSException {} diff --git a/WordPress/Jetpack/Resources/AppStoreStrings.po b/WordPress/Jetpack/Resources/AppStoreStrings.po index 5fa5c1179c39..98713c78262e 100644 --- a/WordPress/Jetpack/Resources/AppStoreStrings.po +++ b/WordPress/Jetpack/Resources/AppStoreStrings.po @@ -81,13 +81,15 @@ msgctxt "app_store_keywords" msgid "social,notes,jetpack,writing,geotagging,media,blog,website,blogging,journal" msgstr "" -msgctxt "v25.6-whats-new" +msgctxt "v25.7-whats-new" msgid "" -"This update:\n" -"- Fixes some UI issues on iPad.\n" -"- Adds the media ID and URL to each media item\n" -"- Uses the new Gravatar Quick Editor to update your avatar.\n" -"- Uses a web-based approach for WordPress.com login. This should be more reliable for most users, but please contact support if you have any trouble!\n" +"For this month's release, we've:\n" +"- Added support for Image Playgrounds! Use it to add images to your posts, generate a featured image, or site icon.\n" +"- Added undo and redo to the experimental editor (have you tried the editor yet? if not, you should!)\n" +"- Made the Gravatar Quick Editor a bit nicer.\n" +"- Added a shortcut for changing the featured image for a post.\n" +"- Improved how images in your media library are displayed – it should be a little nicer now.\n" +"- Done the usual bug fixes and performance improvements (mostly in the editor)\n" msgstr "" #. translators: This is a promo message that will be attached on top of the first screenshot in the App Store. diff --git a/WordPress/Jetpack/Resources/release_notes.txt b/WordPress/Jetpack/Resources/release_notes.txt index 10229352c45c..1d1a5038f3e8 100644 --- a/WordPress/Jetpack/Resources/release_notes.txt +++ b/WordPress/Jetpack/Resources/release_notes.txt @@ -1,48 +1,7 @@ -* [**] Add Image Playground support (part of Apple Intelligence suite) for adding images to your posts, generated featured image, site icons, and more [#23688] -* [**] Enable history navigation (undo and redo) for the experimental editor. [#23961] -* [**] Various bug fixes and improvements in the new experimental editor [#23919] -* [**] Enhance the Gravatar Quick Editor by adding features that allow users to delete and share their avatars. [#23868] -* [**] Add new lightbox screen for images with modern transitions and enhanced performance [#23922] -* [**] Add the capability to create an avatar using Apple Image Playground through the Gravatar Quick Editor. [#23868] -* [*] Fix sorting in Stats Subscriber Emails (latest first) [#23913] -* [*] Reader: Fix a couple of rare crashes in Reader [#23907] -* [*] Add prefetching to Reader streams [#23928] -* [*] Reader: The post cover now uses the standard aspect ratio for covers, so there is no jumping. There are also a few minor improvements to the layout and animations of the cover and the header [#23897, #23909] -* [*] Reader: Move the "Reading Preferences" button to the "More" menu [#23897] -* [*] Reader: Fix an issue with empty state views being non-scrollable in streams [#23908] -* [*] Reader: Hide post toolbar when reading an article and fix like button animations [#23909] -* [*] Reader: Fix off-by-one error in post details like counter when post is liked by you [#23912] -* [*] Fix an issue with blogging reminders prompt not being shown after publishing a new post [#23930] -* [*] Fix transitions in Blogging Reminders flow, improve accessibility, add close buttons [#23931] -* [*] Fix an issue with compliance popover not dismissing for self-hosted site [#23932] -* [*] Fix dynamic type support in the compliance popover [#23932] -* [*] Improve transisions and interactive dismiss gestures for sheets [#23933] -* [*] Add "Share" action to site link context menu on dashboard [#23935] -* [*] Fix layout issues in Privacy Settings section of App Settings [#23936] -* [*] Fix incorrect chevron icons direction in RTL languages [#23940] -* [*] Fix an issue with clear navigation bar background in revision browser [#23941] -* [*] Fix an issue with comments being lost on request failure [#23942] -* [*] Fix an issue with Referrers in Stats showing invalid icons [#23943] -* [*] Integrate zoom transitions in Themes, Reader [#23945, #23947] -* [*] Fix an issue with site icons cropped in share extensions [#23950] -* [*] Show selected filter in the Discover navigation bar [#23956] -* [*] Enable fast deceleration for filters on the Discover tab [#23954] -* [*] Disable universal links support for QR code login. You can only scan the codes using the app now. [#23953] -* [*] Add scroll-to-top button to Reader streams [#23957] -* [*] Add a quick way to replace a featured image for a post [#23962] -* [*] Fix an issue with posts in Reader sometimes showing incorrect covers [#23914] -* [*] Fix non-stable order in Posts and Pages section in Stats [#23915] -* [*] (P2) Reader: Fix an issue with a missing "Mark as Read/Unread" button that was removed in the previous release [#23917] -* [*] (P2) Reader: Show "read" status for P2 posts in the feeds [#23917] -* [*] Fix some missing or invalid social sharing icons [#23918] -* [*] Fix small tap area for “More” button in the Subscriptions screen in Reader [#23964] -* [*] Fix “Notification Settings” for individual posts sometimes being clipped [#23964] -* [*] Add context menu (long-press) and previews for subscriptions with quick access to “Share”, “Copy Link”, “Add to Favorites”, “Notification Settings”, and “Unsubscribe” buttons in both the Subscriptions view and the Sidebar in Reader [#23964] -* [*] Fix an issue with fullscreen button in reply view clipped by the notch [#23965] -* [*] Remove "Lazy Images" option that is no longer part of the Jetpack plugin [#23966] -* [*] Fix an issue with "Speed up your site" section not refreshing (fails silently) [#23966] -* [*] Fix an issue with small tap area of the ellipsis in the post list [#23973] -* [*] Avoid unexpectedly marking post content as unsaved. [#23969] -* [*] Fix an issue with Mastodon connection not working [#23981] -* [**] Send feedback from within the experimental editor "more" options menu. [#23980] -* [**] Support accessing the code editor within the experimental editor. [#23979] \ No newline at end of file +For this month's release, we've: +- Added support for Image Playgrounds! Use it to add images to your posts, generate a featured image, or site icon. +- Added undo and redo to the experimental editor (have you tried the editor yet? if not, you should!) +- Made the Gravatar Quick Editor a bit nicer. +- Added a shortcut for changing the featured image for a post. +- Improved how images in your media library are displayed – it should be a little nicer now. +- Done the usual bug fixes and performance improvements (mostly in the editor) diff --git a/WordPress/Resources/AppStoreStrings.po b/WordPress/Resources/AppStoreStrings.po index 880ba7c93015..95cfdc6b43f9 100644 --- a/WordPress/Resources/AppStoreStrings.po +++ b/WordPress/Resources/AppStoreStrings.po @@ -45,13 +45,15 @@ msgctxt "app_store_keywords" msgid "blogger,writing,blogging,web,maker,online,store,business,make,create,write,blogs" msgstr "" -msgctxt "v25.6-whats-new" -msgid "" -"This update:\n" -"- Fixes some UI issues on iPad.\n" -"- Adds the media ID and URL to each media item\n" -"- Uses the new Gravatar Quick Editor to update your avatar.\n" -"- Uses a web-based approach for WordPress.com login. This should be more reliable for most users, but please contact support if you have any trouble!\n" +msgctxt "v25.7-whats-new" +msgid "" +"For this month's release, we've:\n" +"- Added support for Image Playgrounds! Use it to add images to your posts, generate a featured image, or site icon.\n" +"- Added undo and redo to the experimental editor (have you tried the editor yet? if not, you should!)\n" +"- Made the Gravatar Quick Editor a bit nicer.\n" +"- Added a shortcut for changing the featured image for a post.\n" +"- Improved how images in your media library are displayed – it should be a little nicer now.\n" +"- Done the usual bug fixes and performance improvements (mostly in the editor)\n" msgstr "" #. translators: This is a standard chunk of text used to tell a user what's new with a release when nothing major has changed. diff --git a/WordPress/Resources/release_notes.txt b/WordPress/Resources/release_notes.txt index 10229352c45c..1d1a5038f3e8 100644 --- a/WordPress/Resources/release_notes.txt +++ b/WordPress/Resources/release_notes.txt @@ -1,48 +1,7 @@ -* [**] Add Image Playground support (part of Apple Intelligence suite) for adding images to your posts, generated featured image, site icons, and more [#23688] -* [**] Enable history navigation (undo and redo) for the experimental editor. [#23961] -* [**] Various bug fixes and improvements in the new experimental editor [#23919] -* [**] Enhance the Gravatar Quick Editor by adding features that allow users to delete and share their avatars. [#23868] -* [**] Add new lightbox screen for images with modern transitions and enhanced performance [#23922] -* [**] Add the capability to create an avatar using Apple Image Playground through the Gravatar Quick Editor. [#23868] -* [*] Fix sorting in Stats Subscriber Emails (latest first) [#23913] -* [*] Reader: Fix a couple of rare crashes in Reader [#23907] -* [*] Add prefetching to Reader streams [#23928] -* [*] Reader: The post cover now uses the standard aspect ratio for covers, so there is no jumping. There are also a few minor improvements to the layout and animations of the cover and the header [#23897, #23909] -* [*] Reader: Move the "Reading Preferences" button to the "More" menu [#23897] -* [*] Reader: Fix an issue with empty state views being non-scrollable in streams [#23908] -* [*] Reader: Hide post toolbar when reading an article and fix like button animations [#23909] -* [*] Reader: Fix off-by-one error in post details like counter when post is liked by you [#23912] -* [*] Fix an issue with blogging reminders prompt not being shown after publishing a new post [#23930] -* [*] Fix transitions in Blogging Reminders flow, improve accessibility, add close buttons [#23931] -* [*] Fix an issue with compliance popover not dismissing for self-hosted site [#23932] -* [*] Fix dynamic type support in the compliance popover [#23932] -* [*] Improve transisions and interactive dismiss gestures for sheets [#23933] -* [*] Add "Share" action to site link context menu on dashboard [#23935] -* [*] Fix layout issues in Privacy Settings section of App Settings [#23936] -* [*] Fix incorrect chevron icons direction in RTL languages [#23940] -* [*] Fix an issue with clear navigation bar background in revision browser [#23941] -* [*] Fix an issue with comments being lost on request failure [#23942] -* [*] Fix an issue with Referrers in Stats showing invalid icons [#23943] -* [*] Integrate zoom transitions in Themes, Reader [#23945, #23947] -* [*] Fix an issue with site icons cropped in share extensions [#23950] -* [*] Show selected filter in the Discover navigation bar [#23956] -* [*] Enable fast deceleration for filters on the Discover tab [#23954] -* [*] Disable universal links support for QR code login. You can only scan the codes using the app now. [#23953] -* [*] Add scroll-to-top button to Reader streams [#23957] -* [*] Add a quick way to replace a featured image for a post [#23962] -* [*] Fix an issue with posts in Reader sometimes showing incorrect covers [#23914] -* [*] Fix non-stable order in Posts and Pages section in Stats [#23915] -* [*] (P2) Reader: Fix an issue with a missing "Mark as Read/Unread" button that was removed in the previous release [#23917] -* [*] (P2) Reader: Show "read" status for P2 posts in the feeds [#23917] -* [*] Fix some missing or invalid social sharing icons [#23918] -* [*] Fix small tap area for “More” button in the Subscriptions screen in Reader [#23964] -* [*] Fix “Notification Settings” for individual posts sometimes being clipped [#23964] -* [*] Add context menu (long-press) and previews for subscriptions with quick access to “Share”, “Copy Link”, “Add to Favorites”, “Notification Settings”, and “Unsubscribe” buttons in both the Subscriptions view and the Sidebar in Reader [#23964] -* [*] Fix an issue with fullscreen button in reply view clipped by the notch [#23965] -* [*] Remove "Lazy Images" option that is no longer part of the Jetpack plugin [#23966] -* [*] Fix an issue with "Speed up your site" section not refreshing (fails silently) [#23966] -* [*] Fix an issue with small tap area of the ellipsis in the post list [#23973] -* [*] Avoid unexpectedly marking post content as unsaved. [#23969] -* [*] Fix an issue with Mastodon connection not working [#23981] -* [**] Send feedback from within the experimental editor "more" options menu. [#23980] -* [**] Support accessing the code editor within the experimental editor. [#23979] \ No newline at end of file +For this month's release, we've: +- Added support for Image Playgrounds! Use it to add images to your posts, generate a featured image, or site icon. +- Added undo and redo to the experimental editor (have you tried the editor yet? if not, you should!) +- Made the Gravatar Quick Editor a bit nicer. +- Added a shortcut for changing the featured image for a post. +- Improved how images in your media library are displayed – it should be a little nicer now. +- Done the usual bug fixes and performance improvements (mostly in the editor) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 416831a84ab3..228f44b17036 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -434,7 +434,6 @@ 4A266B91282B13A70089CF3D /* CoreDataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A266B90282B13A70089CF3D /* CoreDataTestCase.swift */; }; 4A2C73E42A943DEA00ACE79E /* TaggedManagedObjectIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2C73E32A943DEA00ACE79E /* TaggedManagedObjectIDTests.swift */; }; 4A2C73F72A9585B000ACE79E /* PostRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2C73F62A9585B000ACE79E /* PostRepositoryTests.swift */; }; - 4A2ED63B2CF86A0E009E0821 /* DataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A2ED63A2CF86A0E009E0821 /* DataStoreTests.swift */; }; 4A5598852B05AC180083C220 /* PagesListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5598842B05AC180083C220 /* PagesListTests.swift */; }; 4A5EDD812CE58B9900A605FC /* ZendeskUtilsTests+A8CEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5EDD802CE58B9900A605FC /* ZendeskUtilsTests+A8CEmail.swift */; }; 4A690C152BA791B100A8E0C5 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 4A690C142BA790BC00A8E0C5 /* PrivacyInfo.xcprivacy */; }; @@ -2315,7 +2314,6 @@ 4A266B90282B13A70089CF3D /* CoreDataTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataTestCase.swift; sourceTree = ""; }; 4A2C73E32A943DEA00ACE79E /* TaggedManagedObjectIDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaggedManagedObjectIDTests.swift; sourceTree = ""; }; 4A2C73F62A9585B000ACE79E /* PostRepositoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostRepositoryTests.swift; sourceTree = ""; }; - 4A2ED63A2CF86A0E009E0821 /* DataStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreTests.swift; sourceTree = ""; }; 4A2F746A2C2198CB00E72117 /* WordPressAuthenticatorTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = WordPressAuthenticatorTests.xcconfig; sourceTree = ""; }; 4A5598842B05AC180083C220 /* PagesListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagesListTests.swift; sourceTree = ""; }; 4A5EDD802CE58B9900A605FC /* ZendeskUtilsTests+A8CEmail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZendeskUtilsTests+A8CEmail.swift"; sourceTree = ""; }; @@ -6016,7 +6014,6 @@ 0C896DE62A3A832B00D7D4E7 /* SiteVisibilityTests.swift */, 4AD862E42AFAEF1700A07557 /* PostsListAPIStub.swift */, 4A5598842B05AC180083C220 /* PagesListTests.swift */, - 4A2ED63A2CF86A0E009E0821 /* DataStoreTests.swift */, 93B853211B44165B0064FE72 /* Analytics */, DC06DFF727BD52A100969974 /* BackgroundTasks */, FE32E7EF284496F500744D80 /* Blogging Prompts */, @@ -10290,7 +10287,6 @@ 019D699E2A5EA963003B676D /* RootViewCoordinatorTests.swift in Sources */, B09879792B5730BC0048256D /* StatsTrafficDatePickerViewModelTests.swift in Sources */, D821C81B21003AE9002ED995 /* FormattableContentGroupTests.swift in Sources */, - 4A2ED63B2CF86A0E009E0821 /* DataStoreTests.swift in Sources */, 93D86B981C691E71003D8E3E /* LocalCoreDataServiceTests.m in Sources */, 0CD6299B2B9AAA9A00325EA4 /* Foundation+Extensions.swift in Sources */, 1DF7A0D32BA0B1810003CBA3 /* GutenbergContentParser.swift in Sources */, diff --git a/WordPress/WordPressTest/UserListViewModelTests.swift b/WordPress/WordPressTest/UserListViewModelTests.swift index 8c76eac16443..f0f79f2633c9 100644 --- a/WordPress/WordPressTest/UserListViewModelTests.swift +++ b/WordPress/WordPressTest/UserListViewModelTests.swift @@ -4,6 +4,8 @@ import OHHTTPStubs import OHHTTPStubsSwift import Combine import WordPressUI +import WordPressAPI +import WordPressCore @testable import WordPress diff --git a/WordPress/WordPressTest/WordPressUnitTests.xctestplan b/WordPress/WordPressTest/WordPressUnitTests.xctestplan index 83282331e1a3..7bb8d1aa36cf 100644 --- a/WordPress/WordPressTest/WordPressUnitTests.xctestplan +++ b/WordPress/WordPressTest/WordPressUnitTests.xctestplan @@ -34,15 +34,22 @@ { "target" : { "containerPath" : "container:..\/Modules", - "identifier" : "WordPressSharedTests", - "name" : "WordPressSharedTests" + "identifier" : "AsyncImageKitTests", + "name" : "AsyncImageKitTests" } }, { "target" : { "containerPath" : "container:..\/Modules", - "identifier" : "JetpackStatsWidgetsCoreTests", - "name" : "JetpackStatsWidgetsCoreTests" + "identifier" : "DesignSystemTests", + "name" : "DesignSystemTests" + } + }, + { + "target" : { + "containerPath" : "container:WordPress.xcodeproj", + "identifier" : "4AD953BA2C21451700D0EEFA", + "name" : "WordPressAuthenticatorTests" } }, { @@ -55,29 +62,36 @@ { "target" : { "containerPath" : "container:..\/Modules", - "identifier" : "WordPressMediaTests", - "name" : "WordPressMediaTests" + "identifier" : "WordPressCoreTests", + "name" : "WordPressCoreTests" } }, { "target" : { "containerPath" : "container:..\/Modules", - "identifier" : "WordPressFluxTests", - "name" : "WordPressFluxTests" + "identifier" : "WordPressUIUnitTests", + "name" : "WordPressUIUnitTests" } }, { "target" : { "containerPath" : "container:..\/Modules", - "identifier" : "DesignSystemTests", - "name" : "DesignSystemTests" + "identifier" : "WordPressSharedTests", + "name" : "WordPressSharedTests" } }, { "target" : { - "containerPath" : "container:WordPress.xcodeproj", - "identifier" : "4AD953BA2C21451700D0EEFA", - "name" : "WordPressAuthenticatorTests" + "containerPath" : "container:..\/Modules", + "identifier" : "JetpackStatsWidgetsCoreTests", + "name" : "JetpackStatsWidgetsCoreTests" + } + }, + { + "target" : { + "containerPath" : "container:..\/Modules", + "identifier" : "WordPressFluxTests", + "name" : "WordPressFluxTests" } }, {