From 21c36e4878dd7ec7d816532625e4aa5c3721b983 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 27 May 2024 11:29:17 +1000 Subject: [PATCH 01/30] Update required Xcode version to 15.4 --- .xcode-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.xcode-version b/.xcode-version index adbc6d2b1bde..232a7fc1a664 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -15.1 +15.4 From 7eb219e4042f214b763b8582743678226407cc4d Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 27 May 2024 13:16:00 +1000 Subject: [PATCH 02/30] Remove an `XCUIApplication` default parameter because of concurrency --- .../UITests/Utils/XCTest+Extensions.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/WordPress/UITests/Utils/XCTest+Extensions.swift b/WordPress/UITests/Utils/XCTest+Extensions.swift index 8b7a54d6dda3..63a6a235e343 100644 --- a/WordPress/UITests/Utils/XCTest+Extensions.swift +++ b/WordPress/UITests/Utils/XCTest+Extensions.swift @@ -7,11 +7,24 @@ extension XCTestCase { // which require running on the main thread. @MainActor public func setUpTestSuite( - for app: XCUIApplication = XCUIApplication(), + // It doesn't feel right to set app to nil by default, but we cannot set it to XCUIApplication() + // because of the main actor isolation requirement: + // + // > Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context; this is an error in Swift 6 + // + // Requiring every caller to pass an instance would be cumbersome DevEx, so here's the compromise. + for inputApp: XCUIApplication? = .none, removeBeforeLaunching: Bool = false, crashOnCoreDataConcurrencyIssues: Bool = true, - selectWPComSite: String? = nil + selectWPComSite: String? = .none ) { + let app: XCUIApplication + if inputApp == .none { + app = XCUIApplication() + } else { + app = inputApp! + } + // To ensure that the test starts with a new simulator launch each time app.terminate() super.setUp() @@ -41,7 +54,6 @@ extension XCTestCase { // Media permissions alert handler let alertButtonTitle = "Allow Access to All Photos" systemAlertHandler(alertTitle: "“WordPress” Would Like to Access Your Photos", alertButton: alertButtonTitle) - } public func takeScreenshotOfFailedTest() { From ba9f29247b2f6153412c22fdcb2162218ec9753c Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 7 Jun 2024 09:53:45 -0400 Subject: [PATCH 03/30] Remove deprecated _createRevision --- WordPress/Classes/Models/AbstractPost.h | 5 --- WordPress/Classes/Models/AbstractPost.m | 17 -------- .../ViewRelated/Post/PostEditor+Publish.swift | 2 +- .../PostSettingsViewController+Swift.swift | 2 +- .../PrepublishingViewController.swift | 2 +- .../WordPressTest/AbstractPostTest.swift | 14 +++---- .../WordPressTest/PostCoordinatorTests.swift | 40 +++++++++---------- .../PostRepositorySaveTests.swift | 20 +++++----- 8 files changed, 40 insertions(+), 62 deletions(-) diff --git a/WordPress/Classes/Models/AbstractPost.h b/WordPress/Classes/Models/AbstractPost.h index 47c21604d5fe..1bc2a3898883 100644 --- a/WordPress/Classes/Models/AbstractPost.h +++ b/WordPress/Classes/Models/AbstractPost.h @@ -68,11 +68,6 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { // Revision management - (AbstractPost *)createRevision; -/// A new version of `createRevision` that allows you to create revisions based -/// on other revisions. -/// -/// - warning: Work-in-progress (kahu-offline-mode) -- (AbstractPost *)_createRevision; - (void)deleteRevision; - (void)applyRevision; - (AbstractPost *)updatePostFrom:(AbstractPost *)revision; diff --git a/WordPress/Classes/Models/AbstractPost.m b/WordPress/Classes/Models/AbstractPost.m index 529648f0bf92..864bcd129c0a 100644 --- a/WordPress/Classes/Models/AbstractPost.m +++ b/WordPress/Classes/Models/AbstractPost.m @@ -97,23 +97,6 @@ - (AbstractPost *)cloneFrom:(AbstractPost *)source - (AbstractPost *)createRevision { - if ([self isRevision]) { - DDLogInfo(@"Post is already a revision, no need to create a new one"); - return self; - } - if (self.revision) { - DDLogInfo(@"Returning existing revision"); - return self.revision; - } - - AbstractPost *post = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(self.class) inManagedObjectContext:self.managedObjectContext]; - [post cloneFrom:self]; - [post setValue:self forKey:@"original"]; - [post setValue:nil forKey:@"revision"]; - return post; -} - -- (AbstractPost *)_createRevision { NSParameterAssert(self.revision == nil); AbstractPost *post = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(self.class) inManagedObjectContext:self.managedObjectContext]; diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift index 9bd0e1ebdc5e..818c21bfde1a 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift @@ -346,7 +346,7 @@ extension PublishingEditor { if !post.isUnsavedRevision && post.status != .trash { DDLogDebug("Creating new revision") - post = post._createRevision() + post = post.createRevision() } if loadAutosaveRevision { diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift index 37329c74cf55..c5cd36e507b2 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift @@ -17,7 +17,7 @@ extension PostSettingsViewController { } static func showStandaloneEditor(for post: AbstractPost, from presentingViewController: UIViewController) { - let revision = post._createRevision() + let revision = post.createRevision() let viewController = PostSettingsViewController.make(for: revision) viewController.isStandalone = true let navigation = UINavigationController(rootViewController: viewController) diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift index d0c9cea95e5a..e6990c6f6dfa 100644 --- a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift @@ -51,7 +51,7 @@ final class PrepublishingViewController: UIViewController, UITableViewDataSource // If presented from the editor, it make changes to the revision managed by // the editor. But for a standalone publishing sheet, it has to manage // its own revision. - self.post = isStandalone ? post._createRevision() : post + self.post = isStandalone ? post.createRevision() : post self.isStandalone = isStandalone self.viewModel = PrepublishingViewModel(post: self.post) self.uploadsViewModel = PostMediaUploadsViewModel(post: post) diff --git a/WordPress/WordPressTest/AbstractPostTest.swift b/WordPress/WordPressTest/AbstractPostTest.swift index 418f77af8824..787f609ab12d 100644 --- a/WordPress/WordPressTest/AbstractPostTest.swift +++ b/WordPress/WordPressTest/AbstractPostTest.swift @@ -44,13 +44,13 @@ class AbstractPostTest: CoreDataTestCase { XCTAssertNil(post.getLatestRevisionNeedingSync()) // GIVEN a post with a revision that doesn't need sync - let revision1 = post._createRevision() + let revision1 = post.createRevision() // THEN XCTAssertNil(post.getLatestRevisionNeedingSync()) // GIVEN a post with a revision that needs sync - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.remoteStatus = .syncNeeded // THEN @@ -60,9 +60,9 @@ class AbstractPostTest: CoreDataTestCase { func testDeleteSyncedRevisions() { // GIVEN a post with three revisions let post = PostBuilder(mainContext).build() - let revision1 = post._createRevision() - let revision2 = revision1._createRevision() - let revision3 = revision2._createRevision() + let revision1 = post.createRevision() + let revision2 = revision1.createRevision() + let revision3 = revision2.createRevision() // WHEN post.deleteSyncedRevisions(until: revision2) @@ -78,8 +78,8 @@ class AbstractPostTest: CoreDataTestCase { func testDeleteRevisionDeletsAll() { // GIVEN a post with two revisions let post = PostBuilder(mainContext).build() - let revision1 = post._createRevision() - let revision2 = revision1._createRevision() + let revision1 = post.createRevision() + let revision2 = revision1.createRevision() // WHEN post.deleteAllRevisions() diff --git a/WordPress/WordPressTest/PostCoordinatorTests.swift b/WordPress/WordPressTest/PostCoordinatorTests.swift index bbf399ebaebf..c1d2a0664ffa 100644 --- a/WordPress/WordPressTest/PostCoordinatorTests.swift +++ b/WordPress/WordPressTest/PostCoordinatorTests.swift @@ -37,7 +37,7 @@ class PostCoordinatorTests: CoreDataTestCase { post.status = .draft post.authorID = 29043 - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-b" revision1.content = "content-a" @@ -62,7 +62,7 @@ class PostCoordinatorTests: CoreDataTestCase { post.status = .draft post.authorID = 29043 - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-b" revision1.content = "content-a" @@ -100,7 +100,7 @@ class PostCoordinatorTests: CoreDataTestCase { post.status = .draft post.authorID = 29043 - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-a" revision1.content = "content-a" @@ -108,7 +108,7 @@ class PostCoordinatorTests: CoreDataTestCase { stub(condition: isPath("/rest/v1.2/sites/80511/posts/new")) { _ in XCTAssertFalse(Thread.isMainThread) DispatchQueue.main.sync { - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.postTitle = "title-b" self.coordinator.setNeedsSync(for: revision2) } @@ -164,7 +164,7 @@ class PostCoordinatorTests: CoreDataTestCase { // important otherwise MediaService will use temporary objectID and fail try mainContext.save() - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-b" revision1.media = [media] let uploadID = media.gutenbergUploadID @@ -203,7 +203,7 @@ class PostCoordinatorTests: CoreDataTestCase { post.status = .draft post.authorID = 29043 - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-b" revision1.content = "content-a" @@ -263,7 +263,7 @@ class PostCoordinatorTests: CoreDataTestCase { // important otherwise MediaService will use temporary objectID and fail try mainContext.save() - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-b" revision1.media = [media] let uploadID = media.gutenbergUploadID @@ -282,7 +282,7 @@ class PostCoordinatorTests: CoreDataTestCase { coordinator.setNeedsSync(for: revision1) await fulfillment(of: [expectation], timeout: 2) - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.media = [] revision2.content = "empty" @@ -309,7 +309,7 @@ class PostCoordinatorTests: CoreDataTestCase { post.postTitle = "title-b" post.content = "content-a" - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.content = "content-b" // GIVEN a server where the post was deleted @@ -342,7 +342,7 @@ class PostCoordinatorTests: CoreDataTestCase { post.status = .draft post.authorID = 29043 - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-b" revision1.content = "content-a" @@ -374,12 +374,12 @@ class PostCoordinatorTests: CoreDataTestCase { post.status = .draft post.authorID = 29043 - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-a" revision1.content = "content-a" // WHEN a slug was changes during the current editor session - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.wp_slug = "hello" // GIVEN @@ -433,7 +433,7 @@ class PostCoordinatorTests: CoreDataTestCase { post.bloggingPromptID = "prompt-a" // GIVEN an editor revision - let revision = post._createRevision() as! Post + let revision = post.createRevision() as! Post revision.publicizeMessage = "message-a" // GIVEN @@ -479,15 +479,15 @@ class PostCoordinatorTests: CoreDataTestCase { post.authorID = 29043 post.content = "content-a" - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.content = "content-b" revision1.remoteStatus = .syncNeeded - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.content = "content-c" revision2.remoteStatus = .syncNeeded - let revision3 = revision2._createRevision() + let revision3 = revision2.createRevision() revision3.content = "content-d" XCTAssertFalse(revision3.isSyncNeeded) @@ -521,11 +521,11 @@ class PostCoordinatorTests: CoreDataTestCase { post.authorID = 29043 post.content = "content-a" - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.content = "content-b" revision1.remoteStatus = .syncNeeded - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.content = "content-c" try mainContext.save() @@ -557,11 +557,11 @@ class PostCoordinatorTests: CoreDataTestCase { post.authorID = 29043 post.content = "content-a" - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.content = "content-b" revision1.remoteStatus = .syncNeeded - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.content = "content-c" try mainContext.save() diff --git a/WordPress/WordPressTest/PostRepositorySaveTests.swift b/WordPress/WordPressTest/PostRepositorySaveTests.swift index 6edc5340edbf..3b4f3caaa1f9 100644 --- a/WordPress/WordPressTest/PostRepositorySaveTests.swift +++ b/WordPress/WordPressTest/PostRepositorySaveTests.swift @@ -1083,16 +1083,16 @@ class PostRepositorySaveTests: CoreDataTestCase { } // GIVEN a post that has changes that need to be synced (across two local revision) - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-b" revision1.remoteStatus = .syncNeeded - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.postTitle = "title-c" revision2.remoteStatus = .syncNeeded // GIVEN a revision created by an editor - let revision3 = revision2._createRevision() + let revision3 = revision2.createRevision() revision3.postTitle = "title-d" // GIVEN a server where the post was deleted @@ -1165,11 +1165,11 @@ class PostRepositorySaveTests: CoreDataTestCase { } // GIVEN a post that has changes that need to be synced (across two local revision) - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.content = "content-b" revision1.remoteStatus = .syncNeeded - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.content = "content-c" // GIVEN a server where the post was deleted @@ -1282,11 +1282,11 @@ class PostRepositorySaveTests: CoreDataTestCase { } // GIVEN a change that was reverted in a more recent revision - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-b" revision1.remoteStatus = .syncNeeded - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.postTitle = "title-a" revision2.remoteStatus = .syncNeeded @@ -1319,13 +1319,13 @@ class PostRepositorySaveTests: CoreDataTestCase { } // GIVEN a saved revision - let revision1 = post._createRevision() + let revision1 = post.createRevision() revision1.postTitle = "title-a" revision1.content = "content-a" revision1.remoteStatus = .syncNeeded // GIVEN a local revision - let revision2 = revision1._createRevision() + let revision2 = revision1.createRevision() revision2.postTitle = "title-b" // GIVEN a server accepting the new post @@ -1391,7 +1391,7 @@ class PostRepositorySaveTests: CoreDataTestCase { $0.content = "content-a" } - let revision = post._createRevision() + let revision = post.createRevision() revision.content = "content-b" // GIVEN From b743790e16379c50c4fdd0b941cb309cba56d7f3 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 7 Jun 2024 12:31:14 -0400 Subject: [PATCH 04/30] Remove deprecated statuses/strings --- WordPress/Classes/Models/BasePost.swift | 7 ------- WordPress/Classes/Services/PostRepository.swift | 5 +++-- .../Classes/ViewRelated/Post/PostListFilter.swift | 11 ----------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/WordPress/Classes/Models/BasePost.swift b/WordPress/Classes/Models/BasePost.swift index 7a37f561de1d..4f9cd8b383c6 100644 --- a/WordPress/Classes/Models/BasePost.swift +++ b/WordPress/Classes/Models/BasePost.swift @@ -75,10 +75,3 @@ extension BasePost { return nil } } - -extension Sequence where Iterator.Element == BasePost.Status { - /// - warning: deprecated (kahu-offline-mode) - var strings: [String] { - return map({ $0.rawValue }) - } -} diff --git a/WordPress/Classes/Services/PostRepository.swift b/WordPress/Classes/Services/PostRepository.swift index 45a60ae63364..803d533f9671 100644 --- a/WordPress/Classes/Services/PostRepository.swift +++ b/WordPress/Classes/Services/PostRepository.swift @@ -640,8 +640,9 @@ extension PostRepository { throw PostRepository.Error.remoteAPIUnavailable } + let statuses = statuses?.map { $0.rawValue } let options = PostRepositoryPostsSerivceRemoteOptions(options: .init( - statuses: statuses?.strings, + statuses: statuses, number: range.count, offset: range.lowerBound, order: descending ? .descending : .ascending, @@ -656,7 +657,7 @@ extension PostRepository { let updatedPosts = PostHelper.merge( remotePosts, ofType: postType, - withStatuses: statuses?.strings, + withStatuses: statuses, byAuthor: authorUserID, for: try context.existingObject(with: blogID), purgeExisting: deleteOtherLocalPosts, diff --git a/WordPress/Classes/ViewRelated/Post/PostListFilter.swift b/WordPress/Classes/ViewRelated/Post/PostListFilter.swift index 0b5432b700a0..b5b3ad1a5c4e 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListFilter.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListFilter.swift @@ -19,17 +19,6 @@ import Foundation @objc var title: String @objc var accessibilityIdentifier: String = "" - /// For Obj-C compatibility only - @objc(statuses) - var statusesStrings: [String] { - get { - return statuses.strings - } - set { - statuses = newValue.compactMap({ BasePost.Status(rawValue: $0) }) - } - } - init(title: String, filterType: Status, predicate: NSPredicate, statuses: [BasePost.Status]) { hasMore = false From 0ef363a5727d0ce63112700f64173591710df443 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 7 Jun 2024 12:35:14 -0400 Subject: [PATCH 05/30] Remove deprecated code --- .../Models/AbstractPost+Searchable.swift | 4 +- WordPress/Classes/Models/AbstractPost.h | 5 -- WordPress/Classes/Models/AbstractPost.m | 6 --- WordPress/Classes/Models/AbstractPost.swift | 51 ------------------- WordPress/Classes/Services/PostHelper.m | 2 - WordPress/WordPressTest/PostTests.swift | 29 ----------- 6 files changed, 2 insertions(+), 95 deletions(-) diff --git a/WordPress/Classes/Models/AbstractPost+Searchable.swift b/WordPress/Classes/Models/AbstractPost+Searchable.swift index aa7abea569c9..45e6699c0107 100644 --- a/WordPress/Classes/Models/AbstractPost+Searchable.swift +++ b/WordPress/Classes/Models/AbstractPost+Searchable.swift @@ -76,9 +76,9 @@ fileprivate extension AbstractPost { title = postTitle } - guard status != .publish, let statusTitle = statusTitle else { + guard status != .publish, let status else { return title } - return "[\(statusTitle)] \(title)" + return "[\(AbstractPost.title(for: status))] \(title)" } } diff --git a/WordPress/Classes/Models/AbstractPost.h b/WordPress/Classes/Models/AbstractPost.h index 1bc2a3898883..6eb9fc0540fe 100644 --- a/WordPress/Classes/Models/AbstractPost.h +++ b/WordPress/Classes/Models/AbstractPost.h @@ -60,8 +60,6 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { /// Used to deduplicate new posts @property (nonatomic, strong, nullable) NSUUID *foreignID; -/// - warning: deprecated (kahu-offline-mode) -@property (nonatomic, strong, nullable) NSString *confirmedChangesHash; @property (nonatomic, strong, nullable) NSDate *confirmedChangesTimestamp; @property (nonatomic, strong, nullable) NSString *voiceContent; @@ -83,9 +81,6 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { - (BOOL)hasCategories; - (BOOL)hasTags; -/// - note: deprecated (kahu-offline-mode) -@property (nonatomic, assign, readonly) BOOL isFailed; - @property (nonatomic, assign, readonly) BOOL hasFailedMedia; /** diff --git a/WordPress/Classes/Models/AbstractPost.m b/WordPress/Classes/Models/AbstractPost.m index 864bcd129c0a..f38b0fea55bb 100644 --- a/WordPress/Classes/Models/AbstractPost.m +++ b/WordPress/Classes/Models/AbstractPost.m @@ -15,7 +15,6 @@ @implementation AbstractPost @dynamic comments; @dynamic featuredImage; @dynamic revisions; -@dynamic confirmedChangesHash; @dynamic confirmedChangesTimestamp; @dynamic autoUploadAttemptsCount; @dynamic autosaveContent; @@ -213,11 +212,6 @@ - (BOOL)hasVideo return NO; } -- (BOOL)isFailed -{ - return self.remoteStatus == AbstractPostRemoteStatusFailed || [[MediaCoordinator shared] hasFailedMediaFor:self] || self.hasFailedMedia; -} - - (BOOL)hasFailedMedia { if ([self.media count] == 0) { diff --git a/WordPress/Classes/Models/AbstractPost.swift b/WordPress/Classes/Models/AbstractPost.swift index 78938528f6e8..fdc9fbffcff9 100644 --- a/WordPress/Classes/Models/AbstractPost.swift +++ b/WordPress/Classes/Models/AbstractPost.swift @@ -33,16 +33,6 @@ extension AbstractPost { statuses.contains(status ?? .draft) } - /// - note: deprecated (kahu-offline-mode) - @objc - var statusTitle: String? { - guard let status = self.status else { - return nil - } - - return AbstractPost.title(for: status) - } - @objc var remoteStatus: AbstractPostRemoteStatus { get { @@ -59,47 +49,6 @@ extension AbstractPost { } } - /// The status of self when we last received its data from the API. - /// - /// This is mainly used to identify which Post List tab should the post be shown in. For - /// example, if a published post is transitioned to a draft but the app has not finished - /// updating the server yet, we will continue to show the post in the Published list instead of - /// the Drafts list. We believe this behavior is less confusing for the user. - /// - /// This is not meant to be up to date with the remote API. Eventually, this information will - /// be outdated. For example, the user could have changed the status in the web while the device - /// was offline. So we wouldn't recommend using this value aside from its original intention. - /// - /// - SeeAlso: PostService - /// - SeeAlso: PostListFilter - /// - /// - note: deprecated (kahu-offline-mode) - var statusAfterSync: Status? { - get { - return rawValue(forKey: "statusAfterSync") - } - set { - setRawValue(newValue, forKey: "statusAfterSync") - } - } - - /// The string value of `statusAfterSync` based on `BasePost.Status`. - /// - /// This should only be used in Objective-C. For Swift, use `statusAfterSync`. - /// - /// - SeeAlso: statusAfterSync - /// - /// - note: deprecated (kahu-offline-mode) - @objc(statusAfterSync) - var statusAfterSyncString: String? { - get { - return statusAfterSync?.rawValue - } - set { - statusAfterSync = newValue.flatMap { Status(rawValue: $0) } - } - } - static func title(for status: Status) -> String { return title(forStatus: status.rawValue) } diff --git a/WordPress/Classes/Services/PostHelper.m b/WordPress/Classes/Services/PostHelper.m index b48cccd2a37e..15716846bbf1 100644 --- a/WordPress/Classes/Services/PostHelper.m +++ b/WordPress/Classes/Services/PostHelper.m @@ -99,8 +99,6 @@ + (void)updatePost:(AbstractPost *)post withRemotePost:(RemotePost *)remotePost postPost.publicizeMessageID = publicizeMessageID; postPost.disabledPublicizeConnections = [self disabledPublicizeConnectionsForPost:post andMetadata:remotePost.metadata]; } - - post.statusAfterSync = post.status; } + (void)updatePost:(Post *)post withRemoteCategories:(NSArray *)remoteCategories inContext:(NSManagedObjectContext *)managedObjectContext { diff --git a/WordPress/WordPressTest/PostTests.swift b/WordPress/WordPressTest/PostTests.swift index dc80fd6ed807..5361e6dde4cd 100644 --- a/WordPress/WordPressTest/PostTests.swift +++ b/WordPress/WordPressTest/PostTests.swift @@ -302,35 +302,6 @@ class PostTests: CoreDataTestCase { XCTAssertTrue(post.canEditPublicizeSettings()) } - /// Confidence check for the string setter of `Post.statusAfterSync` - func testStatusAfterSyncStringTranslatesToEnumValues() { - // Arrange - let post = newTestPost() - XCTAssertNil(post.statusAfterSync) - XCTAssertNil(post.statusAfterSyncString) - - // Act - post.statusAfterSyncString = "draft" - - // Assert - XCTAssertEqual(post.statusAfterSync, BasePost.Status.draft) - XCTAssertEqual(post.statusAfterSyncString, "draft") - } - - /// Confidence check for the string setter of `Post.statusAfterSync` - func testStatusAfterSyncStringSetterGraciouslyHandlesInvalidValues() { - // Arrange - let post = newTestPost() - XCTAssertNil(post.statusAfterSync) - - // Act - post.statusAfterSyncString = "invalid value" - - // Assert - XCTAssertNil(post.statusAfterSync) - XCTAssertNil(post.statusAfterSyncString) - } - func testCountLocalDrafts() { let blog = BlogBuilder(mainContext).build() let _ = blog.createDraftPost() From 8786b3563c3bb805dc10a22fc803f8fc221896eb Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 7 Jun 2024 12:41:24 -0400 Subject: [PATCH 06/30] Remove deprecated code --- WordPress/Classes/Models/AbstractPost.h | 27 ++++------------------- WordPress/Classes/Models/AbstractPost.m | 17 -------------- WordPress/WordPressTest/PostBuilder.swift | 11 --------- 3 files changed, 4 insertions(+), 51 deletions(-) diff --git a/WordPress/Classes/Models/AbstractPost.h b/WordPress/Classes/Models/AbstractPost.h index 6eb9fc0540fe..41ed30bc1b4d 100644 --- a/WordPress/Classes/Models/AbstractPost.h +++ b/WordPress/Classes/Models/AbstractPost.h @@ -15,7 +15,7 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { AbstractPostRemoteStatusPushingMedia, // Push Media AbstractPostRemoteStatusAutoSaved, // Post remote auto-saved - // All the previous states were deprecated in 24.7 and are no longer used + // All the previous states were deprecated in 24.9 and are no longer used // by the app. To get the status of the uploads, use `PostCoordinator`. /// The default state of the newly created local revision. @@ -35,22 +35,12 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { @property (nonatomic, strong) NSSet *comments; @property (nonatomic, strong, nullable) Media *featuredImage; -// By convention these should be treated as read only and not manually set. -// These are primarily used as helpers sorting fetchRequests. -@property (nonatomic, assign) BOOL metaIsLocal; -@property (nonatomic, assign) BOOL metaPublishImmediately; -/** - This array will contain a list of revision IDs. - */ +/// This array will contain a list of revision IDs. @property (nonatomic, strong, nullable) NSArray *revisions; -/** - The default value of autoUploadAttemptsCount is 0. -*/ +/// The default value of autoUploadAttemptsCount is 0. @property (nonatomic, strong, nonnull) NSNumber *autoUploadAttemptsCount; -/** - Autosave attributes hold a snapshot of the post's content. - */ +/// Autosave attributes hold a snapshot of the post's content. @property (nonatomic, copy, nullable) NSString *autosaveContent; @property (nonatomic, copy, nullable) NSString *autosaveExcerpt; @property (nonatomic, copy, nullable) NSString *autosaveTitle; @@ -91,7 +81,6 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { - (BOOL)hasRevision; #pragma mark - Conveniece Methods -/// - note: deprecated (kahu-offline-mode) - (BOOL)shouldPublishImmediately; - (NSString *)authorNameForDisplay; - (NSString *)blavatarForDisplay; @@ -120,16 +109,8 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) { /** Returns YES if the original post is a draft */ -/// - note: deprecated (kahu-offline-mode) - (BOOL)originalIsDraft; -/** - Returns YES if dateCreated is nil, or if dateCreated and dateModified are equal. - Used when determining if a post should publish immediately. - */ -/// - note: deprecated (kahu-offline-mode) -- (BOOL)dateCreatedIsNilOrEqualToDateModified; - // Does the post exist on the blog? - (BOOL)hasRemote; diff --git a/WordPress/Classes/Models/AbstractPost.m b/WordPress/Classes/Models/AbstractPost.m index f38b0fea55bb..97f97e893d53 100644 --- a/WordPress/Classes/Models/AbstractPost.m +++ b/WordPress/Classes/Models/AbstractPost.m @@ -10,8 +10,6 @@ @implementation AbstractPost @dynamic blog; @dynamic dateModified; @dynamic media; -@dynamic metaIsLocal; -@dynamic metaPublishImmediately; @dynamic comments; @dynamic featuredImage; @dynamic revisions; @@ -25,19 +23,6 @@ @implementation AbstractPost @dynamic foreignID; @synthesize voiceContent; -+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key -{ - NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; - if ([key isEqualToString:@"metaIsLocal"]) { - keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"remoteStatusNumber"]]; - - } else if ([key isEqualToString:@"metaPublishImmediately"]) { - keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"date_created_gmt"]]; - } - - return keyPaths; -} - #pragma mark - Life Cycle Methods - (void)save @@ -52,7 +37,6 @@ - (void)setRemoteStatusNumber:(NSNumber *)remoteStatusNumber { NSString *key = @"remoteStatusNumber"; [self willChangeValueForKey:key]; - self.metaIsLocal = ([remoteStatusNumber integerValue] == AbstractPostRemoteStatusLocal); [self setPrimitiveValue:remoteStatusNumber forKey:key]; [self didChangeValueForKey:key]; } @@ -61,7 +45,6 @@ - (void)setDate_created_gmt:(NSDate *)date_created_gmt { NSString *key = @"date_created_gmt"; [self willChangeValueForKey:key]; - self.metaPublishImmediately = [self shouldPublishImmediately]; [self setPrimitiveValue:date_created_gmt forKey:key]; [self didChangeValueForKey:key]; } diff --git a/WordPress/WordPressTest/PostBuilder.swift b/WordPress/WordPressTest/PostBuilder.swift index 211f22987dd0..69263eefac5d 100644 --- a/WordPress/WordPressTest/PostBuilder.swift +++ b/WordPress/WordPressTest/PostBuilder.swift @@ -126,11 +126,6 @@ class PostBuilder { return self } - func with(statusAfterSync: BasePost.Status?) -> PostBuilder { - post.statusAfterSync = statusAfterSync - return self - } - func with(image: String, status: MediaRemoteStatus? = nil, autoUploadFailureCount: Int = 0) -> PostBuilder { guard let context = post.managedObjectContext else { return self @@ -163,12 +158,6 @@ class PostBuilder { return self } - func with(autoUploadAttemptsCount: Int) -> PostBuilder { - post.autoUploadAttemptsCount = NSNumber(value: autoUploadAttemptsCount) - - return self - } - func with(disabledConnections: [NSNumber: [String: String]]) -> PostBuilder { post.disabledPublicizeConnections = disabledConnections return self From ad6ffc0bfd44f034cc03ab06495770713e59d7da Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 7 Jun 2024 17:03:23 -0400 Subject: [PATCH 07/30] Fix an issue with missing navigation bar background in Post Settings --- .../ViewRelated/Post/PostEditor+MoreOptions.swift | 15 ++++++--------- .../Post/PostSettingsViewController+Swift.swift | 1 + 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor+MoreOptions.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+MoreOptions.swift index 663a23b27152..cc97fc7467fd 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor+MoreOptions.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor+MoreOptions.swift @@ -4,21 +4,18 @@ import WordPressFlux extension PostEditor { func displayPostSettings() { - let settingsViewController: PostSettingsViewController - if post is Page { - settingsViewController = PageSettingsViewController(post: post) - } else { - settingsViewController = PostSettingsViewController(post: post) - } - settingsViewController.featuredImageDelegate = self as? FeaturedImageDelegate + let viewController = PostSettingsViewController.make(for: post) + viewController.featuredImageDelegate = self as? FeaturedImageDelegate + viewController.configureDefaultNavigationBarAppearance() let doneButton = UIBarButtonItem(systemItem: .done, primaryAction: .init(handler: { [weak self] _ in self?.editorContentWasUpdated() self?.navigationController?.dismiss(animated: true) })) doneButton.accessibilityIdentifier = "close" - settingsViewController.navigationItem.rightBarButtonItem = doneButton + viewController.navigationItem.rightBarButtonItem = doneButton - let navigation = UINavigationController(rootViewController: settingsViewController) + let navigation = UINavigationController(rootViewController: viewController) + navigation.navigationBar.isTranslucent = true // Reset to default self.navigationController?.present(navigation, animated: true) } diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift index 37329c74cf55..52d8673d3b8f 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift @@ -188,6 +188,7 @@ extension PostSettingsViewController { WPAnalytics.track(.editorPostScheduledChanged, properties: ["via": "settings"]) viewModel.setDate(date) } + viewController.configureDefaultNavigationBarAppearance() self.navigationController?.pushViewController(viewController, animated: true) } } From f2ea5119057b6c085b43f8066722b596f032d12a Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 7 Jun 2024 17:37:54 -0400 Subject: [PATCH 08/30] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 3b096edde23b..d3d2db2b033a 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -2,6 +2,7 @@ ----- * [*] [internal] Block editor: Add onContentUpdate bridge functionality [#23234] * [*] Fix a rare crash when trashing posts [#23321] +* [*] Fix an issue with a missing navigation bar background in Post Settings [#23334] 25.0 ----- From 1de5277e9df6c38d85869c899fe42cc112739a0b Mon Sep 17 00:00:00 2001 From: Momo Ozawa Date: Fri, 31 May 2024 15:30:50 +0100 Subject: [PATCH 09/30] Fix typo --- WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index 62c261af1a24..a053a2d61a06 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -34,7 +34,7 @@ struct VoiceToContentView: View { case .recording: VoiceToContentRecordingView(viewModel: viewModel) case .processing: - VoiceToContenProcessingView(viewModel: viewModel) + VoiceToContentProcessingView(viewModel: viewModel) } } @@ -195,7 +195,7 @@ private struct RecordButton: View { } } -private struct VoiceToContenProcessingView: View { +private struct VoiceToContentProcessingView: View { @ObservedObject fileprivate var viewModel: VoiceToContentViewModel var body: some View { From 3b872c5d1ad39c83e4e8f03dfb609595e70d794c Mon Sep 17 00:00:00 2001 From: Momo Ozawa Date: Fri, 31 May 2024 17:45:01 +0100 Subject: [PATCH 10/30] Replace icon with speech visualizer view --- .../Voice/VoiceToContentView.swift | 73 +++++++++++-------- .../Voice/VoiceToContentViewModel.swift | 24 +++++- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index a053a2d61a06..01208e391b90 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -114,26 +114,11 @@ private struct VoiceToContentRecordingView: View { var body: some View { VStack { Spacer() - VStack(spacing: 8) { - if #available(iOS 17.0, *) { - waveformIcon - .symbolEffect(.variableColor) - } else { - waveformIcon - } - } + SpeechVisualizerView(viewModel: viewModel) Spacer() } } - private var waveformIcon: some View { - Image(systemName: "waveform") - .resizable() - .scaledToFit() - .frame(width: 80) - .foregroundStyle(Color(uiColor: .brand)) - } - private var buttonDone: some View { VStack(spacing: 16) { Button(action: viewModel.buttonDoneRecordingTapped) { @@ -153,6 +138,47 @@ private struct VoiceToContentRecordingView: View { } } +private struct VoiceToContentProcessingView: View { + @ObservedObject fileprivate var viewModel: VoiceToContentViewModel + + var body: some View { + VStack { + Spacer() + + ProgressView() + .controlSize(.large) + + Spacer() + } + } +} + +private struct SpeechVisualizerView: View { + @ObservedObject fileprivate var viewModel: VoiceToContentViewModel + + var body: some View { + HStack(spacing: 6) { + ForEach(viewModel.soundSamples, id: \.self) { decibel in + Capsule(style: .continuous) + .fill(Color(uiColor: .brand)) + .frame(width: 10, height: self.normalizeSoundLevel(decibel, min: 1, max: 100)) + } + } + } + + /// Returns a normalized sound level between a specified min and max value. + /// + /// - Parameters: + /// - decibels: Decibels relative to full scale. 0 dBFS is assigned to the maximum possible digital level. + /// See https://en.wikipedia.org/wiki/DBFS for more details. + /// - min: Minimum value for the normalized sound level. + /// - max: Maximum value for the normalized sound level. + private func normalizeSoundLevel(_ decibels: Float, min minValue: CGFloat, max maxValue: CGFloat) -> CGFloat { + let level = max(minValue * 2, CGFloat(decibels) + 50) / 2 + return CGFloat(level * (maxValue / 25)) + } +} + private struct RecordButton: View { @ObservedObject fileprivate var viewModel: VoiceToContentViewModel @@ -195,21 +221,6 @@ private struct RecordButton: View { } } -private struct VoiceToContentProcessingView: View { - @ObservedObject fileprivate var viewModel: VoiceToContentViewModel - - var body: some View { - VStack { - Spacer() - - ProgressView() - .controlSize(.large) - - Spacer() - } - } -} - private enum Strings { static let beginRecording = NSLocalizedString("postFromAudio.beginRecording", value: "Begin recording", comment: "Button title") static let done = NSLocalizedString("postFromAudio.done", value: "Done", comment: "Button title") diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift index f4550e473626..d8cf67bf51ab 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift @@ -11,6 +11,8 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder @Published private(set) var step: Step = .welcome @Published private(set) var loadingState: LoadingState? + @Published private(set) var soundSamples: [Float] = [] + private(set) var errorAlertMessage: String? @Published var isShowingErrorAlert = false @@ -28,6 +30,8 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder private var audioRecorder: AVAudioRecorder? private weak var timer: Timer? private let blog: Blog + private let numberOfSamples: Int + private var currentSample: Int private let completion: (String) -> Void enum Step { @@ -58,8 +62,11 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder timer?.invalidate() } - init(blog: Blog, _ completion: @escaping (String) -> Void) { + init(blog: Blog, numberOfSamples: Int = 20, _ completion: @escaping (String) -> Void) { self.blog = blog + self.numberOfSamples = numberOfSamples + self.currentSample = 0 + self.soundSamples = [Float](repeating: .zero, count: numberOfSamples) self.completion = completion self.title = Strings.title } @@ -167,6 +174,7 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder self.audioRecorder = audioRecorder audioRecorder.delegate = self + audioRecorder.isMeteringEnabled = true audioRecorder.record() step = .recording @@ -177,10 +185,10 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder } private func startRecordingTimer() { - timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in - guard let self, let currentTime = self.audioRecorder?.currentTime else { return } + timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self] _ in + guard let self, let audioRecorder = self.audioRecorder else { return } - let timeRemaining = Constants.recordingTimeLimit - currentTime + let timeRemaining = Constants.recordingTimeLimit - audioRecorder.currentTime guard timeRemaining > 0 else { WPAnalytics.track(.voiceToContentRecordingLimitReached) @@ -188,6 +196,8 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder return } + self.updateSoundSamples(from: audioRecorder) + if #available(iOS 16.0, *) { self.subtitle = Duration.seconds(timeRemaining) .formatted(.time(pattern: .minuteSecond(padMinuteToLength: 2))) @@ -197,6 +207,12 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder } } + private func updateSoundSamples(from audioRecorder: AVAudioRecorder) { + audioRecorder.updateMeters() + soundSamples[currentSample] = audioRecorder.averagePower(forChannel: 0) + currentSample = (currentSample + 1) % numberOfSamples + } + func buttonDoneRecordingTapped() { WPAnalytics.track(.voiceToContentButtonDoneTapped) From ecf050deb74929e66ae534c763014a5de2cd6329 Mon Sep 17 00:00:00 2001 From: Momo Ozawa Date: Fri, 31 May 2024 17:46:29 +0100 Subject: [PATCH 11/30] Speed up replace icon animation --- WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index 01208e391b90..69480f629b97 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -189,7 +189,7 @@ private struct RecordButton: View { Button(action: isRecording ? viewModel.buttonDoneRecordingTapped : viewModel.buttonRecordTapped) { if #available(iOS 17.0, *) { icon - .contentTransition(.symbolEffect(.replace, options: .speed(4))) + .contentTransition(.symbolEffect(.replace, options: .speed(5))) } else { icon } From 60d103a4be6e893c8cb65bfc77e9db97fa550560 Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 08:11:39 -0400 Subject: [PATCH 12/30] Fix issues with the record button transitions --- .../Voice/VoiceToContentView.swift | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index 69480f629b97..d3027233d855 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -118,24 +118,6 @@ private struct VoiceToContentRecordingView: View { Spacer() } } - - private var buttonDone: some View { - VStack(spacing: 16) { - Button(action: viewModel.buttonDoneRecordingTapped) { - Image(systemName: "stop.fill") - .resizable() - .scaledToFit() - .frame(width: 28) - .padding(28) - .background(.black) - .foregroundColor(.white) - .clipShape(Circle()) - } - - Text(Strings.done) - .foregroundStyle(.primary) - } - } } private struct VoiceToContentProcessingView: View { @@ -195,7 +177,7 @@ private struct RecordButton: View { } } - Text(Strings.done) + Text(isRecording ? Strings.done : Strings.beginRecording) .foregroundStyle(.primary) } .opacity(viewModel.isButtonRecordEnabled ? 1 : 0.5) @@ -211,10 +193,8 @@ private struct RecordButton: View { private var icon: some View { Image(systemName: isRecording ? "stop.fill" : "mic") - .resizable() - .scaledToFit() - .frame(width: isRecording ? 28 : 36) - .padding(isRecording ? 28 : 24) + .font(.system(size: isRecording ? 36 : 30)) + .frame(width: 84, height: 84) .background(backgroundColor) .foregroundColor(.white) .clipShape(Circle()) From 93bc664ad27566c1c3ddd23371d234f756cb5054 Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 08:11:58 -0400 Subject: [PATCH 13/30] Make the progress indicator use secondary color --- WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index d3027233d855..08d4c8e917bb 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -128,6 +128,7 @@ private struct VoiceToContentProcessingView: View { Spacer() ProgressView() + .tint(.secondary) .controlSize(.large) Spacer() From 505689b91ca4d0acb961756518bc4e2b59d9107f Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Mon, 10 Jun 2024 13:07:43 +0000 Subject: [PATCH 14/30] Bump version number --- config/Version.internal.xcconfig | 4 ++-- config/Version.public.xcconfig | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/Version.internal.xcconfig b/config/Version.internal.xcconfig index 4d062018c888..da5ad0a2e9b1 100644 --- a/config/Version.internal.xcconfig +++ b/config/Version.internal.xcconfig @@ -1,2 +1,2 @@ -VERSION_LONG = 25.0.0.20240607 -VERSION_SHORT = 25.0 +VERSION_LONG = 25.1.0.20240610 +VERSION_SHORT = 25.1 diff --git a/config/Version.public.xcconfig b/config/Version.public.xcconfig index 4ec665d4579c..dc351841dfff 100644 --- a/config/Version.public.xcconfig +++ b/config/Version.public.xcconfig @@ -1,2 +1,2 @@ -VERSION_LONG = 25.0.0.2 -VERSION_SHORT = 25.0 +VERSION_LONG = 25.1.0.0 +VERSION_SHORT = 25.1 From 59545244edf73cc9cc7aa40a5f7c1b4a48abbbc5 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Mon, 10 Jun 2024 13:07:43 +0000 Subject: [PATCH 15/30] Update draft release notes for 25.1. --- WordPress/Resources/release_notes.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WordPress/Resources/release_notes.txt b/WordPress/Resources/release_notes.txt index 7f6d728caea9..fe6e029f32bd 100644 --- a/WordPress/Resources/release_notes.txt +++ b/WordPress/Resources/release_notes.txt @@ -1,2 +1,4 @@ -Noticed a long delay when saving posts? That might have been because of image, gallery, or file blocks—but not anymore. Saving should be quick and easy, no matter how many photos or files you’ve uploaded. -We also fixed the overlapping “Done” button on the “Post Published” screen. Easy peasy, lemon squeezy. +* [*] [internal] Block editor: Add onContentUpdate bridge functionality [#23234] +* [*] Fix a rare crash when trashing posts [#23321] +* [*] Fix an issue with a missing navigation bar background in Post Settings [#23334] + From fd1492477fd1e8a1f6d535030262d50009904e00 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Mon, 10 Jun 2024 13:07:43 +0000 Subject: [PATCH 16/30] Update draft release notes for 25.1. --- WordPress/Jetpack/Resources/release_notes.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WordPress/Jetpack/Resources/release_notes.txt b/WordPress/Jetpack/Resources/release_notes.txt index f9ad512fbf25..fe6e029f32bd 100644 --- a/WordPress/Jetpack/Resources/release_notes.txt +++ b/WordPress/Jetpack/Resources/release_notes.txt @@ -1,3 +1,4 @@ -Noticed a long delay when saving posts? That might have been because of image, gallery, or file blocks—but not anymore. Saving should be quick and easy, no matter how many photos or files you’ve uploaded. -The Tags feed is live! You can now see content with specific tags, all in one place. Tag, you’re it. -Finally, we fixed the overlapping “Done” button on the “Post Published” screen. Easy peasy, lemon squeezy. +* [*] [internal] Block editor: Add onContentUpdate bridge functionality [#23234] +* [*] Fix a rare crash when trashing posts [#23321] +* [*] Fix an issue with a missing navigation bar background in Post Settings [#23334] + From bde07f47352d042e68f8dff9cf970987319cb489 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Mon, 10 Jun 2024 13:07:43 +0000 Subject: [PATCH 17/30] Release Notes: add new section for next version (25.2) --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index d3d2db2b033a..513de409577e 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,3 +1,7 @@ +25.2 +----- + + 25.1 ----- * [*] [internal] Block editor: Add onContentUpdate bridge functionality [#23234] From a4628c3f7ebd8542780cb7fab7a24f60050f47bc Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Mon, 10 Jun 2024 20:54:40 +0700 Subject: [PATCH 18/30] Update internal libraries to latest stable version for 25.1 --- Podfile | 4 ++-- Podfile.lock | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Podfile b/Podfile index 8b95f855eef4..349b27b3b5e5 100644 --- a/Podfile +++ b/Podfile @@ -55,8 +55,8 @@ def gravatar end def wordpress_kit - # pod 'WordPressKit', '~> 17.1.2' - pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '5323909e18815c36706efca5ffd289c7d6759575' + pod 'WordPressKit', '~> 17.2.0' + # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '' # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: '' # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', tag: '' # pod 'WordPressKit', path: '../WordPressKit-iOS' diff --git a/Podfile.lock b/Podfile.lock index b51cc6144e56..60238a073482 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -71,7 +71,7 @@ PODS: - WordPressKit (~> 17.0) - WordPressShared (~> 2.1-beta) - WordPressUI (~> 1.7-beta) - - WordPressKit (17.1.2): + - WordPressKit (17.2.0): - NSObject-SafeExpectations (~> 0.0.4) - UIDeviceIdentifier (~> 2.0) - WordPressShared (~> 2.0-beta) @@ -121,7 +121,7 @@ DEPENDENCIES: - SwiftLint (= 0.54.0) - WordPress-Editor-iOS (~> 1.19.11) - WordPressAuthenticator (>= 9.0.8, ~> 9.0) - - WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, commit `5323909e18815c36706efca5ffd289c7d6759575`) + - WordPressKit (~> 17.2.0) - WordPressShared (>= 2.3.1, ~> 2.3) - WordPressUI (~> 1.16) - ZendeskSupportSDK (= 5.3.0) @@ -160,6 +160,7 @@ SPEC REPOS: - SVProgressHUD - SwiftLint - UIDeviceIdentifier + - WordPressKit - WordPressUI - wpxmlrpc - ZendeskCommonUISDK @@ -177,17 +178,11 @@ EXTERNAL SOURCES: :tag: 0.2.0 Gutenberg: :podspec: https://cdn.a8c-ci.services/gutenberg-mobile/Gutenberg-v1.120.0.podspec - WordPressKit: - :commit: 5323909e18815c36706efca5ffd289c7d6759575 - :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git CHECKOUT OPTIONS: FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 - WordPressKit: - :commit: 5323909e18815c36706efca5ffd289c7d6759575 - :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git SPEC CHECKSUMS: Alamofire: 02b772c9910e8eba1a079227c32fbd9e46c90a24 @@ -221,7 +216,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 3732c6d865a5c9f35788377bdeda8a80ea10d0a1 WordPress-Editor-iOS: 453345420ced3d3ef20f0051b3df46ff10281e0c WordPressAuthenticator: 898acaac75c5ade9b900c02622a15b9aef8fde1a - WordPressKit: bf29888bc32c1ac18669b81d49b148d867787666 + WordPressKit: 98809cdb460bacc6634b49079335a5ccace34e59 WordPressShared: 0160364ed24f4d67fed4e85003fefa837faad84f WordPressUI: ec5ebcf7e63e797ba51d07513e340c1b14cf45a4 wpxmlrpc: 68db063041e85d186db21f674adf08d9c70627fd @@ -234,6 +229,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: d170fa8e270b2a32bef9dcdcabff5b8f1a5deced -PODFILE CHECKSUM: e1fecb2345dcdfd9a89c1a6dc49322a3d54fc075 +PODFILE CHECKSUM: 739f6071356a5d136381ca7788feb63eab4a3062 COCOAPODS: 1.15.2 From 4dc6f87db1978c999210c3d21aa9fa1b9843fe87 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Mon, 10 Jun 2024 06:59:23 -0700 Subject: [PATCH 19/30] Update strings for localization --- .../Resources/en.lproj/Localizable.strings | 407 ++---------------- 1 file changed, 29 insertions(+), 378 deletions(-) diff --git a/WordPress/Resources/en.lproj/Localizable.strings b/WordPress/Resources/en.lproj/Localizable.strings index e602a6b781f9..c8891f8aaeb4 100644 --- a/WordPress/Resources/en.lproj/Localizable.strings +++ b/WordPress/Resources/en.lproj/Localizable.strings @@ -908,12 +908,6 @@ /* Message prompting the user to confirm that they want to permanently delete a media item. Should match Calypso. */ "Are you sure you want to permanently delete this item?" = "Are you sure you want to permanently delete this item?"; -/* Message of the confirmation alert when deleting a post from the trash. */ -"Are you sure you want to permanently delete this post?" = "Are you sure you want to permanently delete this post?"; - -/* Title of the message shown when the user taps Publish while editing a post. Options will be Publish and Keep Editing. */ -"Are you sure you want to publish?" = "Are you sure you want to publish?"; - /* Text for the alert to confirm a plugin removal. %1$@ is the plugin name, %2$@ is the site title. */ "Are you sure you want to remove %1$@ from %2$@?" = "Are you sure you want to remove %1$@ from %2$@?"; @@ -923,24 +917,6 @@ /* Description for the confirm restore action. %1$@ is a placeholder for the selected date. */ "Are you sure you want to restore your site back to %1$@? This will remove content and options created or changed since then." = "Are you sure you want to restore your site back to %1$@? This will remove content and options created or changed since then."; -/* Title of the message shown when the user taps Save as Draft while editing a post. Options will be Save Now and Keep Editing. */ -"Are you sure you want to save as draft?" = "Are you sure you want to save as draft?"; - -/* Title of the message shown when the user taps Save while editing a post. Options will be Save Now and Keep Editing. */ -"Are you sure you want to save?" = "Are you sure you want to save?"; - -/* Title of message shown when the user taps Schedule while editing a post. Options will be Schedule and Keep Editing */ -"Are you sure you want to schedule?" = "Are you sure you want to schedule?"; - -/* Title of message shown when user taps submit for review. */ -"Are you sure you want to submit for review?" = "Are you sure you want to submit for review?"; - -/* Message of the trash confirmation alert. */ -"Are you sure you want to trash this post?" = "Are you sure you want to trash this post?"; - -/* Title of message shown when user taps update. */ -"Are you sure you want to update?" = "Are you sure you want to update?"; - /* Title that asks the user if they are the trying to login. %1$@ is a placeholder for the browser name (Chrome/Firefox), %2$@ is a placeholder for the users location */ "Are you trying to log in to %1$@ near %2$@?" = "Are you trying to log in to %1$@ near %2$@?"; @@ -1143,9 +1119,6 @@ /* All Time Stats 'Best views ever' label */ "Best views ever" = "Best views ever"; -/* Notice that a page without content has been created */ -"Blank page created" = "Blank page created"; - /* Noun. Links to a blog's Blaze screen. */ "Blaze" = "Blaze"; @@ -1445,7 +1418,6 @@ Note that the word 'go' here should have a closer meaning to 'start' rather than Button label that dismisses the qr log in flow and returns the user back to the previous screen Button title, cancel fixing all threats Button title. Cancels a pending action. - Button title. Cancels automatic uploading of the post when the device is back online. Button title. Tapping it cancels the login flow. Cancel Cancel a prompt @@ -1726,6 +1698,9 @@ Note that the word 'go' here should have a closer meaning to 'start' rather than /* Name of setting configured when a site uses a list of blog posts as its homepage */ "Classic Blog" = "Classic Blog"; +/* Post Editor / Button in the 'More' menu */ +"classicEditor.moreMenu.saveDraft" = "Save Draft"; + /* Message of the trash confirmation alert. */ "Clear all old activity logs?" = "Clear all old activity logs?"; @@ -2113,9 +2088,6 @@ Example: Reply to Pamela Nguyen */ /* No comment provided by engineer. */ "Copy post text" = "Copy post text"; -/* Button title displayed in popup indicating the user copied the local copy */ -"Copy the version from this app" = "Copy the version from this app"; - /* translators: Copy URL from the clipboard, https://sample.url */ "Copy URL from the clipboard, %s" = "Copy URL from the clipboard, %s"; @@ -2607,13 +2579,9 @@ Example: Reply to Pamela Nguyen */ /* Menus confirmation button for deleting a menu. */ "Delete Menu" = "Delete Menu"; -/* Delete option in the confirmation alert when deleting a post from the trash. - Title for button on the comment details page that deletes the comment when tapped. */ +/* Title for button on the comment details page that deletes the comment when tapped. */ "Delete Permanently" = "Delete Permanently"; -/* Title of the confirmation alert when deleting a post from the trash. */ -"Delete Permanently?" = "Delete Permanently?"; - /* Button label for deleting the current site Label for selecting the Delete Site Settings item Title of settings page for deleting a site */ @@ -3102,9 +3070,6 @@ Example: Reply to Pamela Nguyen */ /* Title for the edit sharing buttons section */ "Edit sharing buttons" = "Edit sharing buttons"; -/* Button title displayed in popup indicating that the user edits the post first */ -"Edit the post first" = "Edit the post first"; - /* No comment provided by engineer. */ "Edit using web editor" = "Edit using web editor"; @@ -3246,14 +3211,10 @@ Example: Reply to Pamela Nguyen */ /* No comment provided by engineer. */ "Enter a password" = "Enter a password"; -/* Message explaining why the user might enter a password. */ -"Enter a password to protect this post" = "Enter a password to protect this post"; - /* Accessibility Label for the enter full screen button on the comment reply text view */ "Enter Full Screen" = "Enter Full Screen"; -/* (placeholder) Help enter WordPress password - Placeholder of a field to type a password to protect the post. */ +/* (placeholder) Help enter WordPress password */ "Enter password" = "Enter password"; /* Instruction text on the login's site addresss screen. @@ -3344,24 +3305,12 @@ Example: Reply to Pamela Nguyen */ /* Message displayed when trashing a comment fails. */ "Error moving comment to trash." = "Error moving comment to trash."; -/* Text displayed in notice while a post is being published. */ -"Error occurred during publishing" = "Error occurred during publishing"; - -/* Text displayed in notice after attempting to save a draft post and an error occurred. */ -"Error occurred during saving" = "Error occurred during saving"; - -/* Text displayed in notice while a post is being scheduled to be published. */ -"Error occurred during scheduling" = "Error occurred during scheduling"; - /* Register Domain - Domain contact information error message shown to indicate an error during fetching domain contact information */ "Error occurred fetching domain contact information" = "Error occurred fetching domain contact information"; /* Register Domain - Domain contact information error message shown to indicate an error during fetching list of states */ "Error occurred fetching states" = "Error occurred fetching states"; -/* Text displayed in HUD while a post revision is being loaded. */ -"Error occurred\nduring loading" = "Error occurred\nduring loading"; - /* Title of error dialog when removing a site owner fails. */ "Error removing %@" = "Error removing %@"; @@ -3692,15 +3641,9 @@ Example: Reply to Pamela Nguyen */ /* Format for blogging prompts attribution. %1$@ is the attribution source. */ "From %1$@" = "From %1$@"; -/* Button title displayed in popup indicating date of change on another device */ -"From another device" = "From another device"; - /* No comment provided by engineer. */ "From clipboard" = "From clipboard"; -/* Button title displayed in popup indicating date of change on device */ -"From this device" = "From this device"; - /* Full size image. (default). Should be the same as in core WP. */ "Full Size" = "Full Size"; @@ -3735,9 +3678,6 @@ Example: Reply to Pamela Nguyen */ /* Title. A call to action to generate a new invite link. */ "Generate new link" = "Generate new link"; -/* Message to indicate progress of generating preview */ -"Generating Preview" = "Generating Preview"; - /* The button title for a secondary call-to-action button. When the user wants to try sending a magic link instead of entering a password. */ "Get a login link by email" = "Get a login link by email"; @@ -4702,7 +4642,6 @@ Please install the %3$@ to use the app with this site."; /* Button shown if there are unsaved changes and the author cancelled editing a Comment. Button shown if there are unsaved changes and the author is trying to move away from the post. - Button shown when the author is asked for publishing confirmation. Goes back to editing the post. */ "Keep Editing" = "Keep Editing"; @@ -5506,7 +5445,6 @@ Please install the %3$@ to use the app with this site."; "Move to top" = "Move to top"; /* Title for button on the comment details page that moves the comment to trash when tapped. - Trash option in the trash confirmation alert. Trashes the comment */ "Move to Trash" = "Move to Trash"; @@ -6325,24 +6263,6 @@ Please install the %3$@ to use the app with this site."; /* translators: accessibility text. %s: Page break text. */ "Page break block. %s" = "Page break block. %s"; -/* Notice that a page with content has been created */ -"Page created" = "Page created"; - -/* Title of notification displayed when a page has been successfully saved as a draft. */ -"Page draft uploaded" = "Page draft uploaded"; - -/* Title of notification displayed when a page has failed to upload. */ -"Page failed to upload" = "Page failed to upload"; - -/* Title of notification displayed when a page has been successfully saved as a draft. */ -"Page pending review" = "Page pending review"; - -/* Title of notification displayed when a page has been successfully published. */ -"Page published" = "Page published"; - -/* Title of notification displayed when a page has been successfully scheduled. */ -"Page scheduled" = "Page scheduled"; - /* Name of the button to open the page settings The title of the Page Settings screen. */ "Page Settings" = "Page Settings"; @@ -6353,9 +6273,6 @@ Please install the %3$@ to use the app with this site."; /* translators: accessibility text. empty page title. */ "Page title. Empty" = "Page title. Empty"; -/* Title of notification displayed when a page has been successfully updated. */ -"Page updated" = "Page updated"; - /* Badge for page cells */ "pageList.badgeHomepage" = "Homepage"; @@ -6386,33 +6303,12 @@ Please install the %3$@ to use the app with this site."; /* Message informing the user that their static homepage page was set successfully */ "pages.updatePage.successTitle" = "Page successfully updated"; -/* Delete option in the confirmation alert when deleting a page from the trash. */ -"pagesList.deletePermanently.actionTitle" = "Delete Permanently"; - -/* Message of the confirmation alert when deleting a page from the trash. */ -"pagesList.deletePermanently.alertMessage" = "Are you sure you want to permanently delete this page?"; - -/* Title of the confirmation alert when deleting a page from the trash. */ -"pagesList.deletePermanently.alertTitle" = "Delete Permanently?"; - /* Menu option for filtering posts by everyone */ "pagesList.pagesByEveryone" = "Pages by everyone"; /* Menu option for filtering posts by me */ "pagesList.pagesByMe" = "Pages by me"; -/* Trash option in the trash page confirmation alert. */ -"pagesList.trash.actionTitle" = "Move to Trash"; - -/* Message of the trash page confirmation alert. */ -"pagesList.trash.alertMessage" = "Are you sure you want to trash this page?"; - -/* Title of the trash page confirmation alert. */ -"pagesList.trash.alertTitle" = "Trash this page?"; - -/* Cancels an Action */ -"pagesList.trash.cancel" = "Cancel"; - /* Comments Paging Discussion Settings Settings: Comments Paging preferences */ @@ -6446,9 +6342,6 @@ Please install the %3$@ to use the app with this site."; /* Loader title displayed by the loading view while the password is changed successfully */ "Password changed successfully" = "Password changed successfully"; -/* Privacy setting for posts set to 'Password protected'. Should be the same as in core WP. */ -"Password protected" = "Password protected"; - /* No comment provided by engineer. */ "Paste block after" = "Paste block after"; @@ -6744,30 +6637,15 @@ Please install the %3$@ to use the app with this site."; /* The footer text appears within the footer displaying when the post has been created. */ "Post created on %@" = "Post created on %@"; -/* Title of notification displayed when a post has been successfully saved as a draft. */ -"Post draft uploaded" = "Post draft uploaded"; - -/* Title of notification displayed when a post has failed to upload. */ -"Post failed to upload" = "Post failed to upload"; - /* The post formats available for the post. Should be the same as in core WP. */ "Post Format" = "Post Format"; -/* Title of notification displayed when a post has been successfully saved as a draft. */ -"Post pending review" = "Post pending review"; - -/* Title of notification displayed when a post has been successfully published. */ -"Post published" = "Post published"; - /* Title of the notification presented in Reader when a post is removed from save for later */ "Post removed." = "Post removed."; /* Title of the notification presented in Reader when a post is saved for later */ "Post saved." = "Post saved."; -/* Title of notification displayed when a post has been successfully scheduled. */ -"Post scheduled" = "Post scheduled"; - /* Name of the button to open the post settings The title of the Post Settings screen. */ "Post Settings" = "Post Settings"; @@ -6775,9 +6653,6 @@ Please install the %3$@ to use the app with this site."; /* Window title for Post Stats view. */ "Post Stats" = "Post Stats"; -/* Title displayed in popup when user tries to copy a post with unsaved changes */ -"Post sync conflict" = "Post sync conflict"; - /* translators: accessibility text. %s: text content of the post title. */ "Post title. %s" = "Post title. %s"; @@ -6787,15 +6662,6 @@ Please install the %3$@ to use the app with this site."; /* Title of camera permissions screen */ "Post to WordPress" = "Post to WordPress"; -/* Title of notification displayed when a post has been successfully updated. */ -"Post updated" = "Post updated"; - -/* Status mesasge for post cells */ -"post.deletingPostPermanentlyStatusMessage" = "Deleting post..."; - -/* Status mesasge for post cells */ -"post.movingToTrashStatusMessage" = "Moving post to trash..."; - /* Register Domain - Address information field Postal Code */ "Postal Code" = "Postal Code"; @@ -6844,6 +6710,9 @@ Please install the %3$@ to use the app with this site."; /* Post Editor / Button in the 'More' menu */ "postEditor.moreMenu.revisions" = "Revisions"; +/* Post Editor / Button in the 'More' menu */ +"postEditor.moreMenu.saveDraft" = "Save Draft"; + /* Post Editor / Button in the 'More' menu */ "postEditor.moreMenu.visualEditor" = "Visual Editor"; @@ -6874,9 +6743,21 @@ Please install the %3$@ to use the app with this site."; /* Saving draft to generate a preview (status message */ "postEditor.savingDraftForPreview" = "Saving draft..."; +/* Button title */ +"postFromAudio.beginRecording" = "Begin recording"; + /* Button title */ "postFromAudio.buttonUpgrade" = "Upgrade for more requests"; +/* Button title */ +"postFromAudio.done" = "Done"; + +/* The AI failed to understand the request for any reasons */ +"postFromAudio.errorMessage.cantUnderstandRequest" = "There were some issues processing the request. Please, try again later."; + +/* The screen subtitle in the error state */ +"postFromAudio.errorMessage.generic" = "Something went wrong"; + /* Message for 'not eligible' state view */ "postFromAudio.notEnoughRequestsMessage" = "You don't have enough requests available to create a post from audio."; @@ -6886,9 +6767,6 @@ Please install the %3$@ to use the app with this site."; /* Button title */ "postFromAudio.retry" = "Retry"; -/* The screen subtitle in the error state */ -"postFromAudio.subtitleError" = "Something went wrong"; - /* The screen subtitle */ "postFromAudio.subtitleRequestsAvailable" = "Requests available:"; @@ -6901,6 +6779,9 @@ Please install the %3$@ to use the app with this site."; /* The screen title when recording */ "postFromAudio.titleRecoding" = "Recording…"; +/* The value for the `requests available:` field for an unlimited plan */ +"postFromAudio.unlimited" = "Unlimited"; + /* Insights 'Posting Activity' header Title for stats Posting Activity view. */ "Posting Activity" = "Posting Activity"; @@ -6980,9 +6861,6 @@ Please install the %3$@ to use the app with this site."; /* A generic error message title */ "postNotice.errorTitle" = "An error occured"; -/* An error message */ -"postNotice.errorUnsyncedChangesMessage" = "The app is uploading previously made changes to the server. Please try again later."; - /* Button OK */ "postNotice.ok" = "OK"; @@ -7049,9 +6927,6 @@ Please install the %3$@ to use the app with this site."; /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "Promote with Blaze"; -/* Label for the Post List option that cancels automatic uploading of a post. */ -"posts.cancelUpload.actionTitle" = "Cancel upload"; - /* Label for post comments option. Tapping displays comments for a post. */ "posts.comments.actionTitle" = "Comments"; @@ -7073,9 +6948,6 @@ Please install the %3$@ to use the app with this site."; /* Label for the publish post button. */ "posts.publish.actionTitle" = "Publish"; -/* Retry uploading the post. */ -"posts.retry.actionTitle" = "Retry"; - /* Set the selected page as the homepage. */ "posts.setHomepage.actionTitle" = "Set as homepage"; @@ -7148,24 +7020,9 @@ Please install the %3$@ to use the app with this site."; /* Beginning text of the remaining social shares a user has left. %1$d is their current remaining shares. This text is combined with ' in the next 30 days' if there is no warning displayed. */ "postsettings.social.shares.text.format" = "%1$d social shares remaining"; -/* Error message on post/page settings screen */ -"postSettings.updateFailedMessage" = "Failed to update the post settings"; - /* An alert message explaning that by changing the visibility to private, the post will be published immediately to your site */ "postSettings.warningPostWillBePublishedAlertMessage" = "By changing the visibility to 'Private', the post will be published immediately"; -/* A short message explaining that a page was deleted permanently. */ -"postsList.deletePage.message" = "Page deleted permanently"; - -/* A short message explaining that a post was deleted permanently. */ -"postsList.deletePost.message" = "Post deleted permanently"; - -/* A short message explaining that a page was moved to the trash bin. */ -"postsList.movePageToTrash.message" = "Page moved to trash"; - -/* A short message explaining that a post was moved to the trash bin. */ -"postsList.movePostToTrash.message" = "Post moved to trash"; - /* Menu option for filtering posts by everyone */ "postsList.postsByEveryone" = "Posts by everyone"; @@ -7235,9 +7092,6 @@ Please install the %3$@ to use the app with this site."; /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.mediaUploadFailedTitle" = "Failed to upload media"; -/* Placeholder for a cell in the pre-publishing sheet */ -"prepublishing.postTitle" = "Title"; - /* Primary button label in the pre-publishing sheet */ "prepublishing.publish" = "Publish"; @@ -7378,8 +7232,7 @@ Tapping on this row allows the user to edit the sharing message. */ Privacy Settings Title */ "Privacy Settings" = "Privacy Settings"; -/* Name for the status of a post that is marked private. - Privacy setting for posts set to 'Private'. Should be the same as in core WP. */ +/* Name for the status of a post that is marked private. */ "Private" = "Private"; /* No comment provided by engineer. */ @@ -7417,11 +7270,7 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "View all responses"; -/* Privacy setting for posts set to 'Public' (default). Should be the same as in core WP. */ -"Public" = "Public"; - -/* Button title. Publishes a post. - Label for the publish (verb) button. Tapping publishes a draft post. +/* Label for the publish (verb) button. Tapping publishes a draft post. Publish post action on share extension site picker screen. Section title for the publish table section in the blog details screen */ "Publish" = "Publish"; @@ -7473,9 +7322,6 @@ Tapping on this row allows the user to edit the sharing message. */ /* A short message that informs the user a post is being published to the server from the share extension. */ "Publishing post..." = "Publishing post..."; -/* Text displayed in HUD while a post is being published. */ -"Publishing..." = "Publishing..."; - /* Post publish success view: button 'Done' */ "publishSuccessView.done" = "Done"; @@ -8451,10 +8297,8 @@ This empty state component is displayed only when the app fails to load posts un /* A prompt to attempt the failed network request again A prompt to attempt the failed network request again. Button title, displayed when media has failed to upload. Allows the user to try the upload again. - Button title. Retries uploading a post. If a user taps the button with this label, the action that evinced this error view will be retried. Opens the media library . - Retries the upload of a user's post. Retry updating User's Role Retry. Action Retry. Verb – retry a failed media upload. @@ -8544,12 +8388,6 @@ This empty state component is displayed only when the app fails to load posts un /* Text displayed in the share extension's summary view that describes the action of saving a single photo in a draft post. */ "Save 1 photo as a draft post on:" = "Save 1 photo as a draft post on:"; -/* Title of button allowing users to change the status of the post they are currently editing to Draft. */ -"Save as Draft" = "Save as Draft"; - -/* Button shown if there are unsaved changes and the author is trying to move away from the post. */ -"Save Draft" = "Save Draft"; - /* Text displayed in the share extension's summary view that describes the save draft page action. */ "Save draft page on:" = "Save draft page on:"; @@ -8581,8 +8419,7 @@ This empty state component is displayed only when the app fails to load posts un /* A short message that informs the user a draft post is being saved to the server from the share extension. */ "Saving post…" = "Saving post…"; -/* Menus save button title while it is saving a Menu. - Text displayed in HUD while a post is being saved as a draft. */ +/* Menus save button title while it is saving a Menu. */ "Saving..." = "Saving..."; /* Noun. Links to a blog's Jetpack Scan screen. @@ -8609,16 +8446,10 @@ This empty state component is displayed only when the app fails to load posts un /* Title for label when the actively scanning the users site */ "Scanning files" = "Scanning files"; -/* Schedule button, this is what the Publish button changes to in the Post Editor if the post has been scheduled for posting later. */ -"Schedule" = "Schedule"; - /* Name for the status of a scheduled post Title of the scheduled filter. This filter shows a list of posts that are scheduled to be published at a future date. */ "Scheduled" = "Scheduled"; -/* Text displayed in HUD while a post is being scheduled to be published. */ -"Scheduling..." = "Scheduling..."; - /* No comment provided by engineer. */ "Scrollable block menu closed." = "Scrollable block menu closed."; @@ -9668,9 +9499,6 @@ This empty state component is displayed only when the app fails to load posts un /* The label for the option to show Stats Traffic chart for Years. */ "stats.traffic.years" = "Years"; -/* The status of the post. Should be the same as in core WP. */ -"Status" = "Status"; - /* Section title for the moderation section of the comment details screen. */ "STATUS" = "STATUS"; @@ -9752,9 +9580,6 @@ This empty state component is displayed only when the app fails to load posts un /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Feedback"; -/* Text displayed in HUD while a post is being submitted for review. */ -"Submitting for Review..." = "Submitting for Review..."; - /* Notice displayed to the user after clearing the spotlight index in app settings. */ "Successfully cleared spotlight index" = "Successfully cleared spotlight index"; @@ -10236,9 +10061,6 @@ This empty state component is displayed only when the app fails to load posts un /* Text above the selection of the number of posts to show per blog page */ "The number of posts to show per page." = "The number of posts to show per page."; -/* Message displayed in popup when user tries to copy a post with conflicts */ -"The post you are trying to copy has two versions that are in conflict or you recently made changes but didn\'t save them.\nEdit the post first to resolve any conflict or proceed with copying the version from this app." = "The post you are trying to copy has two versions that are in conflict or you recently made changes but didn\'t save them.\nEdit the post first to resolve any conflict or proceed with copying the version from this app."; - /* A failure reason for when the request couldn't be serialized. */ "The serialization of the request failed." = "The serialization of the request failed."; @@ -10470,12 +10292,6 @@ This empty state component is displayed only when the app fails to load posts un /* Message displayed in Media Library if the user attempts to edit a media asset (image / video) after it has been deleted. */ "This media item has been deleted." = "This media item has been deleted."; -/* Prompts the user that the page is being uploaded and cannot be edited while that process is ongoing. */ -"This page is currently uploading. It won't take long – try again soon and you'll be able to edit it." = "This page is currently uploading. It won't take long – try again soon and you'll be able to edit it."; - -/* Prompts the user that the post is being uploaded and cannot be edited while that process is ongoing. */ -"This post is currently uploading. It won't take long – try again soon and you'll be able to edit it." = "This post is currently uploading. It won't take long – try again soon and you'll be able to edit it."; - /* Post is in my likes. Accessibility label */ "This post is in My Likes" = "This post is in My Likes"; @@ -10666,9 +10482,6 @@ This empty state component is displayed only when the app fails to load posts un Trashes the comment */ "Trash" = "Trash"; -/* Title of the trash confirmation alert. */ -"Trash this post?" = "Trash this post?"; - /* Name for the status of a trashed post Title of the trashed filter. This filter shows posts that have been moved to the trash bin. Title of trashed Comments filter. */ @@ -10964,18 +10777,9 @@ This empty state component is displayed only when the app fails to load posts un Update button label (saving content, ex: Post, Page, Comment). */ "Update" = "Update"; -/* Button shown if there are unsaved changes and the author is trying to move away from an already saved draft. */ -"Update Draft" = "Update Draft"; - /* Label action for updating a link on the editor */ "Update Link" = "Update Link"; -/* Button shown if there are unsaved changes and the author is trying to move away from an already published page. */ -"Update Page" = "Update Page"; - -/* Button shown if there are unsaved changes and the author is trying to move away from an already published post. */ -"Update Post" = "Update Post"; - /* Update profile photo in Me > My Profile */ "Update Profile Photo" = "Update Profile Photo"; @@ -11006,17 +10810,13 @@ This empty state component is displayed only when the app fails to load posts un /* Text in the profile editing page. Lets the user know about the consequences of their profile editing actions and how this relates to Gravatar. */ "Updating your avatar, name and about info here will also update it across all sites that use Gravatar profiles." = "Updating your avatar, name and about info here will also update it across all sites that use Gravatar profiles."; -/* Text displayed in HUD while a draft or scheduled post is being updated. */ -"Updating..." = "Updating..."; - /* No comment provided by engineer. */ "Upgrade your plan to upload audio" = "Upgrade your plan to upload audio"; /* No comment provided by engineer. */ "Upgrade your plan to use video covers" = "Upgrade your plan to use video covers"; -/* Message displayed on a post's card when the post has failed to upload - System notification displayed to the user when media files have failed to upload. */ +/* System notification displayed to the user when media files have failed to upload. */ "Upload failed" = "Upload failed"; /* Description to show on post setting for a featured image that failed to upload. */ @@ -11054,15 +10854,6 @@ This empty state component is displayed only when the app fails to load posts un /* Title for alert when trying to save/exit a post before media upload process is complete. */ "Uploading media" = "Uploading media"; -/* Message displayed on a post's card while the post is uploading media */ -"Uploading media..." = "Uploading media..."; - -/* Title for alert when trying to preview a post before the uploading process is complete. */ -"Uploading post" = "Uploading post"; - -/* Message displayed on a post's card when the post has failed to upload */ -"Uploading post..." = "Uploading post..."; - /* Message of an alert informing users that the video they are trying to select is not allowed. */ "Uploading videos longer than 5 minutes requires a paid plan." = "Uploading videos longer than 5 minutes requires a paid plan."; @@ -11174,8 +10965,7 @@ This empty state component is displayed only when the app fails to load posts un /* Period Stats 'Videos' header */ "Videos" = "Videos"; -/* Button title. Displays a summary / sharing screen for a specific post. - Opens the post epilogue screen to allow sharing / viewing of a post. +/* Opens the post epilogue screen to allow sharing / viewing of a post. Theme View action title Verb. The screen title shown when viewing a post inside the app. */ "View" = "View"; @@ -11225,8 +11015,7 @@ This empty state component is displayed only when the app fails to load posts un /* Insights views and visitors header */ "Views & Visitors" = "Views & Visitors"; -/* The visibility settings of the post. Should be the same as in core WP. - Visibility label */ +/* The visibility settings of the post. Should be the same as in core WP. */ "Visibility" = "Visibility"; /* An option to visit the site to which a specific post belongs */ @@ -11309,98 +11098,15 @@ This empty state component is displayed only when the app fails to load posts un /* No comment provided by engineer. */ "We are working hard to add more blocks with each release." = "We are working hard to add more blocks with each release."; -/* Text displayed in notice after the app fails to upload a page, not new attempt will be made. */ -"We couldn't complete this action, and didn't publish this page." = "We couldn't complete this action, and didn't publish this page."; - -/* Text displayed in notice after the app fails to upload a post, not new attempt will be made. */ -"We couldn't complete this action, and didn't publish this post." = "We couldn't complete this action, and didn't publish this post."; - -/* Text displayed after the app fails to upload a private page, no new attempt will be made. */ -"We couldn't complete this action, and didn't publish this private page." = "We couldn't complete this action, and didn't publish this private page."; - -/* Text displayed after the app fails to upload a private post, no new attempt will be made. */ -"We couldn't complete this action, and didn't publish this private post." = "We couldn't complete this action, and didn't publish this private post."; - -/* Text displayed after the app fails to upload a scheduled page, no new attempt will be made. */ -"We couldn't complete this action, and didn't schedule this page." = "We couldn't complete this action, and didn't schedule this page."; - -/* Text displayed after the app fails to upload a scheduled post, no new attempt will be made. */ -"We couldn't complete this action, and didn't schedule this post." = "We couldn't complete this action, and didn't schedule this post."; - -/* Text displayed in notice after the app fails to upload a page, not new attempt will be made. */ -"We couldn't complete this action, and didn't submit this page for review." = "We couldn't complete this action, and didn't submit this page for review."; - -/* Text displayed in notice after the app fails to upload a post, not new attempt will be made. */ -"We couldn't complete this action, and didn't submit this post for review." = "We couldn't complete this action, and didn't submit this post for review."; - -/* Text displayed after the app fails to upload a page, it will attempt to upload it later. - Text displayed after the app fails to upload a post, it will attempt to upload it later. */ -"We couldn't complete this action, but we'll try again later." = "We couldn't complete this action, but we'll try again later."; - -/* Text displayed after the app fails to upload a page, no new attempt will be made. - Text displayed after the app fails to upload a post, no new attempt will be made. */ -"We couldn't complete this action." = "We couldn't complete this action."; - /* Message for error displayed when preparing a backup fails. */ "We couldn't create your backup. Please try again later." = "We couldn't create your backup. Please try again later."; -/* Text displayed in notice after the app fails to upload a page, it will attempt to upload it later. */ -"We couldn't publish this page, but we'll try again later." = "We couldn't publish this page, but we'll try again later."; - -/* Text displayed in notice after the app fails to upload a post, it will attempt to upload it later. */ -"We couldn't publish this post, but we'll try again later." = "We couldn't publish this post, but we'll try again later."; - -/* Text displayed after the app fails to upload a private page, it will attempt to upload it later. */ -"We couldn't publish this private page, but we'll try again later." = "We couldn't publish this private page, but we'll try again later."; - -/* Text displayed after the app fails to upload a private post, it will attempt to upload it later. */ -"We couldn't publish this private post, but we'll try again later." = "We couldn't publish this private post, but we'll try again later."; - /* Message for error displayed when restoring a site fails. */ "We couldn't restore your site. Please try again later." = "We couldn't restore your site. Please try again later."; /* Description that appears when a media fails to load in the Media Editor. */ "We couldn't retrieve this media.\nPlease tap to retry." = "We couldn't retrieve this media.\nPlease tap to retry."; -/* Text displayed after the app fails to upload a scheduled page, it will attempt to upload it later. */ -"We couldn't schedule this page, but we'll try again later." = "We couldn't schedule this page, but we'll try again later."; - -/* Text displayed after the app fails to upload a scheduled post, it will attempt to upload it later. */ -"We couldn't schedule this post, but we'll try again later." = "We couldn't schedule this post, but we'll try again later."; - -/* Text displayed in notice after the app fails to upload a page, it will attempt to upload it later. */ -"We couldn't submit this page for review, but we'll try again later." = "We couldn't submit this page for review, but we'll try again later."; - -/* Text displayed in notice after the app fails to upload a post, it will attempt to upload it later. */ -"We couldn't submit this post for review, but we'll try again later." = "We couldn't submit this post for review, but we'll try again later."; - -/* Text displayed if a media couldn't be uploaded for a published page. */ -"We couldn't upload this media, and didn't publish the page." = "We couldn't upload this media, and didn't publish the page."; - -/* Text displayed if a media couldn't be uploaded for a published post. */ -"We couldn't upload this media, and didn't publish the post." = "We couldn't upload this media, and didn't publish the post."; - -/* Text displayed if a media couldn't be uploaded for a private page. */ -"We couldn't upload this media, and didn't publish this private page." = "We couldn't upload this media, and didn't publish this private page."; - -/* Text displayed if a media couldn't be uploaded for a private post. */ -"We couldn't upload this media, and didn't publish this private post." = "We couldn't upload this media, and didn't publish this private post."; - -/* Text displayed if a media couldn't be uploaded for a scheduled page. */ -"We couldn't upload this media, and didn't schedule this page." = "We couldn't upload this media, and didn't schedule this page."; - -/* Text displayed if a media couldn't be uploaded for a scheduled post. */ -"We couldn't upload this media, and didn't schedule this post." = "We couldn't upload this media, and didn't schedule this post."; - -/* Text displayed if a media couldn't be uploaded for a pending page. */ -"We couldn't upload this media, and didn't submit this page for review." = "We couldn't upload this media, and didn't submit this page for review."; - -/* Text displayed if a media couldn't be uploaded for a pending post. */ -"We couldn't upload this media, and didn't submit this post for review." = "We couldn't upload this media, and didn't submit this post for review."; - -/* Text displayed if a media couldnt be uploaded. */ -"We couldn't upload this media." = "We couldn't upload this media."; - /* Hint displayed when we fail to fetch the status of the backup in progress. */ "We couldn’t find the status to say how long your backup will take." = "We couldn’t find the status to say how long your backup will take."; @@ -11434,22 +11140,6 @@ This empty state component is displayed only when the app fails to load posts un /* Description for label when the actively scanning the users site */ "We will send you an email if security threats are found. In the meantime feel free to continue to use your site as normal, you can check back on progress at any time." = "We will send you an email if security threats are found. In the meantime feel free to continue to use your site as normal, you can check back on progress at any time."; -/* Title for notice displayed on canceling auto-upload published page - Title for notice displayed on canceling auto-upload published post */ -"We won't publish these changes." = "We won't publish these changes."; - -/* Title for notice displayed on canceling auto-upload of a draft page - Title for notice displayed on canceling auto-upload of a draft post */ -"We won't save the latest changes to your draft." = "We won't save the latest changes to your draft."; - -/* Title for notice displayed on canceling auto-upload of a scheduled page - Title for notice displayed on canceling auto-upload of a scheduled post */ -"We won't schedule these changes." = "We won't schedule these changes."; - -/* Title for notice displayed on canceling auto-upload pending page - Title for notice displayed on canceling auto-upload pending post */ -"We won't submit these changes for review." = "We won't submit these changes for review."; - /* Instructional text for the magic link login flow. */ "We'll email you a magic link that'll log you in instantly, no password needed. Hunt and peck no more!" = "We'll email you a magic link that'll log you in instantly, no password needed. Hunt and peck no more!"; @@ -11462,33 +11152,6 @@ This empty state component is displayed only when the app fails to load posts un /* Body text of the first alert preparing users to grant permission for us to send them push notifications. */ "We'll notify you when you get new followers, comments, and likes. Would you like to allow push notifications?" = "We'll notify you when you get new followers, comments, and likes. Would you like to allow push notifications?"; -/* Text displayed in notice after a page if published while offline. */ -"We'll publish the page when your device is back online." = "We'll publish the page when your device is back online."; - -/* Text displayed in notice after a post if published while offline. */ -"We'll publish the post when your device is back online." = "We'll publish the post when your device is back online."; - -/* Text displayed in notice after the app fails to upload a private page. */ -"We'll publish your private page when your device is back online." = "We'll publish your private page when your device is back online."; - -/* Text displayed in notice after the app fails to upload a private post. */ -"We'll publish your private post when your device is back online." = "We'll publish your private post when your device is back online."; - -/* Text displayed in notice after the app fails to upload a draft. */ -"We'll save your draft when your device is back online." = "We'll save your draft when your device is back online."; - -/* Text displayed after the app fails to upload a scheduled page. */ -"We'll schedule your page when your device is back online." = "We'll schedule your page when your device is back online."; - -/* Text displayed after the app fails to upload a scheduled post. */ -"We'll schedule your post when your device is back online." = "We'll schedule your post when your device is back online."; - -/* Text displayed in notice after the app fails to upload a page], it will attempt to upload it later. */ -"We'll submit your page for review when your device is back online." = "We'll submit your page for review when your device is back online."; - -/* Text displayed in notice after the app fails to upload a post, it will attempt to upload it later. */ -"We'll submit your post for review when your device is back online." = "We'll submit your post for review when your device is back online."; - /* Text confirming email address to be used for new account. */ "We'll use this email address to create your new WordPress.com account." = "We'll use this email address to create your new WordPress.com account."; @@ -11651,9 +11314,6 @@ This empty state component is displayed only when the app fails to load posts un /* Text display when the view when there aren't any Activities to display in the Activity Log */ "When you make changes to your site you'll be able to see your activity history here." = "When you make changes to your site you'll be able to see your activity history here."; -/* Title displayed in popup when user has the option to load unsaved changes */ -"Which version would you like to edit?" = "Which version would you like to edit?"; - /* An error message shown when a wpcom user provides the wrong password. */ "Whoops, something went wrong and we couldn't log you in. Please try again!" = "Whoops, something went wrong and we couldn't log you in. Please try again!"; @@ -12126,9 +11786,6 @@ from anywhere."; /* Example Likes notification displayed in the prologue carousel of the app. Number of likes should marked with * characters and will be displayed as bold text. */ "You received *50 likes* on your site today" = "You received *50 likes* on your site today"; -/* Message displayed in popup when user has the option to load unsaved changes. \n is a placeholder for a new line, and the two %@ are placeholders for the date of last save on this device, and date of last autosave on another device, respectively. */ -"You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %@\n\nFrom another device\nSaved on %@\n" = "You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %1$@\n\nFrom another device\nSaved on %2$@\n"; - /* Informs that the user has replied to this comment. Notification text - below a comment notification detail Text to look for */ @@ -12159,9 +11816,6 @@ from anywhere."; /* Error message displayed when unable to close user account due to being unauthorized. */ "You're not authorized to close the account." = "You're not authorized to close the account."; -/* Message displayed on a post's card when the post has unsaved changes */ -"You've made unsaved changes to this post" = "You've made unsaved changes to this post"; - /* Share extension error dialog text. */ "Your account does not have permission to upload media to this site. The Site Administrator can change these permissions." = "Your account does not have permission to upload media to this site. The Site Administrator can change these permissions."; @@ -12186,9 +11840,6 @@ from anywhere."; /* Help text that describes how the password should be. It appears while editing the password */ "Your password should be at least six characters long. To make it stronger, use upper and lower case letters, numbers, and symbols like ! \" ? $ % ^ & )." = "Your password should be at least six characters long. To make it stronger, use upper and lower case letters, numbers, and symbols like ! \" ? $ % ^ & )."; -/* This is a notification the user receives if they are trying to preview a post before the upload process is complete. */ -"Your post is currently being uploaded. Please wait until this completes." = "Your post is currently being uploaded. Please wait until this completes."; - /* Description on the first screen of the Blogging Reminders Settings flow called aftet post publishing. */ "Your post is publishing... in the meantime, set up your blogging reminders on days you want to post." = "Your post is publishing... in the meantime, set up your blogging reminders on days you want to post."; From 620addc193307d00cf4d2b31aaa5cb56f19f7f5e Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 11:54:49 -0400 Subject: [PATCH 20/30] Extract AudioRecorderVisualizerView and change animation a bit --- .../Voice/AudioRecorderVisualizerView.swift | 71 +++++++++++++++++++ .../Voice/VoiceToContentView.swift | 30 +------- .../Voice/VoiceToContentViewModel.swift | 19 +---- WordPress/WordPress.xcodeproj/project.pbxproj | 6 ++ 4 files changed, 82 insertions(+), 44 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift diff --git a/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift b/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift new file mode 100644 index 000000000000..14fbc3ea23d8 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift @@ -0,0 +1,71 @@ +import SwiftUI +import AVFoundation + +struct AudioRecorderVisualizerView: View { + let recorder: AVAudioRecorder + @StateObject private var viewModel = AudioRecorderVisualizerViewModel() + + var body: some View { + AudiowaveView(samples: viewModel.samples) + .onAppear { + viewModel.recorder = recorder + } + } +} + +private struct AudiowaveView: View { + let samples: [Float] + + var body: some View { + let indices = Array(samples.indices) + HStack(spacing: 6) { + ForEach(indices, id: \.self) { index in + let height = max(10, 80 * normalizePowerLevel(samples[index])) + Capsule(style: .continuous) + .fill(Color(uiColor: .brand)) + .frame(width: 10, height: CGFloat(height)) + .animation(.spring(duration: 0.2), value: height) + } + } + } + + // For human voice, the expected range is in about -40 to -20. + private func normalizePowerLevel(_ power: Float) -> Float { + let minPower: Float = -40 + let maxPower: Float = -25 + let value = (power + abs(minPower)) / abs(maxPower - minPower) + return min(1, max(0, value)) + } +} + +private final class AudioRecorderVisualizerViewModel: ObservableObject { + @Published private(set) var samples = Array(repeating: -160, count: numberOfSamples) + private(set) var currentSample = 0 + + var recorder: AVAudioRecorder? + + private weak var timer: Timer? + + deinit { + timer?.invalidate() + } + + init() { + timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in + self?.refresh() + } + } + + private func refresh() { + guard let recorder else { return } + recorder.updateMeters() + samples.removeFirst() + samples.append(recorder.averagePower(forChannel: 0)) + } +} + +private let numberOfSamples = 20 + +#Preview { + AudiowaveView(samples: [-37, -35, 10, 20, 30]) +} diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index 08d4c8e917bb..dd2ffde7f1f1 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -114,7 +114,9 @@ private struct VoiceToContentRecordingView: View { var body: some View { VStack { Spacer() - SpeechVisualizerView(viewModel: viewModel) + if let recorder = viewModel.audioRecorder { + AudioRecorderVisualizerView(recorder: recorder) + } Spacer() } } @@ -136,32 +138,6 @@ private struct VoiceToContentProcessingView: View { } } -private struct SpeechVisualizerView: View { - @ObservedObject fileprivate var viewModel: VoiceToContentViewModel - - var body: some View { - HStack(spacing: 6) { - ForEach(viewModel.soundSamples, id: \.self) { decibel in - Capsule(style: .continuous) - .fill(Color(uiColor: .brand)) - .frame(width: 10, height: self.normalizeSoundLevel(decibel, min: 1, max: 100)) - } - } - } - - /// Returns a normalized sound level between a specified min and max value. - /// - /// - Parameters: - /// - decibels: Decibels relative to full scale. 0 dBFS is assigned to the maximum possible digital level. - /// See https://en.wikipedia.org/wiki/DBFS for more details. - /// - min: Minimum value for the normalized sound level. - /// - max: Maximum value for the normalized sound level. - private func normalizeSoundLevel(_ decibels: Float, min minValue: CGFloat, max maxValue: CGFloat) -> CGFloat { - let level = max(minValue * 2, CGFloat(decibels) + 50) / 2 - return CGFloat(level * (maxValue / 25)) - } -} - private struct RecordButton: View { @ObservedObject fileprivate var viewModel: VoiceToContentViewModel diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift index d8cf67bf51ab..7f5401252ca0 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift @@ -11,8 +11,6 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder @Published private(set) var step: Step = .welcome @Published private(set) var loadingState: LoadingState? - @Published private(set) var soundSamples: [Float] = [] - private(set) var errorAlertMessage: String? @Published var isShowingErrorAlert = false @@ -27,11 +25,9 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder } private var audioSession: AVAudioSession? - private var audioRecorder: AVAudioRecorder? + private(set) var audioRecorder: AVAudioRecorder? private weak var timer: Timer? private let blog: Blog - private let numberOfSamples: Int - private var currentSample: Int private let completion: (String) -> Void enum Step { @@ -62,11 +58,8 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder timer?.invalidate() } - init(blog: Blog, numberOfSamples: Int = 20, _ completion: @escaping (String) -> Void) { + init(blog: Blog, _ completion: @escaping (String) -> Void) { self.blog = blog - self.numberOfSamples = numberOfSamples - self.currentSample = 0 - self.soundSamples = [Float](repeating: .zero, count: numberOfSamples) self.completion = completion self.title = Strings.title } @@ -196,8 +189,6 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder return } - self.updateSoundSamples(from: audioRecorder) - if #available(iOS 16.0, *) { self.subtitle = Duration.seconds(timeRemaining) .formatted(.time(pattern: .minuteSecond(padMinuteToLength: 2))) @@ -207,12 +198,6 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder } } - private func updateSoundSamples(from audioRecorder: AVAudioRecorder) { - audioRecorder.updateMeters() - soundSamples[currentSample] = audioRecorder.averagePower(forChannel: 0) - currentSample = (currentSample + 1) % numberOfSamples - } - func buttonDoneRecordingTapped() { WPAnalytics.track(.voiceToContentButtonDoneTapped) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index a6ffe6a68466..252166218854 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -517,6 +517,8 @@ 0C75E26F2A9F63CB00B784E5 /* MediaImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C75E26D2A9F63CB00B784E5 /* MediaImageService.swift */; }; 0C7762232AAFD39700E07A88 /* SiteMediaAddMediaMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7762222AAFD39700E07A88 /* SiteMediaAddMediaMenuController.swift */; }; 0C7762242AAFD39700E07A88 /* SiteMediaAddMediaMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7762222AAFD39700E07A88 /* SiteMediaAddMediaMenuController.swift */; }; + 0C7A48452C1727BE00B16BAA /* AudioRecorderVisualizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7A48442C1727BE00B16BAA /* AudioRecorderVisualizerView.swift */; }; + 0C7A48462C1727BE00B16BAA /* AudioRecorderVisualizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7A48442C1727BE00B16BAA /* AudioRecorderVisualizerView.swift */; }; 0C7D481A2A4DB9300023CF84 /* blaze-search-response.json in Resources */ = {isa = PBXBuildFile; fileRef = 0C7D48192A4DB9300023CF84 /* blaze-search-response.json */; }; 0C7E09202A4286A00052324C /* PostMetaButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E091F2A4286A00052324C /* PostMetaButton.m */; }; 0C7E09212A4286A00052324C /* PostMetaButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E091F2A4286A00052324C /* PostMetaButton.m */; }; @@ -6330,6 +6332,7 @@ 0C749D792B0543D0004CB468 /* WPImageViewController+Swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WPImageViewController+Swift.swift"; sourceTree = ""; }; 0C75E26D2A9F63CB00B784E5 /* MediaImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaImageService.swift; sourceTree = ""; }; 0C7762222AAFD39700E07A88 /* SiteMediaAddMediaMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteMediaAddMediaMenuController.swift; sourceTree = ""; }; + 0C7A48442C1727BE00B16BAA /* AudioRecorderVisualizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderVisualizerView.swift; sourceTree = ""; }; 0C7D48192A4DB9300023CF84 /* blaze-search-response.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blaze-search-response.json"; sourceTree = ""; }; 0C7E091F2A4286A00052324C /* PostMetaButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PostMetaButton.m; sourceTree = ""; }; 0C7E09222A4286AA0052324C /* PostMetaButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PostMetaButton.h; sourceTree = ""; }; @@ -10535,6 +10538,7 @@ children = ( 0C13ACC62BF406CB00FF7405 /* VoiceToContentView.swift */, 0C13ACC82BF406E700FF7405 /* VoiceToContentViewModel.swift */, + 0C7A48442C1727BE00B16BAA /* AudioRecorderVisualizerView.swift */, ); path = Voice; sourceTree = ""; @@ -21859,6 +21863,7 @@ 4A072CD229093704006235BE /* AsyncBlockOperation.swift in Sources */, 0CA10F6D2ADAE86D00CE75AC /* PostSearchSuggestionsService.swift in Sources */, 02761EC02270072F009BAF0F /* BlogDetailsViewController+SectionHelpers.swift in Sources */, + 0C7A48452C1727BE00B16BAA /* AudioRecorderVisualizerView.swift in Sources */, 984B138E21F65F870004B6A2 /* SiteStatsPeriodTableViewController.swift in Sources */, 83F1BED22BA4D1190057BC0F /* ReaderTopicService+FollowedSites.swift in Sources */, 433ADC1D223B2A7F00ED9DE1 /* TextBundleWrapper.m in Sources */, @@ -24728,6 +24733,7 @@ FABB21B72602FC2C00C8785C /* TopicsCollectionView.swift in Sources */, 08A250FD28D9F0E200F50420 /* CommentDetailInfoViewModel.swift in Sources */, FABB21B82602FC2C00C8785C /* SiteSegmentsService.swift in Sources */, + 0C7A48462C1727BE00B16BAA /* AudioRecorderVisualizerView.swift in Sources */, FABB21B92602FC2C00C8785C /* BlogListDataSource.swift in Sources */, FABB21BA2602FC2C00C8785C /* MediaItemHeaderView.swift in Sources */, F158541A267D3B6000A2E966 /* BloggingRemindersStore.swift in Sources */, From 164aa68cfa344b6176edec46f55700676c910c4f Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 12:12:42 -0400 Subject: [PATCH 21/30] Show VoiceToContent in smaller window on iPad --- .../Blog/My Site/MySiteViewController+FAB.swift | 1 + .../Voice/AudioRecorderVisualizerView.swift | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+FAB.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+FAB.swift index 2418554df3c3..2b23b5c26f32 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+FAB.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+FAB.swift @@ -70,6 +70,7 @@ extension MySiteViewController { if UIDevice.isPad() { host.modalPresentationStyle = .formSheet + host.preferredContentSize = CGSize(width: 380, height: 480) } else { if let sheetController = host.sheetPresentationController { sheetController.detents = [.medium()] diff --git a/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift b/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift index 14fbc3ea23d8..458592d8c8ce 100644 --- a/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift +++ b/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift @@ -24,15 +24,15 @@ private struct AudiowaveView: View { Capsule(style: .continuous) .fill(Color(uiColor: .brand)) .frame(width: 10, height: CGFloat(height)) - .animation(.spring(duration: 0.2), value: height) + .animation(.spring(duration: 0.1), value: height) } } } - // For human voice, the expected range is in about -40 to -20. private func normalizePowerLevel(_ power: Float) -> Float { - let minPower: Float = -40 - let maxPower: Float = -25 + // About the expected range for voice + let minPower: Float = -42 + let maxPower: Float = -22 let value = (power + abs(minPower)) / abs(maxPower - minPower) return min(1, max(0, value)) } @@ -51,7 +51,7 @@ private final class AudioRecorderVisualizerViewModel: ObservableObject { } init() { - timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in + timer = Timer.scheduledTimer(withTimeInterval: 0.033, repeats: true) { [weak self] _ in self?.refresh() } } From 9b6df4117ca0966e1c73c315ea97c1ebae1403aa Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 15:46:15 -0400 Subject: [PATCH 22/30] Allow user to retry on processing step --- .../Voice/VoiceToContentView.swift | 10 ++++++---- .../Voice/VoiceToContentViewModel.swift | 20 +++++++++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index 62c261af1a24..b0b92fb1d4d2 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -34,7 +34,7 @@ struct VoiceToContentView: View { case .recording: VoiceToContentRecordingView(viewModel: viewModel) case .processing: - VoiceToContenProcessingView(viewModel: viewModel) + VoiceToContentProcessingView(viewModel: viewModel) } } @@ -195,15 +195,17 @@ private struct RecordButton: View { } } -private struct VoiceToContenProcessingView: View { +private struct VoiceToContentProcessingView: View { @ObservedObject fileprivate var viewModel: VoiceToContentViewModel var body: some View { VStack { Spacer() - ProgressView() - .controlSize(.large) + if case .loading = viewModel.loadingState { + ProgressView() + .controlSize(.large) + } Spacer() } diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift index f4550e473626..d08bf1cf28c3 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift @@ -206,18 +206,22 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder // MARK: - Processing private func startProcessing() { - guard let fileURL = audioRecorder?.url else { - wpAssertionFailure("audio-recorder: file missing") - return - } audioRecorder?.stop() - audioRecorder = nil audioSession = nil timer?.invalidate() title = Strings.titleProcessing subtitle = "" step = .processing + + processFile() + } + + private func processFile() { + guard let fileURL = audioRecorder?.url else { + wpAssertionFailure("audio-recorder: file missing") + return + } Task { await self.process(fileURL: fileURL) } @@ -225,6 +229,8 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder @MainActor private func process(fileURL: URL) async { + loadingState = .loading + guard let api = blog.wordPressComRestApi() else { wpAssertionFailure("only available for .com sites") return @@ -244,7 +250,9 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder } self.completion(content) } catch { - showError(error) + loadingState = .failed(message: error.localizedDescription) { [weak self] in + self?.processFile() + } } } From a8225a63c9e94675ca38911da5065f41c15d2a81 Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 15:47:14 -0400 Subject: [PATCH 23/30] Use transcription if processing fails --- .../Classes/ViewRelated/Voice/VoiceToContentViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift index d08bf1cf28c3..846072c99613 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift @@ -238,14 +238,14 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder let service = JetpackAIServiceRemote(wordPressComRestApi: api, siteID: blog.dotComID ?? 0) do { let token = try await service.getAuthorizationToken() - // TODO: this doesn't seem to handle 401 and other "error" status codes correctly let transcription = try await service.transcribeAudio(from: fileURL, token: token) let content = try await service.makePostContent(fromPlainText: transcription, token: token) // "the __JETPACK_AI_ERROR__ is a special marker we ask GPT to add to // the request when it can’t understand the request for any reason" guard content != "__JETPACK_AI_ERROR__" else { - showError(VoiceToContentError.cantUnderstandRequest) + // There is no point in retrying, but the transcription can still be useful. + self.completion(transcription) return } self.completion(content) From 5a5c9a94774916e130abd7bfabd67a9d9fa160fb Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 16:04:10 -0400 Subject: [PATCH 24/30] Update WordPressKit --- Podfile | 3 +-- Podfile.lock | 11 ++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Podfile b/Podfile index 349b27b3b5e5..44edd3ca857f 100644 --- a/Podfile +++ b/Podfile @@ -55,8 +55,7 @@ def gravatar end def wordpress_kit - pod 'WordPressKit', '~> 17.2.0' - # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '' + pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: 'c334dad3c0e8fced5a61c5bf1d745d23d1924a9c' # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: '' # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', tag: '' # pod 'WordPressKit', path: '../WordPressKit-iOS' diff --git a/Podfile.lock b/Podfile.lock index 60238a073482..84fda843164e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -121,7 +121,7 @@ DEPENDENCIES: - SwiftLint (= 0.54.0) - WordPress-Editor-iOS (~> 1.19.11) - WordPressAuthenticator (>= 9.0.8, ~> 9.0) - - WordPressKit (~> 17.2.0) + - WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, commit `c334dad3c0e8fced5a61c5bf1d745d23d1924a9c`) - WordPressShared (>= 2.3.1, ~> 2.3) - WordPressUI (~> 1.16) - ZendeskSupportSDK (= 5.3.0) @@ -160,7 +160,6 @@ SPEC REPOS: - SVProgressHUD - SwiftLint - UIDeviceIdentifier - - WordPressKit - WordPressUI - wpxmlrpc - ZendeskCommonUISDK @@ -178,11 +177,17 @@ EXTERNAL SOURCES: :tag: 0.2.0 Gutenberg: :podspec: https://cdn.a8c-ci.services/gutenberg-mobile/Gutenberg-v1.120.0.podspec + WordPressKit: + :commit: c334dad3c0e8fced5a61c5bf1d745d23d1924a9c + :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git CHECKOUT OPTIONS: FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 + WordPressKit: + :commit: c334dad3c0e8fced5a61c5bf1d745d23d1924a9c + :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git SPEC CHECKSUMS: Alamofire: 02b772c9910e8eba1a079227c32fbd9e46c90a24 @@ -229,6 +234,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: d170fa8e270b2a32bef9dcdcabff5b8f1a5deced -PODFILE CHECKSUM: 739f6071356a5d136381ca7788feb63eab4a3062 +PODFILE CHECKSUM: c0cebe0945c26f689b810c6ea6ed7ad16580048d COCOAPODS: 1.15.2 From 743c0ba136e49b6c83eb96c091770e251dbb310b Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 16:06:47 -0400 Subject: [PATCH 25/30] Show upgradeURL from feature info --- .../ViewRelated/Voice/VoiceToContentViewModel.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift index f4550e473626..5c705616692a 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift @@ -24,6 +24,7 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder return isEligible } + private var featureInfo: JetpackAssistantFeatureDetails? private var audioSession: AVAudioSession? private var audioRecorder: AVAudioRecorder? private weak var timer: Timer? @@ -99,6 +100,7 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder } private func didFetchFeatureDetails(_ info: JetpackAssistantFeatureDetails) { + self.featureInfo = info self.loadingState = nil if info.isSiteUpdateRequired == true { self.subtitle = Strings.subtitleRequestsAvailable + " 0" @@ -113,11 +115,12 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder func buttonUpgradeTapped() { WPAnalytics.track(.voiceToContentButtonUpgradeTapped) - // TODO: this does not work - guard let siteURL = blog.url.flatMap(URL.init) else { - return wpAssertionFailure("invalid blog URL") + guard let featureInfo else { + return wpAssertionFailure("feature info missing") + } + guard let upgradeURL = featureInfo.upgradeURL.flatMap(URL.init) else { + return wpAssertionFailure("invalid or missing upgrade URL") } - let upgradeURL = siteURL.appendingPathComponent("/wp-admin/admin.php?page=my-jetpack#/add-jetpack-ai") UIApplication.shared.open(upgradeURL) } From acb42dbfa338871c7ce65cb17dcfa9318151963d Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 10 Jun 2024 16:19:36 -0400 Subject: [PATCH 26/30] Hide the upgrade button it no upgradeURL present --- .../ViewRelated/Voice/VoiceToContentView.swift | 10 ++++++---- .../ViewRelated/Voice/VoiceToContentViewModel.swift | 12 ++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index 62c261af1a24..e826ab526538 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -96,10 +96,12 @@ private struct VoiceToContentWelcomeView: View { VStack(spacing: 4) { Text(Strings.notEnoughRequests) .multilineTextAlignment(.center) - Button(action: viewModel.buttonUpgradeTapped) { - HStack { - Text(Strings.upgrade) - Image("icon-post-actionbar-view") + if let upgradeURL = viewModel.upgradeURL { + Button(action: { viewModel.buttonUpgradeTapped(withUpgradeURL: upgradeURL) }) { + HStack { + Text(Strings.upgrade) + Image("icon-post-actionbar-view") + } } } }.frame(maxWidth: 320) diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift index 5c705616692a..ab4c50876877 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentViewModel.swift @@ -24,6 +24,9 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder return isEligible } + var upgradeURL: URL? { + featureInfo?.upgradeURL.flatMap(URL.init) + } private var featureInfo: JetpackAssistantFeatureDetails? private var audioSession: AVAudioSession? private var audioRecorder: AVAudioRecorder? @@ -112,15 +115,8 @@ final class VoiceToContentViewModel: NSObject, ObservableObject, AVAudioRecorder } } - func buttonUpgradeTapped() { + func buttonUpgradeTapped(withUpgradeURL upgradeURL: URL) { WPAnalytics.track(.voiceToContentButtonUpgradeTapped) - - guard let featureInfo else { - return wpAssertionFailure("feature info missing") - } - guard let upgradeURL = featureInfo.upgradeURL.flatMap(URL.init) else { - return wpAssertionFailure("invalid or missing upgrade URL") - } UIApplication.shared.open(upgradeURL) } From 74f2277ada4a6126a535b7ecf43a56a7bc02e342 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 11 Jun 2024 12:11:34 +1000 Subject: [PATCH 27/30] Try giving Fastlane a longer timeout for UI tests launch issue See https://github.com/fastlane/fastlane/issues/20919#issuecomment-1344976529 --- .buildkite/commands/run-ui-tests.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.buildkite/commands/run-ui-tests.sh b/.buildkite/commands/run-ui-tests.sh index fe169bf885ad..51298df2ac4f 100755 --- a/.buildkite/commands/run-ui-tests.sh +++ b/.buildkite/commands/run-ui-tests.sh @@ -26,7 +26,9 @@ echo "--- 🔬 Testing" xcrun simctl list >> /dev/null rake mocks & set +e -bundle exec fastlane test_without_building name:Jetpack device:"$DEVICE" +# See https://github.com/fastlane/fastlane/issues/20919#issuecomment-1344976529 +FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=120 \ + bundle exec fastlane test_without_building name:Jetpack device:"$DEVICE" TESTS_EXIT_STATUS=$? set -e From 1688918e755fb849e35e7dccac6b9e9c35e217b0 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 11 Jun 2024 16:50:47 +1000 Subject: [PATCH 28/30] Update target iPad Simulator for CI Could it be that the failure we are seeing in the Xcode 15.4 image are due to which iPad it is trying to load? --- .buildkite/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 04a622f0be67..34834c9fe426 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -76,7 +76,7 @@ steps: context: "UI Tests (iPhone)" - label: "🔬 :jetpack: UI Tests (iPad)" - command: .buildkite/commands/run-ui-tests.sh 'iPad Pro (12.9-inch) (6th generation)' + command: .buildkite/commands/run-ui-tests.sh 'iPad Pro 13-inch (M4)' depends_on: "build_jetpack" plugins: [$CI_TOOLKIT_PLUGIN] artifact_paths: From 9b1e734336a3f6ecc187946f8d83c63b79f28c46 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 11 Jun 2024 19:48:14 +1000 Subject: [PATCH 29/30] Revert "Try giving Fastlane a longer timeout for UI tests launch issue" This reverts commit 74f2277ada4a6126a535b7ecf43a56a7bc02e342 as it proved insufficient to fix the issue. --- .buildkite/commands/run-ui-tests.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.buildkite/commands/run-ui-tests.sh b/.buildkite/commands/run-ui-tests.sh index 51298df2ac4f..fe169bf885ad 100755 --- a/.buildkite/commands/run-ui-tests.sh +++ b/.buildkite/commands/run-ui-tests.sh @@ -26,9 +26,7 @@ echo "--- 🔬 Testing" xcrun simctl list >> /dev/null rake mocks & set +e -# See https://github.com/fastlane/fastlane/issues/20919#issuecomment-1344976529 -FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=120 \ - bundle exec fastlane test_without_building name:Jetpack device:"$DEVICE" +bundle exec fastlane test_without_building name:Jetpack device:"$DEVICE" TESTS_EXIT_STATUS=$? set -e From d42af2b0706498dda7c3948dd23c4959aea83574 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 11 Jun 2024 13:06:18 -0400 Subject: [PATCH 30/30] Update accessibiility identifiers --- .../Voice/AudioRecorderVisualizerView.swift | 4 ++-- .../ViewRelated/Voice/VoiceToContentView.swift | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift b/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift index 458592d8c8ce..8dc80512364a 100644 --- a/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift +++ b/WordPress/Classes/ViewRelated/Voice/AudioRecorderVisualizerView.swift @@ -31,7 +31,7 @@ private struct AudiowaveView: View { private func normalizePowerLevel(_ power: Float) -> Float { // About the expected range for voice - let minPower: Float = -42 + let minPower: Float = -41 let maxPower: Float = -22 let value = (power + abs(minPower)) / abs(maxPower - minPower) return min(1, max(0, value)) @@ -51,7 +51,7 @@ private final class AudioRecorderVisualizerViewModel: ObservableObject { } init() { - timer = Timer.scheduledTimer(withTimeInterval: 0.033, repeats: true) { [weak self] _ in + timer = Timer.scheduledTimer(withTimeInterval: 0.04, repeats: true) { [weak self] _ in self?.refresh() } } diff --git a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift index dd2ffde7f1f1..0ac76bb21030 100644 --- a/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift +++ b/WordPress/Classes/ViewRelated/Voice/VoiceToContentView.swift @@ -69,6 +69,7 @@ struct VoiceToContentView: View { .font(.title2) .foregroundStyle(.secondary, Color(uiColor: .secondarySystemFill)) } + .accessibilityLabel(Strings.close) .buttonStyle(.plain) } } @@ -144,18 +145,18 @@ private struct RecordButton: View { private var isRecording: Bool { viewModel.step == .recording } var body: some View { - VStack(spacing: 16) { - Button(action: isRecording ? viewModel.buttonDoneRecordingTapped : viewModel.buttonRecordTapped) { + Button(action: isRecording ? viewModel.buttonDoneRecordingTapped : viewModel.buttonRecordTapped) { + VStack(spacing: 16) { if #available(iOS 17.0, *) { icon .contentTransition(.symbolEffect(.replace, options: .speed(5))) } else { icon } + Text(isRecording ? Strings.done : Strings.beginRecording) + .font(.callout) + .tint(.primary) } - - Text(isRecording ? Strings.done : Strings.beginRecording) - .foregroundStyle(.primary) } .opacity(viewModel.isButtonRecordEnabled ? 1 : 0.5) .disabled(!viewModel.isButtonRecordEnabled) @@ -185,4 +186,5 @@ private enum Strings { static let notEnoughRequests = NSLocalizedString("postFromAudio.notEnoughRequestsMessage", value: "You don't have enough requests available to create a post from audio.", comment: "Message for 'not eligible' state view") static let upgrade = NSLocalizedString("postFromAudio.buttonUpgrade", value: "Upgrade for more requests", comment: "Button title") static let ok = NSLocalizedString("postFromAudio.ok", value: "OK", comment: "Button title") + static let close = NSLocalizedString("postFromAudio.close", value: "Close", comment: "Button close title (only used as an accessibility identifier)") }