From 3f2baa8b7c8e0a5881758a3616015fbf29793697 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 16 Jun 2023 10:34:53 -0400 Subject: [PATCH 001/175] Add SiteSettingsRelatedPostsView to replace the old version --- .../SiteSettingsRelatedPostsView.swift | 137 ++++++++++++++++++ .../SiteSettingsViewController+Swift.swift | 7 + .../SiteSettingsViewController.m | 7 - WordPress/WordPress.xcodeproj/project.pbxproj | 6 + 4 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift new file mode 100644 index 000000000000..921adc719499 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift @@ -0,0 +1,137 @@ +import Foundation +import SwiftUI +import SVProgressHUD +import WordPressShared + +struct RelatedPostsSettingsView: View { + private let blog: Blog + @ObservedObject private var settings: BlogSettings + + init(blog: Blog) { + self.blog = blog + assert(blog.settings != nil, "Settings should never be nil") + self.settings = blog.settings ?? BlogSettings(context: ContextManager.shared.mainContext) + } + + var body: some View { + Form { + settingsSection + if settings.relatedPostsEnabled { + previewsSection + } + } + .toggleStyle(SwitchToggleStyle(tint: Color(UIColor.jetpackGreen))) + .onChange(of: settings.relatedPostsEnabled) { + save(field: "show_related_posts", value: $0) + } + .onChange(of: settings.relatedPostsShowHeadline) { + save(field: "show_related_posts_header", value: $0) + } + .onChange(of: settings.relatedPostsShowThumbnails) { + save(field: "show_related_posts_thumbnail", value: $0) + } + .navigationTitle(Strings.title) + .navigationBarTitleDisplayMode(.inline) + } + + private var settingsSection: some View { + let section = Section { + Toggle(Strings.showRelatedPosts, isOn: $settings.relatedPostsEnabled) + if settings.relatedPostsEnabled { + Toggle(Strings.showHeader, isOn: $settings.relatedPostsShowHeadline) + Toggle(Strings.showThumbnail, isOn: $settings.relatedPostsShowThumbnails) + } + } footer: { + Text(Strings.optionsFooter) + } + if #available(iOS 15, *) { + return section.tint(Color(UIColor.jetpackGreen)) + } else { + return section.toggleStyle(SwitchToggleStyle(tint: Color(UIColor.jetpackGreen))) + } + } + + private var previewsSection: some View { + Section { + VStack(spacing: settings.relatedPostsShowThumbnails ? 10 : 5) { + if settings.relatedPostsShowHeadline { + Text(Strings.relatedPostsHeader) + .font(.system(size: 11, weight: .semibold)) + .foregroundColor(Color(UIColor.neutral)) + .frame(maxWidth: .infinity, alignment: .leading) + } + ForEach(PreviewViewModel.previews, content: makePreview) + } + } header: { + Text(Strings.previewsHeader) + } + } + + private func makePreview(for viewModel: PreviewViewModel) -> some View { + VStack(spacing: 5) { + if settings.relatedPostsShowThumbnails { + Image(viewModel.imageName) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(height: 96) + .clipped() + } + HStack { + VStack(alignment: .leading, spacing: 0) { + Text(viewModel.title) + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(Color(UIColor.neutral(.shade70))) + Text(viewModel.details) + .font(.system(size: 11).italic()) + .foregroundColor(Color(UIColor.neutral)) + } + Spacer() + } + } + } + + private func save(field: String, value: Any) { + WPAnalytics.trackSettingsChange("related_posts", fieldName: field, value: value) + BlogService(coreDataStack: ContextManager.shared).updateSettings(for: blog, success: nil, failure: { _ in + SVProgressHUD.showDismissibleError(withStatus: Strings.saveFailed) + }) + } +} + +private struct PreviewViewModel: Identifiable { + let id = UUID() + let title: String + let details: String + let imageName: String + + static let previews: [PreviewViewModel] = [ + PreviewViewModel( + title: NSLocalizedString("relatedPostsSettings.preview1.title", value: "Big iPhone/iPad Update Now Available", comment: "Text for related post cell preview"), + details: NSLocalizedString("relatedPostsSettings.preview1.details", value: "in \"Mobile\"", comment: "Text for related post cell preview"), + imageName: "relatedPostsPreview1" + ), + PreviewViewModel( + title: NSLocalizedString("relatedPostsSettings.preview2.title", value: "The WordPress for Android App Gets a Big Facelift", comment: "Text for related post cell preview"), + details: NSLocalizedString("relatedPostsSettings.preview2.details", value: "in \"Apps\"", comment: "Text for related post cell preview"), + imageName: "relatedPostsPreview2" + ), + PreviewViewModel( + title: NSLocalizedString("relatedPostsSettings.preview3.title", value: "Upgrade Focus: VideoPress For Weddings", comment: "Text for related post cell preview"), + details: NSLocalizedString("relatedPostsSettings.preview3.details", value: "in \"Upgrade\"", comment: "Text for related post cell preview"), + imageName: "relatedPostsPreview3" + ) + ] +} + +private extension RelatedPostsSettingsView { + enum Strings { + static let title = NSLocalizedString("relatedPostsSettings.title", value: "Related Posts", comment: "Title for screen that allows configuration of your blog/site related posts settings.") + static let showRelatedPosts = NSLocalizedString("relatedPostsSettings.showRelatedPosts", value: "Show Related Posts", comment: "Label for configuration switch to enable/disable related posts") + static let showHeader = NSLocalizedString("relatedPostsSettings.showHeader", value: "Show Header", comment: "Label for configuration switch to show/hide the header for the related posts section") + static let showThumbnail = NSLocalizedString("relatedPostsSettings.showThumbnail", value: "Show Images", comment: "Label for configuration switch to show/hide images thumbnail for the related posts") + static let optionsFooter = NSLocalizedString("relatedPostsSettings.optionsFooter", value: "Related Posts displays relevant content from your site below your posts", comment: "Information of what related post are and how they are presented") + static let previewsHeader = NSLocalizedString("relatedPostsSettings.previewsHeaders", value: "Preview", comment: "Section title for related posts section preview") + static let relatedPostsHeader = NSLocalizedString("relatedPostsSettings.relatedPostsHeader", value: "Related Posts", comment: "Label for Related Post header preview") + static let saveFailed = NSLocalizedString("relatedPostsSettings.settingsUpdateFailed", value: "Settings update failed", comment: "Message to show when setting save failed") + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift index 07616be964ef..38ced37b41ba 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift @@ -1,6 +1,7 @@ import Foundation import SwiftUI import WordPressFlux +import SwiftUI // This is just a wrapper for the receipts, since Receipt isn't exposed to Obj-C @objc class TimeZoneObserver: NSObject { @@ -145,6 +146,12 @@ extension SiteSettingsViewController { navigationController?.pushViewController(speedUpSiteSettingsViewController, animated: true) } + @objc func showRelatedPostsSettings() { + let view = RelatedPostsSettingsView(blog: blog) + let host = UIHostingController(rootView: view) + navigationController?.pushViewController(host, animated: true) + } + // MARK: Footers @objc(getTrafficSettingsSectionFooterView) diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m index c70232b92e36..5964940bcea7 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m @@ -859,13 +859,6 @@ - (void)showPostFormatSelector [self.navigationController pushViewController:vc animated:YES]; } -- (void)showRelatedPostsSettings -{ - RelatedPostsSettingsViewController *relatedPostsViewController = [[RelatedPostsSettingsViewController alloc] initWithBlog:self.blog]; - - [self.navigationController pushViewController:relatedPostsViewController animated:YES]; -} - - (void)tableView:(UITableView *)tableView didSelectInWritingSectionRow:(NSInteger)row { NSInteger writingRow = [self.writingSectionRows[row] integerValue]; diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 2fe87e483c4b..184b0f7f339e 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -362,6 +362,8 @@ 0C896DE42A3A7BDC00D7D4E7 /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DDF2A3A763400D7D4E7 /* SettingsCell.swift */; }; 0C896DE52A3A7C1F00D7D4E7 /* SiteVisibility+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */; }; 0C896DE72A3A832B00D7D4E7 /* SiteVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DE62A3A832B00D7D4E7 /* SiteVisibilityTests.swift */; }; + 0C71959B2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; + 0C71959C2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; 0CB4056B29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */; }; 0CB4056C29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */; }; 0CB4056E29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */; }; @@ -6057,6 +6059,7 @@ 0C896DDF2A3A763400D7D4E7 /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteVisibility+Extensions.swift"; sourceTree = ""; }; 0C896DE62A3A832B00D7D4E7 /* SiteVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteVisibilityTests.swift; sourceTree = ""; }; + 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingsRelatedPostsView.swift; sourceTree = ""; }; 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationService.swift; sourceTree = ""; }; 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationServiceTests.swift; sourceTree = ""; }; 0CB4057029C8DCF4008EED0A /* BlogDashboardPersonalizationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationViewModel.swift; sourceTree = ""; }; @@ -11229,6 +11232,7 @@ 8313B9ED298B1ACD000AF26E /* SiteSettingsViewController+Blogging.swift */, 82C420751FE44BD900CFB15B /* SiteSettingsViewController+Swift.swift */, 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */, + 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */, 3F43603723F369A9001DEE70 /* Related Posts */, ); path = "Site Settings"; @@ -22023,6 +22027,7 @@ 8B74A9A8268E3C68003511CE /* RewindStatus+multiSite.swift in Sources */, 3F5B3EAF23A851330060FF1F /* ReaderReblogPresenter.swift in Sources */, 7E3E7A6220E44E6A0075D159 /* BodyContentGroup.swift in Sources */, + 0C71959B2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */, FEA088012696E7F600193358 /* ListTableHeaderView.swift in Sources */, 17AD36D51D36C1A60044B10D /* WPStyleGuide+Search.swift in Sources */, F1E3536B25B9F74C00992E3A /* WindowManager.swift in Sources */, @@ -24939,6 +24944,7 @@ FABB24542602FC2C00C8785C /* StoriesIntroViewController.swift in Sources */, FABB24552602FC2C00C8785C /* WPStyleGuide+Suggestions.m in Sources */, FABB24562602FC2C00C8785C /* SiteStatsInsightsViewModel.swift in Sources */, + 0C71959C2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */, FABB24572602FC2C00C8785C /* CheckmarkTableViewCell.swift in Sources */, FABB24582602FC2C00C8785C /* ReaderManageScenePresenter.swift in Sources */, FABB24592602FC2C00C8785C /* MediaService.swift in Sources */, From 191c8764c20c673797fb991ff88952ffe8774788 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 16 Jun 2023 12:44:43 -0400 Subject: [PATCH 002/175] Remove RelatedPostsSettingsViewController.h that was replaced --- .../RelatedPostsPreviewTableViewCell.h | 11 - .../RelatedPostsPreviewTableViewCell.m | 211 ------------------ .../RelatedPostsSettingsViewController.h | 9 - .../SiteSettingsViewController.m | 1 - WordPress/WordPress.xcodeproj/project.pbxproj | 24 -- 5 files changed, 256 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsPreviewTableViewCell.h delete mode 100644 WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsPreviewTableViewCell.m delete mode 100644 WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsSettingsViewController.h diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsPreviewTableViewCell.h b/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsPreviewTableViewCell.h deleted file mode 100644 index 914725e40f5c..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsPreviewTableViewCell.h +++ /dev/null @@ -1,11 +0,0 @@ -#import -#import - -@interface RelatedPostsPreviewTableViewCell : WPTableViewCell - -@property (nonatomic, assign) BOOL enabledHeader; -@property (nonatomic, assign) BOOL enabledImages; - -- (CGFloat)heightForWidth:(CGFloat)availableWidth; - -@end diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsPreviewTableViewCell.m b/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsPreviewTableViewCell.m deleted file mode 100644 index 83729754bd1b..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsPreviewTableViewCell.m +++ /dev/null @@ -1,211 +0,0 @@ -#import "RelatedPostsPreviewTableViewCell.h" -#import -#import -#import "WordPress-Swift.h" - -static CGFloat HorizontalMargin = 0.0; -static CGFloat VerticalMargin = 5.0; -static CGFloat ImageHeight = 96.0; - -@interface RelatedPostsPreview : NSObject - -@property (nonatomic, copy) NSString *title; -@property (nonatomic, copy) NSString *site; -@property (nonatomic, copy) NSString *imageName; - -@property (nonatomic, strong) UILabel *titleLabel; -@property (nonatomic, strong) UILabel *siteLabel; -@property (nonatomic, strong) UIImageView *imageView; - -- (instancetype)initWithTitle:(NSString *)title site:(NSString *)site imageName:(NSString *)imageName; - -@end - -@implementation RelatedPostsPreview - -- (instancetype)initWithTitle:(NSString *)title site:(NSString *)site imageName:(NSString *)imageName -{ - self = [super init]; - if (self) { - _title = title; - _site = site; - _imageName = imageName; - } - - return self; -} - -- (UILabel *)titleLabel -{ - if (!_titleLabel) { - _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - _titleLabel.textColor = [UIColor murielNeutral70]; - _titleLabel.font = [WPFontManager systemSemiBoldFontOfSize:14.0]; - _titleLabel.numberOfLines = 0; - } - _titleLabel.text = self.title; - return _titleLabel; -} - -- (UILabel *)siteLabel -{ - if (!_siteLabel) { - _siteLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - _siteLabel.textColor = [UIColor murielNeutral]; - _siteLabel.font = [WPFontManager systemItalicFontOfSize:11.0]; - _siteLabel.numberOfLines = 0; - } - _siteLabel.text = self.site; - return _siteLabel; -} - -- (UIImageView *)imageView -{ - if (!_imageView){ - _imageView = [[UIImageView alloc] init]; - [_imageView setContentMode:UIViewContentModeScaleAspectFill]; - [_imageView setClipsToBounds:YES]; - } - _imageView.image = [UIImage imageNamed:self.imageName]; - return _imageView; -} - -@end - -// Temporary container view for helping to follow readable margins until we can properly adopt this view for readability. -// Brent C. Jul/22/2016 -@protocol RelatedPostsPreviewReadableContentViewDelegate; - -@interface RelatedPostsPreviewReadableContentView : UIView -@property (nonatomic, weak) id delegate; -@end - -@protocol RelatedPostsPreviewReadableContentViewDelegate -- (void)postPreviewReadableContentViewDidLayoutSubviews:(RelatedPostsPreviewReadableContentView *)readableContentView; -@end - -@interface RelatedPostsPreviewTableViewCell() - -@property (nonatomic, strong) UIView *readableContentView; -@property (nonatomic, strong) UILabel *headerLabel; -@property (nonatomic, strong) NSArray *previewPosts; - -@end; - -@implementation RelatedPostsPreviewTableViewCell - -- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier -{ - self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]; - if (self) { - - RelatedPostsPreviewReadableContentView *readableContentView = [[RelatedPostsPreviewReadableContentView alloc] init]; - readableContentView.delegate = self; - readableContentView.translatesAutoresizingMaskIntoConstraints = NO; - readableContentView.backgroundColor = [UIColor clearColor]; - [self.contentView addSubview:readableContentView]; - - UILayoutGuide *readableGuide = self.contentView.readableContentGuide; - [NSLayoutConstraint activateConstraints:@[ - [readableContentView.leadingAnchor constraintEqualToAnchor:readableGuide.leadingAnchor], - [readableContentView.trailingAnchor constraintEqualToAnchor:readableGuide.trailingAnchor], - [readableContentView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor], - [readableContentView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor] - ]]; - _readableContentView = readableContentView; - - _enabledHeader = YES; - _enabledImages = YES; - _headerLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - _headerLabel.text = NSLocalizedString(@"Related Posts", @"Label for Related Post header preview"); - _headerLabel.textColor = [UIColor murielNeutral]; - _headerLabel.font = [WPFontManager systemSemiBoldFontOfSize:11.0]; - [readableContentView addSubview:_headerLabel]; - - RelatedPostsPreview *preview1 = [[RelatedPostsPreview alloc] initWithTitle:NSLocalizedString(@"Big iPhone/iPad Update Now Available", @"Text for related post cell preview") - site:NSLocalizedString(@"in \"Mobile\"", @"Text for related post cell preview") - imageName:@"relatedPostsPreview1"]; - RelatedPostsPreview *preview2 = [[RelatedPostsPreview alloc] initWithTitle:NSLocalizedString(@"The WordPress for Android App Gets a Big Facelift", @"Text for related post cell preview") - site:NSLocalizedString(@"in \"Apps\"", @"Text for related post cell preview") - imageName:@"relatedPostsPreview2"]; - RelatedPostsPreview *preview3 = [[RelatedPostsPreview alloc] initWithTitle:NSLocalizedString(@"Upgrade Focus: VideoPress For Weddings", @"Text for related post cell preview") - site:NSLocalizedString(@"in \"Upgrade\"", @"Text for related post cell preview") - imageName:@"relatedPostsPreview3"]; - - _previewPosts = @[preview1, preview2, preview3]; - - for (RelatedPostsPreview *relatedPostPreview in _previewPosts) { - [readableContentView addSubview:relatedPostPreview.imageView]; - [readableContentView addSubview:relatedPostPreview.titleLabel]; - [readableContentView addSubview:relatedPostPreview.siteLabel]; - } - } - return self; -} - -- (CGFloat)heightForWidth:(CGFloat)availableWidth -{ - CGFloat width = self.readableContentView.frame.size.width - (2 * HorizontalMargin); - CGFloat height = 0; - CGSize sizeRestriction = CGSizeMake(width, CGFLOAT_MAX); - if (self.enabledHeader) { - height += ceilf([self.headerLabel sizeThatFits:sizeRestriction].height) + VerticalMargin; - } - for (RelatedPostsPreview *relatedPostPreview in _previewPosts) { - if (self.enabledImages) { - height += ImageHeight + (2 * VerticalMargin); - } - height += ceilf([relatedPostPreview.titleLabel sizeThatFits:sizeRestriction].height) + VerticalMargin; - height += ceilf([relatedPostPreview.siteLabel sizeThatFits:sizeRestriction].height); - } - height += VerticalMargin; - - return height; -} - -#pragma mark - RelatedPostsPreviewReadableContentViewDelegate - -- (void)postPreviewReadableContentViewDidLayoutSubviews:(RelatedPostsPreviewReadableContentView *)readableContentView -{ - CGFloat width = self.readableContentView.frame.size.width - (2 * HorizontalMargin); - CGFloat height = 0; - CGSize sizeRestriction = CGSizeMake(width, CGFLOAT_MAX); - if (self.enabledHeader) { - height = ceilf([self.headerLabel sizeThatFits:sizeRestriction].height); - self.headerLabel.frame = CGRectMake(HorizontalMargin, VerticalMargin, width, height); - } else { - self.headerLabel.frame = CGRectZero; - } - UIView *referenceView = self.headerLabel; - for (RelatedPostsPreview *relatedPostPreview in _previewPosts) { - if (self.enabledImages) { - relatedPostPreview.imageView.frame = CGRectMake(HorizontalMargin, CGRectGetMaxY(referenceView.frame) + (2 * VerticalMargin), width, ImageHeight); - relatedPostPreview.imageView.hidden = NO; - referenceView = relatedPostPreview.imageView; - } else { - relatedPostPreview.imageView.frame = CGRectZero; - relatedPostPreview.imageView.hidden = YES; - } - - height = ceilf([relatedPostPreview.titleLabel sizeThatFits:sizeRestriction].height); - relatedPostPreview.titleLabel.frame = CGRectMake(HorizontalMargin, CGRectGetMaxY(referenceView.frame) + VerticalMargin, width, height); - referenceView = relatedPostPreview.titleLabel; - - height = ceilf([relatedPostPreview.siteLabel sizeThatFits:sizeRestriction].height); - relatedPostPreview.siteLabel.frame = CGRectMake(HorizontalMargin, CGRectGetMaxY(referenceView.frame), width, height); - referenceView = relatedPostPreview.siteLabel; - } -} - -@end - -@implementation RelatedPostsPreviewReadableContentView - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - [self.delegate postPreviewReadableContentViewDidLayoutSubviews:self]; -} - -@end diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsSettingsViewController.h b/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsSettingsViewController.h deleted file mode 100644 index 15799d188360..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsSettingsViewController.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@class Blog; - -@interface RelatedPostsSettingsViewController : UITableViewController - -- (instancetype)initWithBlog:(Blog *)blog; - -@end diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m index 5964940bcea7..9f6b525c38f1 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController.m @@ -6,7 +6,6 @@ #import "NSURL+IDN.h" #import "PostCategory.h" #import "PostCategoryService.h" -#import "RelatedPostsSettingsViewController.h" #import "SettingsSelectionViewController.h" #import "SettingsMultiTextViewController.h" #import "SettingTableViewCell.h" diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 184b0f7f339e..f35eb2cec2a7 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -5094,7 +5094,6 @@ FABB24EE2602FC2C00C8785C /* Suggestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03B9235250BC5FD000A40AF /* Suggestion.swift */; }; FABB24EF2602FC2C00C8785C /* ShareNoticeConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74EA3B87202A0462004F802D /* ShareNoticeConstants.swift */; }; FABB24F02602FC2C00C8785C /* FeatureFlagOverrideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A09B98238FE13B0022AE0D /* FeatureFlagOverrideStore.swift */; }; - FABB24F12602FC2C00C8785C /* RelatedPostsPreviewTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FF1933FE1BB17DA3006825B8 /* RelatedPostsPreviewTableViewCell.m */; }; FABB24F22602FC2C00C8785C /* IntrinsicTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54075D31D3D7D5B0095C318 /* IntrinsicTableView.swift */; }; FABB24F32602FC2C00C8785C /* HomeWidgetTodayData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB34ACA25672A90001A74A6 /* HomeWidgetTodayData.swift */; }; FABB24F42602FC2C00C8785C /* NoticePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E27D21FD98135003EA321 /* NoticePresenter.swift */; }; @@ -5306,7 +5305,6 @@ FABB25C52602FC2C00C8785C /* MenusSelectionItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 08D3454D1CD7F50900358E8C /* MenusSelectionItemView.m */; }; FABB25C62602FC2C00C8785C /* TenorReponseParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81CCD6D243AF09900A83E27 /* TenorReponseParser.swift */; }; FABB25C72602FC2C00C8785C /* WPStyleGuide+Gridicon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4020B2BC2007AC850002C963 /* WPStyleGuide+Gridicon.swift */; }; - FABB25C82602FC2C00C8785C /* RelatedPostsSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FF42584F1BA092EE00580C68 /* RelatedPostsSettingsViewController.m */; }; FABB25C92602FC2C00C8785C /* CreateButtonCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E032D5240889EB003AF350 /* CreateButtonCoordinator.swift */; }; FABB25CA2602FC2C00C8785C /* SearchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74729CA22056FA0900D1394D /* SearchManager.swift */; }; FABB25CB2602FC2C00C8785C /* BlogToBlogMigration87to88.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8980C922E8C7A600C567B0 /* BlogToBlogMigration87to88.swift */; }; @@ -5593,7 +5591,6 @@ FF0B2567237A023C004E255F /* GutenbergVideoUploadProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0B2566237A023C004E255F /* GutenbergVideoUploadProcessorTests.swift */; }; FF0D8146205809C8000EE505 /* PostCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0D8145205809C8000EE505 /* PostCoordinator.swift */; }; FF0F722C206E5345000DAAB5 /* Post+RefreshStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0F722B206E5345000DAAB5 /* Post+RefreshStatus.swift */; }; - FF1933FF1BB17DA3006825B8 /* RelatedPostsPreviewTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FF1933FE1BB17DA3006825B8 /* RelatedPostsPreviewTableViewCell.m */; }; FF1B11E5238FDFE70038B93E /* GutenbergGalleryUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1B11E4238FDFE70038B93E /* GutenbergGalleryUploadProcessor.swift */; }; FF1B11E7238FE27A0038B93E /* GutenbergGalleryUploadProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1B11E6238FE27A0038B93E /* GutenbergGalleryUploadProcessorTests.swift */; }; FF1FD0242091268900186384 /* URL+LinkNormalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1FD0232091268900186384 /* URL+LinkNormalization.swift */; }; @@ -5605,7 +5602,6 @@ FF2EC3C22209AC19006176E1 /* GutenbergImgUploadProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EC3C12209AC19006176E1 /* GutenbergImgUploadProcessorTests.swift */; }; FF355D981FB492DD00244E6D /* ExportableAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF355D971FB492DD00244E6D /* ExportableAsset.swift */; }; FF37F90922385CA000AFA3DB /* RELEASE-NOTES.txt in Resources */ = {isa = PBXBuildFile; fileRef = FF37F90822385C9F00AFA3DB /* RELEASE-NOTES.txt */; }; - FF4258501BA092EE00580C68 /* RelatedPostsSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FF42584F1BA092EE00580C68 /* RelatedPostsSettingsViewController.m */; }; FF4C069F206560E500E0B2BC /* MediaThumbnailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4C069E206560E500E0B2BC /* MediaThumbnailCoordinator.swift */; }; FF4DEAD8244B56E300ACA032 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FF4DEAD7244B56E200ACA032 /* CoreServices.framework */; }; FF5371631FDFF64F00619A3F /* MediaService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5371621FDFF64F00619A3F /* MediaService.swift */; }; @@ -9360,8 +9356,6 @@ FF0B2566237A023C004E255F /* GutenbergVideoUploadProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergVideoUploadProcessorTests.swift; sourceTree = ""; }; FF0D8145205809C8000EE505 /* PostCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostCoordinator.swift; sourceTree = ""; }; FF0F722B206E5345000DAAB5 /* Post+RefreshStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Post+RefreshStatus.swift"; sourceTree = ""; }; - FF1933FD1BB17DA3006825B8 /* RelatedPostsPreviewTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RelatedPostsPreviewTableViewCell.h; sourceTree = ""; }; - FF1933FE1BB17DA3006825B8 /* RelatedPostsPreviewTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RelatedPostsPreviewTableViewCell.m; sourceTree = ""; }; FF1B11E4238FDFE70038B93E /* GutenbergGalleryUploadProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GutenbergGalleryUploadProcessor.swift; sourceTree = ""; }; FF1B11E6238FE27A0038B93E /* GutenbergGalleryUploadProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GutenbergGalleryUploadProcessorTests.swift; path = Gutenberg/GutenbergGalleryUploadProcessorTests.swift; sourceTree = ""; }; FF1FD0232091268900186384 /* URL+LinkNormalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+LinkNormalization.swift"; sourceTree = ""; }; @@ -9376,8 +9370,6 @@ FF2EC3C12209AC19006176E1 /* GutenbergImgUploadProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GutenbergImgUploadProcessorTests.swift; path = Gutenberg/GutenbergImgUploadProcessorTests.swift; sourceTree = ""; }; FF355D971FB492DD00244E6D /* ExportableAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableAsset.swift; sourceTree = ""; }; FF37F90822385C9F00AFA3DB /* RELEASE-NOTES.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "RELEASE-NOTES.txt"; path = "../RELEASE-NOTES.txt"; sourceTree = ""; }; - FF42584E1BA092EE00580C68 /* RelatedPostsSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RelatedPostsSettingsViewController.h; sourceTree = ""; }; - FF42584F1BA092EE00580C68 /* RelatedPostsSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RelatedPostsSettingsViewController.m; sourceTree = ""; }; FF4C069E206560E500E0B2BC /* MediaThumbnailCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaThumbnailCoordinator.swift; sourceTree = ""; }; FF4DEAD7244B56E200ACA032 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; FF5371621FDFF64F00619A3F /* MediaService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaService.swift; sourceTree = ""; }; @@ -11205,17 +11197,6 @@ path = "Blog Selector"; sourceTree = ""; }; - 3F43603723F369A9001DEE70 /* Related Posts */ = { - isa = PBXGroup; - children = ( - FF42584E1BA092EE00580C68 /* RelatedPostsSettingsViewController.h */, - FF42584F1BA092EE00580C68 /* RelatedPostsSettingsViewController.m */, - FF1933FD1BB17DA3006825B8 /* RelatedPostsPreviewTableViewCell.h */, - FF1933FE1BB17DA3006825B8 /* RelatedPostsPreviewTableViewCell.m */, - ); - path = "Related Posts"; - sourceTree = ""; - }; 3F43603823F36A76001DEE70 /* Site Settings */ = { isa = PBXGroup; children = ( @@ -11233,7 +11214,6 @@ 82C420751FE44BD900CFB15B /* SiteSettingsViewController+Swift.swift */, 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */, 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */, - 3F43603723F369A9001DEE70 /* Related Posts */, ); path = "Site Settings"; sourceTree = ""; @@ -22284,7 +22264,6 @@ B03B9236250BC5FD000A40AF /* Suggestion.swift in Sources */, 74EA3B88202A0462004F802D /* ShareNoticeConstants.swift in Sources */, 17A09B99238FE13B0022AE0D /* FeatureFlagOverrideStore.swift in Sources */, - FF1933FF1BB17DA3006825B8 /* RelatedPostsPreviewTableViewCell.m in Sources */, B54075D41D3D7D5B0095C318 /* IntrinsicTableView.swift in Sources */, 08A4E12C289D2337001D9EC7 /* UserPersistentRepository.swift in Sources */, 3FB34ADA25672AA5001A74A6 /* HomeWidgetTodayData.swift in Sources */, @@ -22582,7 +22561,6 @@ C7B7CC702812FDCE007B9807 /* MySiteViewController+OnboardingPrompt.swift in Sources */, C81CCD6F243AF7D700A83E27 /* TenorReponseParser.swift in Sources */, 4020B2BD2007AC850002C963 /* WPStyleGuide+Gridicon.swift in Sources */, - FF4258501BA092EE00580C68 /* RelatedPostsSettingsViewController.m in Sources */, 982D261F2788DDF200A41286 /* ReaderCommentsFollowPresenter.swift in Sources */, F5E032D6240889EB003AF350 /* CreateButtonCoordinator.swift in Sources */, 74729CA32056FA0900D1394D /* SearchManager.swift in Sources */, @@ -25160,7 +25138,6 @@ FABB24EE2602FC2C00C8785C /* Suggestion.swift in Sources */, FABB24EF2602FC2C00C8785C /* ShareNoticeConstants.swift in Sources */, FABB24F02602FC2C00C8785C /* FeatureFlagOverrideStore.swift in Sources */, - FABB24F12602FC2C00C8785C /* RelatedPostsPreviewTableViewCell.m in Sources */, FABB24F22602FC2C00C8785C /* IntrinsicTableView.swift in Sources */, FABB24F32602FC2C00C8785C /* HomeWidgetTodayData.swift in Sources */, FABB24F42602FC2C00C8785C /* NoticePresenter.swift in Sources */, @@ -25459,7 +25436,6 @@ FABB25C52602FC2C00C8785C /* MenusSelectionItemView.m in Sources */, FABB25C62602FC2C00C8785C /* TenorReponseParser.swift in Sources */, FABB25C72602FC2C00C8785C /* WPStyleGuide+Gridicon.swift in Sources */, - FABB25C82602FC2C00C8785C /* RelatedPostsSettingsViewController.m in Sources */, FABB25C92602FC2C00C8785C /* CreateButtonCoordinator.swift in Sources */, 3FBF21B8267AA17A0098335F /* BloggingRemindersAnimator.swift in Sources */, FABB25CA2602FC2C00C8785C /* SearchManager.swift in Sources */, From 1635c68643dbf62d69ae6a8fa20763f9b9cd8c46 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 16 Jun 2023 12:46:03 -0400 Subject: [PATCH 003/175] Add constants to SiteSettingsRelatedPostsView.swift --- .../Blog/Site Settings/SiteSettingsRelatedPostsView.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift index 921adc719499..00e3fc0bd2cb 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift @@ -73,7 +73,7 @@ struct RelatedPostsSettingsView: View { Image(viewModel.imageName) .resizable() .aspectRatio(contentMode: .fill) - .frame(height: 96) + .frame(height: Constants.imageViewHeight) .clipped() } HStack { @@ -134,4 +134,8 @@ private extension RelatedPostsSettingsView { static let relatedPostsHeader = NSLocalizedString("relatedPostsSettings.relatedPostsHeader", value: "Related Posts", comment: "Label for Related Post header preview") static let saveFailed = NSLocalizedString("relatedPostsSettings.settingsUpdateFailed", value: "Settings update failed", comment: "Message to show when setting save failed") } + + enum Constants { + static let imageViewHeight: CGFloat = 96 + } } From e4b3c1d9aba7e70efd4fe531ed517d040d21b8d1 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 16 Jun 2023 13:04:02 -0400 Subject: [PATCH 004/175] Fix title not being available on push --- .../Blog/Site Settings/SiteSettingsRelatedPostsView.swift | 2 ++ .../Blog/Site Settings/SiteSettingsViewController+Swift.swift | 1 + 2 files changed, 3 insertions(+) diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift index 00e3fc0bd2cb..cfe48f3ba904 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsRelatedPostsView.swift @@ -7,6 +7,8 @@ struct RelatedPostsSettingsView: View { private let blog: Blog @ObservedObject private var settings: BlogSettings + var title: String { Strings.title } + init(blog: Blog) { self.blog = blog assert(blog.settings != nil, "Settings should never be nil") diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift index 38ced37b41ba..a6531757e709 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteSettingsViewController+Swift.swift @@ -149,6 +149,7 @@ extension SiteSettingsViewController { @objc func showRelatedPostsSettings() { let view = RelatedPostsSettingsView(blog: blog) let host = UIHostingController(rootView: view) + host.title = view.title // Make sure title is available before push navigationController?.pushViewController(host, animated: true) } From a69b4a1269bdd7b0e8bdd1b27ad6612a6a726d14 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 21 Jun 2023 17:54:53 -0400 Subject: [PATCH 005/175] Remove older Related Posts files --- .../RelatedPostsSettingsViewController.m | 264 ------------------ 1 file changed, 264 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsSettingsViewController.m diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsSettingsViewController.m b/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsSettingsViewController.m deleted file mode 100644 index a870160e916b..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/Related Posts/RelatedPostsSettingsViewController.m +++ /dev/null @@ -1,264 +0,0 @@ -#import "RelatedPostsSettingsViewController.h" - -#import "Blog.h" -#import "BlogService.h" -#import "CoreDataStack.h" -#import "SettingTableViewCell.h" -#import "SVProgressHud+Dismiss.h" -#import "RelatedPostsPreviewTableViewCell.h" - -#import -#import "WordPress-Swift.h" - - -static const CGFloat RelatePostsSettingsCellHeight = 44; - -typedef NS_ENUM(NSInteger, RelatedPostsSettingsSection) { - RelatedPostsSettingsSectionOptions = 0, - RelatedPostsSettingsSectionPreview, - RelatedPostsSettingsSectionCount -}; - -typedef NS_ENUM(NSInteger, RelatedPostsSettingsOptions) { - RelatedPostsSettingsOptionsEnabled = 0, - RelatedPostsSettingsOptionsShowHeader, - RelatedPostsSettingsOptionsShowThumbnails, - RelatedPostsSettingsOptionsCount, -}; - -@interface RelatedPostsSettingsViewController() - -@property (nonatomic, strong) Blog *blog; - -@property (nonatomic, strong) SwitchTableViewCell *relatedPostsEnabledCell; -@property (nonatomic, strong) SwitchTableViewCell *relatedPostsShowHeaderCell; -@property (nonatomic, strong) SwitchTableViewCell *relatedPostsShowThumbnailsCell; - -@property (nonatomic, strong) RelatedPostsPreviewTableViewCell *relatedPostsPreviewTableViewCell; - -@end - -@implementation RelatedPostsSettingsViewController - -- (instancetype)initWithBlog:(Blog *)blog -{ - NSParameterAssert([blog isKindOfClass:[Blog class]]); - self = [super initWithStyle:UITableViewStyleInsetGrouped]; - if (self) { - _blog = blog; - } - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - [WPStyleGuide configureColorsForView:self.view andTableView:self.tableView]; - self.navigationItem.title = NSLocalizedString(@"Related Posts", @"Title for screen that allows configuration of your blog/site related posts settings."); - self.tableView.allowsSelection = NO; -} - - -#pragma mark - Properties - -- (BlogSettings *)settings -{ - return self.blog.settings; -} - - -#pragma mark - UITableViewDataSource Methods - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - if (self.settings.relatedPostsEnabled) { - return RelatedPostsSettingsSectionCount; - } else { - return RelatedPostsSettingsSectionCount-1; - } -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - switch (section) { - case RelatedPostsSettingsSectionOptions:{ - if (self.settings.relatedPostsEnabled) { - return RelatedPostsSettingsOptionsCount; - } else { - return 1; - } - } - break; - case RelatedPostsSettingsSectionPreview: - return 1; - break; - } - return 0; -} - -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ - switch (section) { - case RelatedPostsSettingsSectionPreview: - return NSLocalizedString(@"Preview", @"Section title for related posts section preview"); - default: - return nil; - } -} - -- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section -{ - switch (section) { - case RelatedPostsSettingsSectionOptions: - return NSLocalizedString(@"Related Posts displays relevant content from your site below your posts", @"Information of what related post are and how they are presented");; - default: - return nil; - } -} - -- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section -{ - [WPStyleGuide configureTableViewSectionFooter:view]; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - switch (indexPath.section) { - case RelatedPostsSettingsSectionOptions:{ - return RelatePostsSettingsCellHeight; - } - break; - case RelatedPostsSettingsSectionPreview:{ - return [self.relatedPostsPreviewTableViewCell heightForWidth:tableView.frame.size.width]; - } - break; - case RelatedPostsSettingsSectionCount: - break; - } - return 0; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - RelatedPostsSettingsSection section = (RelatedPostsSettingsSection)indexPath.section; - switch (section) { - case RelatedPostsSettingsSectionOptions:{ - RelatedPostsSettingsOptions row = indexPath.row; - return [self tableView:tableView cellForOptionsRow:row]; - } - break; - case RelatedPostsSettingsSectionPreview:{ - return [self relatedPostsPreviewTableViewCell]; - } - break; - case RelatedPostsSettingsSectionCount: - break; - } - return [UITableViewCell new]; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForOptionsRow:(RelatedPostsSettingsOptions)row -{ - switch (row) { - case RelatedPostsSettingsOptionsEnabled:{ - self.relatedPostsEnabledCell.on = self.settings.relatedPostsEnabled; - return self.relatedPostsEnabledCell; - } - break; - case RelatedPostsSettingsOptionsShowHeader:{ - self.relatedPostsShowHeaderCell.on = self.settings.relatedPostsShowHeadline; - return self.relatedPostsShowHeaderCell; - } - break; - case RelatedPostsSettingsOptionsShowThumbnails:{ - self.relatedPostsShowThumbnailsCell.on = self.settings.relatedPostsShowThumbnails; - return self.relatedPostsShowThumbnailsCell; - } - break; - case RelatedPostsSettingsOptionsCount: - break; - } - return nil; -} - - -#pragma mark - Cell Helpers - -- (SwitchTableViewCell *)relatedPostsEnabledCell -{ - if (!_relatedPostsEnabledCell) { - _relatedPostsEnabledCell = [SwitchTableViewCell new]; - _relatedPostsEnabledCell.name = NSLocalizedString(@"Show Related Posts", @"Label for configuration switch to enable/disable related posts"); - __weak RelatedPostsSettingsViewController *weakSelf = self; - _relatedPostsEnabledCell.onChange = ^(BOOL value){ - [WPAnalytics trackSettingsChange:@"related_posts" fieldName:@"show_related_posts" value:@(value)]; - - [weakSelf updateRelatedPostsSettings:nil]; - }; - } - return _relatedPostsEnabledCell; -} - -- (SwitchTableViewCell *)relatedPostsShowHeaderCell -{ - if (!_relatedPostsShowHeaderCell) { - _relatedPostsShowHeaderCell = [SwitchTableViewCell new]; - _relatedPostsShowHeaderCell.name = NSLocalizedString(@"Show Header", @"Label for configuration switch to show/hide the header for the related posts section"); - __weak RelatedPostsSettingsViewController *weakSelf = self; - _relatedPostsShowHeaderCell.onChange = ^(BOOL value){ - [WPAnalytics trackSettingsChange:@"related_posts" fieldName:@"show_related_posts_header" value:@(value)]; - [weakSelf updateRelatedPostsSettings:nil]; - }; - } - - return _relatedPostsShowHeaderCell; -} - -- (SwitchTableViewCell *)relatedPostsShowThumbnailsCell -{ - if (!_relatedPostsShowThumbnailsCell) { - _relatedPostsShowThumbnailsCell = [SwitchTableViewCell new]; - _relatedPostsShowThumbnailsCell.name = NSLocalizedString(@"Show Images", @"Label for configuration switch to show/hide images thumbnail for the related posts"); - __weak RelatedPostsSettingsViewController *weakSelf = self; - _relatedPostsShowThumbnailsCell.onChange = ^(BOOL value){ - [WPAnalytics trackSettingsChange:@"related_posts" fieldName:@"show_related_posts_thumbnail" value:@(value)]; - - [weakSelf updateRelatedPostsSettings:nil]; - }; - } - - return _relatedPostsShowThumbnailsCell; -} - - -- (RelatedPostsPreviewTableViewCell *)relatedPostsPreviewTableViewCell -{ - if (!_relatedPostsPreviewTableViewCell) { - _relatedPostsPreviewTableViewCell = [[RelatedPostsPreviewTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault - reuseIdentifier:nil]; - } - _relatedPostsPreviewTableViewCell.enabledImages = self.settings.relatedPostsShowThumbnails; - _relatedPostsPreviewTableViewCell.enabledHeader = self.settings.relatedPostsShowHeadline; - - return _relatedPostsPreviewTableViewCell; - -} - -#pragma mark - Helpers - -- (IBAction)updateRelatedPostsSettings:(id)sender -{ - self.settings.relatedPostsEnabled = self.relatedPostsEnabledCell.on; - self.settings.relatedPostsShowHeadline = self.relatedPostsShowHeaderCell.on; - self.settings.relatedPostsShowThumbnails = self.relatedPostsShowThumbnailsCell.on; - - BlogService *blogService = [[BlogService alloc] initWithCoreDataStack:[ContextManager sharedInstance]]; - [blogService updateSettingsForBlog:self.blog success:^{ - [self.tableView reloadData]; - } failure:^(NSError * __unused error) { - [SVProgressHUD showDismissibleErrorWithStatus:NSLocalizedString(@"Settings update failed", @"Message to show when setting save failed")]; - [self.tableView reloadData]; - }]; - [self.tableView reloadData]; -} - -@end From a6183647b0be3e75f31d58b549900097ffeb09c3 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 20 Jun 2023 14:36:08 -0400 Subject: [PATCH 006/175] Increase the deployment target to iOS 15 --- WordPress/Classes/ViewRelated/Menus/MenuItemView.m | 14 ++++---------- config/Common.xcconfig | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m index f9f6908e8b20..aabfdec0888c 100644 --- a/WordPress/Classes/ViewRelated/Menus/MenuItemView.m +++ b/WordPress/Classes/ViewRelated/Menus/MenuItemView.m @@ -64,16 +64,10 @@ - (void)setupCancelButton button.titleLabel.adjustsFontForContentSizeCategory = YES; [button setTitle:NSLocalizedString(@"Cancel", @"") forState:UIControlStateNormal]; - if (@available(iOS 15, *)) { - UIButtonConfiguration *configuration = [UIButtonConfiguration plainButtonConfiguration]; - configuration.contentInsets = NSDirectionalEdgeInsetsMake(0, 6, 0, 6); - button.configuration = configuration; - } else { - UIEdgeInsets inset = button.contentEdgeInsets; - inset.left = 6.0; - inset.right = inset.left; - button.contentEdgeInsets = inset; - } + UIButtonConfiguration *configuration = [UIButtonConfiguration plainButtonConfiguration]; + configuration.contentInsets = NSDirectionalEdgeInsetsMake(0, 6, 0, 6); + button.configuration = configuration; + button.hidden = YES; [self.accessoryStackView addArrangedSubview:button]; diff --git a/config/Common.xcconfig b/config/Common.xcconfig index b1dedffcad96..83f13cefeb4b 100644 --- a/config/Common.xcconfig +++ b/config/Common.xcconfig @@ -1,3 +1,3 @@ GCC_WARN_UNUSED_PARAMETER = YES WARNING_CFLAGS = -Wno-nullability-completeness -IPHONEOS_DEPLOYMENT_TARGET = 14.0 +IPHONEOS_DEPLOYMENT_TARGET = 15.0 From 55b7b5cc29970907aae98455c2a2250c1f0568cc Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 20 Jun 2023 14:38:24 -0400 Subject: [PATCH 007/175] Remove available checks --- .../WPStyleGuide+ApplicationStyles.swift | 21 +++---- .../NSAttributedString+Helpers.swift | 7 +-- .../Extensions/UIApplication+mainWindow.swift | 10 +--- .../Utility/WebKitViewController.swift | 5 +- .../Overlay/BlazeOverlayViewController.swift | 4 +- .../Webview/BlazeWebViewController.swift | 4 +- .../BlogDashboardViewController.swift | 14 ----- .../Pages/DashboardPageCreationCell.swift | 23 +++----- .../Prompts/DashboardPromptsCardCell.swift | 57 ++----------------- .../QuickStartChecklistViewController.swift | 4 +- .../CommentDetailViewController.swift | 40 +------------ .../Domains/Views/DomainsDashboardView.swift | 10 +--- ...tpackFullscreenOverlayViewController.swift | 46 ++++++--------- .../JetpackBrandingMenuCardCell.swift | 11 +--- .../Post/PostEditorNavigationBarManager.swift | 15 ++--- .../ReaderCommentsViewController.swift | 12 ++-- .../Views/ReaderDetailFeaturedImageView.swift | 4 +- .../BloggingPromptsHeaderView.swift | 14 ++--- .../AnimatedGifAttachmentViewProvider.swift | 1 - .../Views/RichTextView/RichTextView.swift | 4 +- .../MigrationNavigationController.swift | 4 +- ...grationDeleteWordPressViewController.swift | 4 +- .../MigrationWelcomeViewController.swift | 8 +-- 23 files changed, 68 insertions(+), 254 deletions(-) diff --git a/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift b/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift index b580087879c3..ca0456d81a5d 100644 --- a/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift +++ b/WordPress/Classes/Extensions/Colors and Styles/WPStyleGuide+ApplicationStyles.swift @@ -57,9 +57,7 @@ extension WPStyleGuide { /// Style `UITableView` in the app class func configureTableViewAppearance() { - if #available(iOS 15.0, *) { - UITableView.appearance().sectionHeaderTopPadding = 0 - } + UITableView.appearance().sectionHeaderTopPadding = 0 } /// Style the tab bar using Muriel colors @@ -67,14 +65,12 @@ extension WPStyleGuide { UITabBar.appearance().tintColor = .tabSelected UITabBar.appearance().unselectedItemTintColor = .tabUnselected - if #available(iOS 15.0, *) { - let appearance = UITabBarAppearance() - appearance.configureWithOpaqueBackground() - appearance.backgroundColor = .systemBackground + let appearance = UITabBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = .systemBackground - UITabBar.appearance().standardAppearance = appearance - UITabBar.appearance().scrollEdgeAppearance = appearance - } + UITabBar.appearance().standardAppearance = appearance + UITabBar.appearance().scrollEdgeAppearance = appearance } /// Style the `LightNavigationController` UINavigationBar and BarButtonItems @@ -114,10 +110,7 @@ extension WPStyleGuide { appearance.configureWithDefaultBackground() UIToolbar.appearance().standardAppearance = appearance - - if #available(iOS 15.0, *) { - UIToolbar.appearance().scrollEdgeAppearance = appearance - } + UIToolbar.appearance().scrollEdgeAppearance = appearance } } diff --git a/WordPress/Classes/Extensions/NSAttributedString+Helpers.swift b/WordPress/Classes/Extensions/NSAttributedString+Helpers.swift index b57f17fd0728..721518d8acaa 100644 --- a/WordPress/Classes/Extensions/NSAttributedString+Helpers.swift +++ b/WordPress/Classes/Extensions/NSAttributedString+Helpers.swift @@ -26,12 +26,7 @@ public extension NSAttributedString { for (value, image) in unwrappedEmbeds { let imageAttachment = NSTextAttachment() let gifType = UTType.gif.identifier - var displayAnimatedGifs = false - - // Check to see if the animated gif view provider is registered - if #available(iOS 15.0, *) { - displayAnimatedGifs = NSTextAttachment.textAttachmentViewProviderClass(forFileType: gifType) == AnimatedGifAttachmentViewProvider.self - } + let displayAnimatedGifs = NSTextAttachment.textAttachmentViewProviderClass(forFileType: gifType) == AnimatedGifAttachmentViewProvider.self // When displaying an animated gif pass the gif data instead of the image if diff --git a/WordPress/Classes/Extensions/UIApplication+mainWindow.swift b/WordPress/Classes/Extensions/UIApplication+mainWindow.swift index 59d59af81a0a..4d5068ae895e 100644 --- a/WordPress/Classes/Extensions/UIApplication+mainWindow.swift +++ b/WordPress/Classes/Extensions/UIApplication+mainWindow.swift @@ -2,13 +2,9 @@ import UIKit extension UIApplication { @objc var mainWindow: UIWindow? { - if #available(iOS 15, *) { - return connectedScenes - .compactMap { ($0 as? UIWindowScene)?.keyWindow } - .first - } else { - return windows.filter { $0.isKeyWindow }.first - } + connectedScenes + .compactMap { ($0 as? UIWindowScene)?.keyWindow } + .first } @objc var currentStatusBarFrame: CGRect { diff --git a/WordPress/Classes/Utility/WebKitViewController.swift b/WordPress/Classes/Utility/WebKitViewController.swift index 78d0649b598e..37c6bfeb895e 100644 --- a/WordPress/Classes/Utility/WebKitViewController.swift +++ b/WordPress/Classes/Utility/WebKitViewController.swift @@ -349,10 +349,7 @@ class WebKitViewController: UIViewController, WebKitAuthenticatable { appearance.backgroundColor = UIColor(light: .white, dark: .appBarBackground) toolBar.standardAppearance = appearance - - if #available(iOS 15.0, *) { - toolBar.scrollEdgeAppearance = appearance - } + toolBar.scrollEdgeAppearance = appearance fixBarButtonsColorForBoldText(on: toolBar) } diff --git a/WordPress/Classes/ViewRelated/Blaze/Overlay/BlazeOverlayViewController.swift b/WordPress/Classes/ViewRelated/Blaze/Overlay/BlazeOverlayViewController.swift index ef2b16b5eae2..c2b9b3fce1b1 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Overlay/BlazeOverlayViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Overlay/BlazeOverlayViewController.swift @@ -156,9 +156,7 @@ final class BlazeOverlayViewController: UIViewController { navigationItem.standardAppearance = appearance navigationItem.compactAppearance = appearance navigationItem.scrollEdgeAppearance = appearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = appearance - } + navigationItem.compactScrollEdgeAppearance = appearance } private func setupView() { diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift index e4352647d868..fe473a455d09 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift @@ -90,9 +90,7 @@ class BlazeWebViewController: UIViewController, BlazeWebView { navigationItem.standardAppearance = appearance navigationItem.compactAppearance = appearance navigationItem.scrollEdgeAppearance = appearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = appearance - } + navigationItem.compactScrollEdgeAppearance = appearance } // MARK: Reachability Helpers diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift index 7e0934cef9ed..945e8c0cd1dc 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift @@ -325,20 +325,6 @@ extension BlogDashboardViewController { } } -// MARK: - UI Popover Delegate - -/// This view controller may host a `DashboardPromptsCardCell` that requires presenting a `MenuSheetViewController`, -/// a fallback implementation of `UIMenu` for iOS 13. For more details, see the docs on `MenuSheetViewController`. -/// -/// NOTE: This should be removed once we drop support for iOS 13. -/// -extension BlogDashboardViewController: UIPopoverPresentationControllerDelegate { - // Force popover views to be presented as a popover (instead of being presented as a form sheet on iPhones). - public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { - return .none - } -} - // MARK: - Helper functions private extension Collection where Element == DashboardCardModel { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Pages/DashboardPageCreationCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Pages/DashboardPageCreationCell.swift index d74428f7c97b..f5c332503bba 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Pages/DashboardPageCreationCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Pages/DashboardPageCreationCell.swift @@ -52,21 +52,14 @@ class DashboardPageCreationCell: UITableViewCell { button.addTarget(self, action: #selector(createPageButtonTapped), for: .touchUpInside) let font = WPStyleGuide.fontForTextStyle(.callout, fontWeight: .bold) - if #available(iOS 15.0, *) { - var buttonConfig: UIButton.Configuration = .plain() - buttonConfig.contentInsets = Metrics.createPageButtonContentInsets - buttonConfig.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer({ incoming in - var outgoing = incoming - outgoing.font = font - return outgoing - }) - button.configuration = buttonConfig - } else { - button.titleLabel?.font = font - button.setTitleColor(.jetpackGreen, for: .normal) - button.contentEdgeInsets = Metrics.createPageButtonContentEdgeInsets - button.flipInsetsForRightToLeftLayoutDirection() - } + var buttonConfig: UIButton.Configuration = .plain() + buttonConfig.contentInsets = Metrics.createPageButtonContentInsets + buttonConfig.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer({ incoming in + var outgoing = incoming + outgoing.font = font + return outgoing + }) + button.configuration = buttonConfig return button }() diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift index 0b6c87a24543..dec2db18d784 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift @@ -12,23 +12,11 @@ class DashboardPromptsCardCell: UICollectionViewCell, Reusable { frameView.translatesAutoresizingMaskIntoConstraints = false frameView.setTitle(Strings.cardFrameTitle) - // NOTE: Remove the logic when support for iOS 14 is dropped - if #available (iOS 15.0, *) { - // assign an empty closure so the button appears. - frameView.onEllipsisButtonTap = { - BlogDashboardAnalytics.trackContextualMenuAccessed(for: .prompts) - } - frameView.ellipsisButton.showsMenuAsPrimaryAction = true - frameView.ellipsisButton.menu = contextMenu - } else { - // Show a fallback implementation using `MenuSheetViewController`. - // iOS 13 doesn't support showing UIMenu programmatically. - // iOS 14 doesn't support `UIDeferredMenuElement.uncached`. - frameView.onEllipsisButtonTap = { [weak self] in - BlogDashboardAnalytics.trackContextualMenuAccessed(for: .prompts) - self?.showMenuSheet() - } + frameView.onEllipsisButtonTap = { + BlogDashboardAnalytics.trackContextualMenuAccessed(for: .prompts) } + frameView.ellipsisButton.showsMenuAsPrimaryAction = true + frameView.ellipsisButton.menu = contextMenu return frameView }() @@ -313,7 +301,6 @@ class DashboardPromptsCardCell: UICollectionViewCell, Reusable { return [defaultItems, [.learnMore(learnMoreTapped)]] } - @available(iOS 15.0, *) private var contextMenu: UIMenu { return .init(title: String(), options: .displayInline, children: contextMenuItems.map { menuSection in UIMenu(title: String(), options: .displayInline, children: [ @@ -526,27 +513,6 @@ private extension DashboardPromptsCardCell { BloggingPromptsIntroductionPresenter(interactionType: .actionable(blog: blog)).present(from: presenterViewController) } - // Fallback context menu implementation for iOS 13. - func showMenuSheet() { - guard let presenterViewController = presenterViewController else { - return - } - WPAnalytics.track(.promptsDashboardCardMenu) - - let menuViewController = MenuSheetViewController(items: contextMenuItems.map { menuSection in - menuSection.map { $0.toMenuSheetItem } - }) - - menuViewController.modalPresentationStyle = .popover - if let popoverPresentationController = menuViewController.popoverPresentationController { - popoverPresentationController.delegate = presenterViewController - popoverPresentationController.sourceView = cardFrameView.ellipsisButton - popoverPresentationController.sourceRect = cardFrameView.ellipsisButton.bounds - } - - presenterViewController.present(menuViewController, animated: true) - } - // MARK: Constants struct Strings { @@ -648,21 +614,6 @@ private extension DashboardPromptsCardCell { } } } - - var toMenuSheetItem: MenuSheetViewController.MenuItem { - switch self { - case .viewMore(let handler), - .skip(let handler), - .remove(let handler), - .learnMore(let handler): - return MenuSheetViewController.MenuItem( - title: title, - image: image, - destructive: menuAttributes.contains(.destructive), - handler: handler - ) - } - } } } diff --git a/WordPress/Classes/ViewRelated/Blog/QuickStartChecklistViewController.swift b/WordPress/Classes/ViewRelated/Blog/QuickStartChecklistViewController.swift index 2120abfc1090..dd0b957235fb 100644 --- a/WordPress/Classes/ViewRelated/Blog/QuickStartChecklistViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/QuickStartChecklistViewController.swift @@ -107,9 +107,7 @@ private extension QuickStartChecklistViewController { navigationItem.standardAppearance = appearance navigationItem.compactAppearance = appearance navigationItem.scrollEdgeAppearance = appearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = appearance - } + navigationItem.compactScrollEdgeAppearance = appearance } func startObservingForQuickStart() { diff --git a/WordPress/Classes/ViewRelated/Comments/CommentDetailViewController.swift b/WordPress/Classes/ViewRelated/Comments/CommentDetailViewController.swift index 2555daadf2e6..f385541a66b7 100644 --- a/WordPress/Classes/ViewRelated/Comments/CommentDetailViewController.swift +++ b/WordPress/Classes/ViewRelated/Comments/CommentDetailViewController.swift @@ -195,18 +195,6 @@ class CommentDetailViewController: UIViewController, NoResultsViewHost { return appearance }() - /// Convenience property that keeps track of whether the content has scrolled. - private var isContentScrolled: Bool = false { - didSet { - if isContentScrolled == oldValue { - return - } - - // show blurred navigation bar when content is scrolled, or opaque style when the scroll position is at the top. - updateNavigationBarAppearance(isBlurred: isContentScrolled) - } - } - // MARK: Nav Bar Buttons private(set) lazy var editBarButtonItem: UIBarButtonItem = { @@ -380,26 +368,11 @@ private extension CommentDetailViewController { } func configureNavigationBar() { - if #available(iOS 15, *) { - // In iOS 15, to apply visual blur only when content is scrolled, keep the scrollEdgeAppearance unchanged as it applies to ALL navigation bars. - navigationItem.standardAppearance = blurredBarAppearance - } else { - // For iOS 14 and below, scrollEdgeAppearance only affects large title navigation bars. Therefore we need to manually detect if the content - // has been scrolled and change the appearance accordingly. - updateNavigationBarAppearance() - } - + navigationItem.standardAppearance = blurredBarAppearance navigationController?.navigationBar.isTranslucent = true configureNavBarButton() } - /// Updates the navigation bar style based on the `isBlurred` boolean parameter. The intent is to show a visual blur effect when the content is scrolled, - /// but reverts to opaque style when the scroll position is at the top. This method may be called multiple times since it's triggered by the `didSet` - /// property observer on the `isContentScrolled` property. - func updateNavigationBarAppearance(isBlurred: Bool = false) { - navigationItem.standardAppearance = isBlurred ? blurredBarAppearance : opaqueBarAppearance - } - func configureNavBarButton() { var barItems: [UIBarButtonItem] = [] barItems.append(shareBarButtonItem) @@ -1044,17 +1017,6 @@ extension CommentDetailViewController: UITableViewDelegate, UITableViewDataSourc } } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - // keep track of whether the content has scrolled or not. This is used to update the navigation bar style in iOS 14 and below. - // in iOS 15, we don't need to do this since it's been handled automatically; hence the early return. - if #available(iOS 15, *) { - return - } - - isContentScrolled = scrollView.contentOffset.y > contentScrollThreshold - } - } // MARK: - Reply Handling diff --git a/WordPress/Classes/ViewRelated/Domains/Views/DomainsDashboardView.swift b/WordPress/Classes/ViewRelated/Domains/Views/DomainsDashboardView.swift index c2b64c51c70b..ca2e83a37d50 100644 --- a/WordPress/Classes/ViewRelated/Domains/Views/DomainsDashboardView.swift +++ b/WordPress/Classes/ViewRelated/Domains/Views/DomainsDashboardView.swift @@ -55,8 +55,7 @@ struct DomainsDashboardView: View { /// Builds the site address section for the given blog private func makeSiteAddressSection(blog: Blog) -> some View { - Section(header: makeSiteAddressHeader(), - footer: Text(TextContent.primarySiteSectionFooter(blog.hasPaidPlan))) { + Section(footer: Text(TextContent.primarySiteSectionFooter(blog.hasPaidPlan))) { VStack(alignment: .leading) { Text(TextContent.siteAddressTitle) Text(blog.freeSiteAddress) @@ -129,13 +128,6 @@ struct DomainsDashboardView: View { .foregroundColor(domain.domain.expirySoon || domain.domain.expired ? Color(UIColor.error) : Color(UIColor.textSubtle)) } - private func makeSiteAddressHeader() -> Divider? { - if #available(iOS 15, *) { - return nil - } - return Divider() - } - /// Instantiates the proper search depending if it's for claiming a free domain with a paid plan or purchasing a new one private func makeDomainSearch(for blog: Blog, onDismiss: @escaping () -> Void) -> some View { return DomainSuggestionViewControllerWrapper( diff --git a/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayViewController.swift b/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayViewController.swift index 88d863db4fa7..29589db8777b 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayViewController.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayViewController.swift @@ -104,9 +104,7 @@ class JetpackFullscreenOverlayViewController: UIViewController { navigationItem.standardAppearance = appearance navigationItem.compactAppearance = appearance navigationItem.scrollEdgeAppearance = appearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = appearance - } + navigationItem.compactScrollEdgeAppearance = appearance } private func addCloseButtonIfNeeded() { @@ -188,33 +186,21 @@ class JetpackFullscreenOverlayViewController: UIViewController { } private func setupButtonInsets() { - if #available(iOS 15.0, *) { - // Continue & Switch Buttons - var buttonConfig: UIButton.Configuration = .plain() - buttonConfig.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer({ incoming in - var outgoing = incoming - outgoing.font = WPStyleGuide.fontForTextStyle(.body, fontWeight: .semibold) - return outgoing - }) - buttonConfig.contentInsets = Metrics.mainButtonsContentInsets - continueButton.configuration = buttonConfig - switchButton.configuration = buttonConfig - - // Learn More Button - var learnMoreButtonConfig: UIButton.Configuration = .plain() - learnMoreButtonConfig.contentInsets = Metrics.learnMoreButtonContentInsets - learnMoreButton.configuration = learnMoreButtonConfig - } else { - // Continue Button - continueButton.contentEdgeInsets = Metrics.mainButtonsContentEdgeInsets - - // Switch Button - switchButton.contentEdgeInsets = Metrics.mainButtonsContentEdgeInsets - - // Learn More Button - learnMoreButton.contentEdgeInsets = Metrics.learnMoreButtonContentEdgeInsets - learnMoreButton.flipInsetsForRightToLeftLayoutDirection() - } + // Continue & Switch Buttons + var buttonConfig: UIButton.Configuration = .plain() + buttonConfig.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer({ incoming in + var outgoing = incoming + outgoing.font = WPStyleGuide.fontForTextStyle(.body, fontWeight: .semibold) + return outgoing + }) + buttonConfig.contentInsets = Metrics.mainButtonsContentInsets + continueButton.configuration = buttonConfig + switchButton.configuration = buttonConfig + + // Learn More Button + var learnMoreButtonConfig: UIButton.Configuration = .plain() + learnMoreButtonConfig.contentInsets = Metrics.learnMoreButtonContentInsets + learnMoreButton.configuration = learnMoreButtonConfig } private func setupLearnMoreButtonTitle() { diff --git a/WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift b/WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift index f3912c63bd1c..88b71a1b6130 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Branding/Menu Card/JetpackBrandingMenuCardCell.swift @@ -97,14 +97,9 @@ class JetpackBrandingMenuCardCell: UITableViewCell { button.setTitle(Strings.learnMoreButtonText, for: .normal) button.addTarget(self, action: #selector(learnMoreButtonTapped), for: .touchUpInside) - if #available(iOS 15.0, *) { - var learnMoreButtonConfig: UIButton.Configuration = .plain() - learnMoreButtonConfig.contentInsets = Metrics.learnMoreButtonContentInsets - button.configuration = learnMoreButtonConfig - } else { - button.contentEdgeInsets = Metrics.learnMoreButtonContentEdgeInsets - button.flipInsetsForRightToLeftLayoutDirection() - } + var learnMoreButtonConfig: UIButton.Configuration = .plain() + learnMoreButtonConfig.contentInsets = Metrics.learnMoreButtonContentInsets + button.configuration = learnMoreButtonConfig return button }() diff --git a/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift b/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift index 227f233a7c63..b0ba43ef65fc 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift @@ -25,15 +25,12 @@ class PostEditorNavigationBarManager { /// lazy var closeButton: UIButton = { let button = UIButton(type: .system) - if #available(iOS 15, *) { - var configuration = UIButton.Configuration.plain() - configuration.image = Assets.closeButtonModalImage - configuration.contentInsets = Constants.closeButtonInsets - button.configuration = configuration - } else { - button.setImage(Assets.closeButtonModalImage, for: .normal) - button.contentEdgeInsets = Constants.closeButtonEdgeInsets - } + + var configuration = UIButton.Configuration.plain() + configuration.image = Assets.closeButtonModalImage + configuration.contentInsets = Constants.closeButtonInsets + button.configuration = configuration + button.addTarget(self, action: #selector(closeWasPressed), for: .touchUpInside) button.setContentHuggingPriority(.required, for: .horizontal) button.accessibilityIdentifier = "editor-close-button" diff --git a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift index 362f46688bb5..334565e5f0dd 100644 --- a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift @@ -57,13 +57,11 @@ extension NSNotification.Name { // if the comment can be moderated, show the context menu when tapping the accessory button. // Note that accessoryButtonAction will be ignored when the menu is assigned. - if #available (iOS 14.0, *) { - cell.accessoryButton.showsMenuAsPrimaryAction = isModerationMenuEnabled(for: comment) - cell.accessoryButton.menu = isModerationMenuEnabled(for: comment) ? menu(for: comment, - indexPath: indexPath, - handler: handler, - sourceView: cell.accessoryButton) : nil - } + cell.accessoryButton.showsMenuAsPrimaryAction = isModerationMenuEnabled(for: comment) + cell.accessoryButton.menu = isModerationMenuEnabled(for: comment) ? menu(for: comment, + indexPath: indexPath, + handler: handler, + sourceView: cell.accessoryButton) : nil cell.configure(with: comment, renderMethod: .richContent) { _ in // don't adjust cell height when it's already scrolled out of viewport. diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailFeaturedImageView.swift b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailFeaturedImageView.swift index 3de61d8d4301..bacec85bac52 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailFeaturedImageView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailFeaturedImageView.swift @@ -340,9 +340,7 @@ class ReaderDetailFeaturedImageView: UIView, NibLoadable { navigationItem.standardAppearance = appearance navigationItem.compactAppearance = appearance navigationItem.scrollEdgeAppearance = appearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = appearance - } + navigationItem.compactScrollEdgeAppearance = appearance if isLoaded, imageView.image == nil { navBarTintColor = Styles.endTintColor diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/BloggingPromptsHeaderView.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/BloggingPromptsHeaderView.swift index 71dc149fbef0..471de1910ae1 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/BloggingPromptsHeaderView.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/BloggingPromptsHeaderView.swift @@ -91,15 +91,10 @@ private extension BloggingPromptsHeaderView { } func configureInsets() { - if #available(iOS 15.0, *) { - var config: UIButton.Configuration = .plain() - config.contentInsets = Constants.buttonContentInsets - answerPromptButton.configuration = config - shareButton.configuration = config - } else { - answerPromptButton.contentEdgeInsets = Constants.buttonContentEdgeInsets - shareButton.contentEdgeInsets = Constants.buttonContentEdgeInsets - } + var config: UIButton.Configuration = .plain() + config.contentInsets = Constants.buttonContentInsets + answerPromptButton.configuration = config + shareButton.configuration = config } func configure(_ prompt: BloggingPrompt?) { @@ -143,7 +138,6 @@ private extension BloggingPromptsHeaderView { static let promptSpacing: CGFloat = 8.0 static let answeredViewSpacing: CGFloat = 9.0 static let answerPromptButtonSpacing: CGFloat = 9.0 - static let buttonContentEdgeInsets = UIEdgeInsets(top: 16.0, left: 0.0, bottom: 16.0, right: 0.0) static let buttonContentInsets = NSDirectionalEdgeInsets(top: 16.0, leading: 0.0, bottom: 16.0, trailing: 0.0) } diff --git a/WordPress/Classes/ViewRelated/Views/RichTextView/AnimatedGifAttachmentViewProvider.swift b/WordPress/Classes/ViewRelated/Views/RichTextView/AnimatedGifAttachmentViewProvider.swift index da4fa28c8a34..65b20773431c 100644 --- a/WordPress/Classes/ViewRelated/Views/RichTextView/AnimatedGifAttachmentViewProvider.swift +++ b/WordPress/Classes/ViewRelated/Views/RichTextView/AnimatedGifAttachmentViewProvider.swift @@ -5,7 +5,6 @@ import UIKit * This can be used by using: `NSTextAttachment.registerViewProviderClass` * */ -@available(iOS 15.0, *) class AnimatedGifAttachmentViewProvider: NSTextAttachmentViewProvider { deinit { guard let animatedImageView = view as? CachedAnimatedImageView else { diff --git a/WordPress/Classes/ViewRelated/Views/RichTextView/RichTextView.swift b/WordPress/Classes/ViewRelated/Views/RichTextView/RichTextView.swift index e17ea7ae1a6d..0ca9d176431f 100644 --- a/WordPress/Classes/ViewRelated/Views/RichTextView/RichTextView.swift +++ b/WordPress/Classes/ViewRelated/Views/RichTextView/RichTextView.swift @@ -163,9 +163,7 @@ import UniformTypeIdentifiers pinSubviewToAllEdges(textView) // Allow animatable gifs to be displayed - if #available(iOS 15.0, *) { - NSTextAttachment.registerViewProviderClass(AnimatedGifAttachmentViewProvider.self, forFileType: UTType.gif.identifier) - } + NSTextAttachment.registerViewProviderClass(AnimatedGifAttachmentViewProvider.self, forFileType: UTType.gif.identifier) } fileprivate func renderAttachments() { diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationNavigationController.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationNavigationController.swift index a0567d625138..997f044b38cd 100644 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationNavigationController.swift +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Common/Navigation/MigrationNavigationController.swift @@ -52,9 +52,7 @@ class MigrationNavigationController: UINavigationController { navigationBar.standardAppearance = standardAppearance navigationBar.scrollEdgeAppearance = scrollEdgeAppearance navigationBar.compactAppearance = standardAppearance - if #available(iOS 15.0, *) { - navigationBar.compactScrollEdgeAppearance = scrollEdgeAppearance - } + navigationBar.compactScrollEdgeAppearance = scrollEdgeAppearance navigationBar.isTranslucent = true listenForStateChanges() } diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Delete WordPress/MigrationDeleteWordPressViewController.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Delete WordPress/MigrationDeleteWordPressViewController.swift index 82a7472e99fe..42a27de4b6ea 100644 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Delete WordPress/MigrationDeleteWordPressViewController.swift +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Delete WordPress/MigrationDeleteWordPressViewController.swift @@ -63,9 +63,7 @@ final class MigrationDeleteWordPressViewController: UIViewController { navigationItem.standardAppearance = appearance navigationItem.scrollEdgeAppearance = appearance navigationItem.compactAppearance = appearance - if #available(iOS 15.0, *) { - navigationItem.compactScrollEdgeAppearance = appearance - } + navigationItem.compactScrollEdgeAppearance = appearance } private func setupDismissButton() { diff --git a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewController.swift b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewController.swift index 383b6cb2bf73..e27c55ea2f8a 100644 --- a/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewController.swift +++ b/WordPress/Jetpack/Classes/ViewRelated/WordPress-to-Jetpack Migration/Welcome/MigrationWelcomeViewController.swift @@ -116,13 +116,7 @@ final class MigrationWelcomeViewController: UIViewController { static let tableViewLeadingMargin = CGFloat(30) /// Used for the `tableHeaderView` layout guide margins. - static let tableHeaderViewMargins: NSDirectionalEdgeInsets = { - var insets = NSDirectionalEdgeInsets(top: 20, leading: 30, bottom: 30, trailing: 30) - if #available(iOS 15, *) { - insets.top = 0 - } - return insets - }() + static let tableHeaderViewMargins = NSDirectionalEdgeInsets(top: 0, leading: 30, bottom: 30, trailing: 30) } } From b0d45eb01ea39e2c11e20b76b305d2632b6f8f32 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 21 Jun 2023 18:36:55 -0400 Subject: [PATCH 008/175] Remove MenuSheetViewController --- .../Comments/ReaderCommentsViewController.m | 5 +- .../ReaderCommentsViewController.swift | 36 ---- .../Views/MenuSheetViewController.swift | 171 ------------------ WordPress/WordPress.xcodeproj/project.pbxproj | 6 - 4 files changed, 1 insertion(+), 217 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Views/MenuSheetViewController.swift diff --git a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.m b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.m index ed8e238ddb43..baaf195788c2 100644 --- a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.m +++ b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.m @@ -1122,10 +1122,7 @@ - (void)configureCell:(UITableViewCell *)aCell atIndexPath:(NSIndexPath *)indexP __weak __typeof(self) weakSelf = self; cell.accessoryButtonAction = ^(UIView * _Nonnull sourceView) { - if (comment && [self isModerationMenuEnabledFor:comment]) { - // NOTE: Remove when minimum version is bumped to iOS 14. - [self showMenuSheetFor:comment indexPath:indexPath handler:weakSelf.tableViewHandler sourceView:sourceView]; - } else if (comment) { + if (comment) { [self shareComment:comment sourceView:sourceView]; } }; diff --git a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift index 334565e5f0dd..72496fb0f773 100644 --- a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsViewController.swift @@ -89,28 +89,6 @@ extension NSNotification.Name { present(activityViewController, animated: true, completion: nil) } - /// Shows a contextual menu through `UIPopoverPresentationController`. This is a fallback implementation for iOS 13, since the menu can't be - /// shown programmatically or through a single tap. - /// - /// NOTE: Remove this once we bump the minimum version to iOS 14. - /// - func showMenuSheet(for comment: Comment, indexPath: IndexPath, handler: WPTableViewHandler, sourceView: UIView?) { - let commentMenus = commentMenu(for: comment, indexPath: indexPath, handler: handler, sourceView: sourceView) - let menuViewController = MenuSheetViewController(items: commentMenus.map { menuSection in - // Convert ReaderCommentMenu to MenuSheetViewController.MenuItem - menuSection.map { $0.toMenuItem } - }) - - menuViewController.modalPresentationStyle = .popover - if let popoverPresentationController = menuViewController.popoverPresentationController { - popoverPresentationController.delegate = self - popoverPresentationController.sourceView = sourceView - popoverPresentationController.sourceRect = sourceView?.bounds ?? .null - } - - present(menuViewController, animated: true) - } - func isModerationMenuEnabled(for comment: Comment) -> Bool { return comment.allowsModeration() } @@ -398,18 +376,4 @@ enum ReaderCommentMenu { } } } - - /// NOTE: Remove when minimum version is bumped to iOS 14. - var toMenuItem: MenuSheetViewController.MenuItem { - switch self { - case .unapprove(let handler), - .spam(let handler), - .trash(let handler), - .edit(let handler), - .share(let handler): - return MenuSheetViewController.MenuItem(title: title, image: image) { - handler() - } - } - } } diff --git a/WordPress/Classes/ViewRelated/Views/MenuSheetViewController.swift b/WordPress/Classes/ViewRelated/Views/MenuSheetViewController.swift deleted file mode 100644 index a8b09c0c3f8f..000000000000 --- a/WordPress/Classes/ViewRelated/Views/MenuSheetViewController.swift +++ /dev/null @@ -1,171 +0,0 @@ -import UIKit -import WordPressUI - -/// Provides a fallback implementation for showing `UIMenu` in iOS 13. To "mimic" the `UIContextMenu` appearance, this -/// view controller should be presented modally with a `.popover` presentation style. Note that to simplify things, -/// nested elements will be displayed as if `UIMenuOptions.displayInline` is applied. -/// -/// In iOS 13, `UIMenu` can only appear through long press gesture. There is no way to make it appear programmatically -/// or through different gestures. However, in iOS 14 menus can be configured to appear on tap events. Refer to -/// `showsMenuAsPrimaryAction` for more details. -/// -/// TODO: Remove this component (and its usage) in favor of `UIMenu` when the minimum version is bumped to iOS 14. -/// -class MenuSheetViewController: UITableViewController { - - struct MenuItem { - let title: String - let image: UIImage? - let handler: () -> Void - let destructive: Bool - - init(title: String, image: UIImage? = nil, destructive: Bool = false, handler: @escaping () -> Void) { - self.title = title - self.image = image - self.handler = handler - self.destructive = destructive - } - - var foregroundColor: UIColor { - return destructive ? .error : .text - } - } - - private let itemSource: [[MenuItem]] - private let orientation: UIDeviceOrientation // used to track if orientation changes. - - // MARK: Lifecycle - - required init(items: [[MenuItem]]) { - self.itemSource = items - self.orientation = UIDevice.current.orientation - - super.init(style: .plain) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - configureTable() - } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - preferredContentSize = CGSize(width: min(tableView.contentSize.width, Constants.maxWidth), height: tableView.contentSize.height) - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - - // Dismiss the menu when the orientation changes. This mimics the behavior of UIContextMenu/UIMenu. - if UIDevice.current.orientation != orientation { - dismissMenu() - } - } - -} - -// MARK: - Table View - -extension MenuSheetViewController { - - override func numberOfSections(in tableView: UITableView) -> Int { - return itemSource.count - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let items = itemSource[safe: section] else { - return 0 - } - return items.count - } - - /// Override separator color in dark mode so it kinda matches the separator color in `UIContextMenu`. - /// With system colors, somehow dark colors won't go darker below the cell's background color. - /// Note that returning nil means falling back to the default behavior. - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard traitCollection.userInterfaceStyle == .dark else { - return nil - } - - let headerView = UIView() - headerView.backgroundColor = Constants.darkSeparatorColor - return headerView - } - - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return section == 0 ? tableView.sectionHeaderHeight : Constants.tableSectionHeight - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let items = itemSource[safe: indexPath.section], - let item = items[safe: indexPath.row] else { - return .init() - } - - let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellIdentifier, for: indexPath) - cell.tintColor = item.foregroundColor - cell.textLabel?.textColor = item.foregroundColor - cell.textLabel?.setText(item.title) - cell.textLabel?.numberOfLines = 0 - cell.accessoryView = UIImageView(image: item.image?.withTintColor(.text)) - - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - dismissMenu { - guard let items = self.itemSource[safe: indexPath.section], - let item = items[safe: indexPath.row] else { - return - } - - item.handler() - } - } -} - -// MARK: - Private Helpers - -private extension MenuSheetViewController { - struct Constants { - // maximum width follows the approximate width of `UIContextMenu`. - static let maxWidth: CGFloat = 250 - static let tableSectionHeight: CGFloat = 8 - static let darkSeparatorColor = UIColor(fromRGBColorWithRed: 11, green: 11, blue: 11) - static let cellIdentifier = "cell" - } - - func configureTable() { - tableView.register(UITableViewCell.self, forCellReuseIdentifier: Constants.cellIdentifier) - tableView.sectionHeaderHeight = 0 - tableView.bounces = false - - // draw the separators from edge to edge. - tableView.separatorInset = .zero - - // hide separators for the last row. - tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 0)) - } - - func dismissMenu(completion: (() -> Void)? = nil) { - if let controller = popoverPresentationController { - controller.delegate?.presentationControllerWillDismiss?(controller) - } - - dismiss(animated: true) { - defer { - if let controller = self.popoverPresentationController { - controller.delegate?.presentationControllerDidDismiss?(controller) - } - } - completion?() - } - } -} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 2fe87e483c4b..134096c9d6ba 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -5500,8 +5500,6 @@ FE2E3729281C839C00A1E82A /* BloggingPromptsServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2E3728281C839C00A1E82A /* BloggingPromptsServiceTests.swift */; }; FE320CC5294705990046899B /* ReaderPostBackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE320CC4294705990046899B /* ReaderPostBackupTests.swift */; }; FE32E7F12844971000744D80 /* ReminderScheduleCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32E7F02844971000744D80 /* ReminderScheduleCoordinatorTests.swift */; }; - FE32EFFF275914390040BE67 /* MenuSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32EFFE275914390040BE67 /* MenuSheetViewController.swift */; }; - FE32F000275914390040BE67 /* MenuSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32EFFE275914390040BE67 /* MenuSheetViewController.swift */; }; FE32F002275F602E0040BE67 /* CommentContentRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32F001275F602E0040BE67 /* CommentContentRenderer.swift */; }; FE32F003275F602E0040BE67 /* CommentContentRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32F001275F602E0040BE67 /* CommentContentRenderer.swift */; }; FE32F006275F62620040BE67 /* WebCommentContentRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32F005275F62620040BE67 /* WebCommentContentRenderer.swift */; }; @@ -9296,7 +9294,6 @@ FE320CC4294705990046899B /* ReaderPostBackupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderPostBackupTests.swift; sourceTree = ""; }; FE32E7F02844971000744D80 /* ReminderScheduleCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderScheduleCoordinatorTests.swift; sourceTree = ""; }; FE32E7F32846A68800744D80 /* WordPress 142.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 142.xcdatamodel"; sourceTree = ""; }; - FE32EFFE275914390040BE67 /* MenuSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuSheetViewController.swift; sourceTree = ""; }; FE32F001275F602E0040BE67 /* CommentContentRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentContentRenderer.swift; sourceTree = ""; }; FE32F005275F62620040BE67 /* WebCommentContentRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCommentContentRenderer.swift; sourceTree = ""; }; FE341704275FA157005D5CA7 /* RichCommentContentRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichCommentContentRenderer.swift; sourceTree = ""; }; @@ -9763,7 +9760,6 @@ 37EAAF4C1A11799A006D6306 /* CircularImageView.swift */, ADF544C0195A0F620092213D /* CustomHighlightButton.h */, ADF544C1195A0F620092213D /* CustomHighlightButton.m */, - FE32EFFE275914390040BE67 /* MenuSheetViewController.swift */, B5B410B51B1772B000CFCF8D /* NavigationTitleView.swift */, 982A4C3420227D6700B5518E /* NoResultsViewController.swift */, 98B33C87202283860071E1E2 /* NoResults.storyboard */, @@ -21643,7 +21639,6 @@ 2481B17F260D4D4E00AE59DB /* WPAccount+Lookup.swift in Sources */, 7E3E9B702177C9DC00FD5797 /* GutenbergViewController.swift in Sources */, 800035C1292307E8007D2D26 /* ExtensionConfiguration.swift in Sources */, - FE32EFFF275914390040BE67 /* MenuSheetViewController.swift in Sources */, 3FC7F89E2612341900FD8728 /* UnifiedPrologueStatsContentView.swift in Sources */, 7D21280D251CF0850086DD2C /* EditPageViewController.swift in Sources */, 738B9A4F21B85CF20005062B /* SiteCreator.swift in Sources */, @@ -23919,7 +23914,6 @@ FABB21612602FC2C00C8785C /* ReaderShowAttributionAction.swift in Sources */, FABB21622602FC2C00C8785C /* LinkSettingsViewController.swift in Sources */, 9856A39E261FC21E008D6354 /* UserProfileUserInfoCell.swift in Sources */, - FE32F000275914390040BE67 /* MenuSheetViewController.swift in Sources */, FABB21632602FC2C00C8785C /* FeatureItemCell.swift in Sources */, C3FBF4E928AFEDF8003797DF /* JetpackBrandingAnalyticsHelper.swift in Sources */, 803C493C283A7C0C00003E9B /* QuickStartChecklistHeader.swift in Sources */, From 364c75c0b07c6671ce528b6887d93cbe1c07b12c Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 23 Jun 2023 14:52:26 +0800 Subject: [PATCH 009/175] add media blocks ui test --- WordPress/UITests/README.md | 3 +- .../UITests/Tests/EditorGutenbergTests.swift | 15 +++-- .../Screens/Editor/BlockEditorScreen.swift | 62 +++++++++++++++++-- .../Screens/TabNavComponent.swift | 7 ++- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/WordPress/UITests/README.md b/WordPress/UITests/README.md index 97e898bd1e7a..e2d66a9a0e5d 100644 --- a/WordPress/UITests/README.md +++ b/WordPress/UITests/README.md @@ -32,8 +32,7 @@ The following flows are covered/planned to be covered by UI tests. Tests that ar - [x] Publish Basic Public Post with Category and Tag - [x] Add and Remove Featured Image - [x] Add Gallery Block - - [ ] Add Image Block - - [ ] Add Video Block + - [x] Add Media Blocks (Image, Video and Audio) - [ ] Create Scheduled Post - [ ] Pages: - [ ] Create Page from Layout diff --git a/WordPress/UITests/Tests/EditorGutenbergTests.swift b/WordPress/UITests/Tests/EditorGutenbergTests.swift index 7fa174656578..d127324fbc61 100644 --- a/WordPress/UITests/Tests/EditorGutenbergTests.swift +++ b/WordPress/UITests/Tests/EditorGutenbergTests.swift @@ -10,10 +10,9 @@ class EditorGutenbergTests: XCTestCase { email: WPUITestCredentials.testWPcomUserEmail, password: WPUITestCredentials.testWPcomPassword ) - try EditorFlow - .goToMySiteScreen() - .tabBar.gotoBlockEditorScreen() - .dismissNotificationAlertIfNeeded(.accept) + + try TabNavComponent() + .gotoBlockEditorScreen() } override func tearDownWithError() throws { @@ -77,4 +76,12 @@ class EditorGutenbergTests: XCTestCase { .addImageGallery() .verifyContentStructure(blocks: 2, words: content.components(separatedBy: " ").count, characters: content.count) } + + func testAddMediaBlocks() throws { + try BlockEditorScreen() + .addImage() + .addVideo() + .addAudio() + .verifyMediaBlocksDisplayed() + } } diff --git a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift index e8b37d253bb5..26ff87dc3e42 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift @@ -8,19 +8,30 @@ public class BlockEditorScreen: ScreenObject { $0.navigationBars["Gutenberg Editor Navigation Bar"].buttons["Close"] } - var editorCloseButton: XCUIElement { editorCloseButtonGetter(app) } - let addBlockButtonGetter: (XCUIApplication) -> XCUIElement = { - $0.buttons["add-block-button"] // Uses a testID + $0.buttons["add-block-button"] } - var addBlockButton: XCUIElement { addBlockButtonGetter(app) } - let moreButtonGetter: (XCUIApplication) -> XCUIElement = { $0.buttons["more_post_options"] } + let insertFromUrlButtonGetter: (XCUIApplication) -> XCUIElement = { + $0.buttons["Insert from URL"] + } + + let applyButtonGetter: (XCUIApplication) -> XCUIElement = { + $0.buttons["Apply"] + } + + var editorCloseButton: XCUIElement { editorCloseButtonGetter(app) } + var addBlockButton: XCUIElement { addBlockButtonGetter(app) } var moreButton: XCUIElement { moreButtonGetter(app) } + var insertFromUrlButton: XCUIElement { insertFromUrlButtonGetter(app) } + var applyButton: XCUIElement { applyButtonGetter(app) } + + var videoUrlPath = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" + var audioUrlPath = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" public init(app: XCUIApplication = XCUIApplication()) throws { // The block editor has _many_ elements but most are loaded on-demand. To verify the screen @@ -80,6 +91,45 @@ public class BlockEditorScreen: ScreenObject { return self } + public func addVideo() throws -> Self { + addMediaBlockFromUrl( + blockType: "Video block", + UrlPath: videoUrlPath + ) + + return self + } + + public func addAudio() throws -> Self { + addMediaBlockFromUrl( + blockType: "Audio block", + UrlPath: audioUrlPath + ) + + return self + } + + private func addMediaBlockFromUrl(blockType: String, UrlPath: String) { + addBlock(blockType) + insertFromUrlButton.tap() + app.textFields.element.typeText(UrlPath) + applyButton.tap() + } + + @discardableResult + public func verifyMediaBlocksDisplayed() throws -> Self { + let imagePredicate = NSPredicate(format: "label CONTAINS[c] 'Image block'") + let videoPredicate = NSPredicate(format: "label CONTAINS[c] 'Video block'") + let audioPredicate = NSPredicate(format: "label CONTAINS[c] 'Audio block'") + + XCTAssertTrue(app.buttons.containing(imagePredicate).firstMatch.exists) + XCTAssertTrue(app.buttons.containing(videoPredicate).firstMatch.exists) + XCTAssertTrue(app.buttons.containing(audioPredicate).firstMatch.exists) + + return self + } + + /** Selects a block based on part of the block label (e.g. partial text in a paragraph block) */ @@ -186,7 +236,7 @@ public class BlockEditorScreen: ScreenObject { private func addBlock(_ blockLabel: String) { addBlockButton.tap() let blockButton = app.buttons[blockLabel] - XCTAssertTrue(blockButton.waitForIsHittable(timeout: 3)) + if !blockButton.exists { app.scrollDownToElement(element: blockButton) } blockButton.tap() } diff --git a/WordPress/UITestsFoundation/Screens/TabNavComponent.swift b/WordPress/UITestsFoundation/Screens/TabNavComponent.swift index 93bc9eab9555..c4c627d0f901 100644 --- a/WordPress/UITestsFoundation/Screens/TabNavComponent.swift +++ b/WordPress/UITestsFoundation/Screens/TabNavComponent.swift @@ -56,10 +56,11 @@ public class TabNavComponent: ScreenObject { return try AztecEditorScreen(mode: .rich) } + @discardableResult public func gotoBlockEditorScreen() throws -> BlockEditorScreen { - let mySite = try goToMySiteScreen() - let actionSheet = try mySite.goToCreateSheet() - actionSheet.goToBlogPost() + try goToMySiteScreen() + .goToCreateSheet() + .goToBlogPost() return try BlockEditorScreen() } From 913aeead530f2d7fa07d10c216857419dbdb83d4 Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 23 Jun 2023 16:34:39 +0800 Subject: [PATCH 010/175] attempt to remove some dismiss alerts functions --- WordPress/UITests/Flows/LoginFlow.swift | 3 --- .../UITestsFoundation/Screens/Editor/BlockEditorScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/TabNavComponent.swift | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/WordPress/UITests/Flows/LoginFlow.swift b/WordPress/UITests/Flows/LoginFlow.swift index 5d778d81a22c..794be5eff30c 100644 --- a/WordPress/UITests/Flows/LoginFlow.swift +++ b/WordPress/UITests/Flows/LoginFlow.swift @@ -10,7 +10,6 @@ class LoginFlow { .proceedWith(email: email) .proceedWithValidPassword() .continueWithSelectedSite(title: selectedSiteTitle) - .dismissNotificationAlertIfNeeded() } // Login with self-hosted site via Site Address. @@ -21,7 +20,6 @@ class LoginFlow { .proceedWith(siteUrl: siteUrl) .proceedWith(username: username, password: password) .continueWithSelectedSite() - .dismissNotificationAlertIfNeeded() } // Login with WP site via Site Address. @@ -33,7 +31,6 @@ class LoginFlow { .proceedWith(email: email) .proceedWithValidPassword() .continueWithSelectedSite() - .dismissNotificationAlertIfNeeded() } // Login with self-hosted site via Site Address. diff --git a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift index 26ff87dc3e42..9f65ba573a3a 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift @@ -129,7 +129,6 @@ public class BlockEditorScreen: ScreenObject { return self } - /** Selects a block based on part of the block label (e.g. partial text in a paragraph block) */ @@ -236,7 +235,7 @@ public class BlockEditorScreen: ScreenObject { private func addBlock(_ blockLabel: String) { addBlockButton.tap() let blockButton = app.buttons[blockLabel] - if !blockButton.exists { app.scrollDownToElement(element: blockButton) } + if !blockButton.isHittable { app.scrollDownToElement(element: blockButton) } blockButton.tap() } diff --git a/WordPress/UITestsFoundation/Screens/TabNavComponent.swift b/WordPress/UITestsFoundation/Screens/TabNavComponent.swift index c4c627d0f901..e57ca925e846 100644 --- a/WordPress/UITestsFoundation/Screens/TabNavComponent.swift +++ b/WordPress/UITestsFoundation/Screens/TabNavComponent.swift @@ -73,7 +73,6 @@ public class TabNavComponent: ScreenObject { public func goToNotificationsScreen() throws -> NotificationsScreen { notificationsTabButton.tap() - try dismissNotificationAlertIfNeeded() return try NotificationsScreen() } From ad4e2fb53523f586d56a46d76f4032c8d8f8517f Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Fri, 23 Jun 2023 17:34:45 +0700 Subject: [PATCH 011/175] Temporarily use WordPressKit from trunk --- Podfile | 4 ++-- Podfile.lock | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Podfile b/Podfile index 221e4666d016..193e38d4708c 100644 --- a/Podfile +++ b/Podfile @@ -50,8 +50,8 @@ def wordpress_ui end def wordpress_kit - pod 'WordPressKit', '~> 8.4-beta' - # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: '' + # pod 'WordPressKit', '~> 8.4-beta' + pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: 'trunk' # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', tag: '' # pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '' # pod 'WordPressKit', path: '../WordPressKit-iOS' diff --git a/Podfile.lock b/Podfile.lock index 31df298e5fef..1147dce88e81 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -612,7 +612,7 @@ DEPENDENCIES: - SwiftLint (~> 0.50) - WordPress-Editor-iOS (~> 1.19.8) - WordPressAuthenticator (~> 6.1-beta) - - WordPressKit (~> 8.4-beta) + - WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, branch `trunk`) - WordPressShared (from `https://github.com/wordpress-mobile/WordPress-iOS-Shared.git`, branch `trunk`) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) @@ -661,7 +661,6 @@ SPEC REPOS: - UIDeviceIdentifier - WordPress-Aztec-iOS - WordPress-Editor-iOS - - WordPressKit - WordPressUI - WPMediaPicker - wpxmlrpc @@ -776,6 +775,9 @@ EXTERNAL SOURCES: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true :tag: v1.98.0 + WordPressKit: + :branch: trunk + :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git WordPressShared: :branch: trunk :git: https://github.com/wordpress-mobile/WordPress-iOS-Shared.git @@ -794,6 +796,9 @@ CHECKOUT OPTIONS: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true :tag: v1.98.0 + WordPressKit: + :commit: 058f63431202315b9002f3cd29703eccf802c05c + :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git WordPressShared: :commit: 9a010fdab8d31f9e1fa0511f231e7068ef0170b1 :git: https://github.com/wordpress-mobile/WordPress-iOS-Shared.git @@ -900,6 +905,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 -PODFILE CHECKSUM: 3f47ee0c345c8439e137d677752d9c4166d2c695 +PODFILE CHECKSUM: 90c3e9c419d04dcf47e28a9ffe4ac34e0a9382bb COCOAPODS: 1.12.1 From a2376e823fe3d02928520ea86e62653568454bdd Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 23 Jun 2023 19:24:03 +0800 Subject: [PATCH 012/175] re-add dismissNotificationAlertIfNeeded() - it's needed --- WordPress/UITestsFoundation/Screens/TabNavComponent.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/UITestsFoundation/Screens/TabNavComponent.swift b/WordPress/UITestsFoundation/Screens/TabNavComponent.swift index e57ca925e846..c4c627d0f901 100644 --- a/WordPress/UITestsFoundation/Screens/TabNavComponent.swift +++ b/WordPress/UITestsFoundation/Screens/TabNavComponent.swift @@ -73,6 +73,7 @@ public class TabNavComponent: ScreenObject { public func goToNotificationsScreen() throws -> NotificationsScreen { notificationsTabButton.tap() + try dismissNotificationAlertIfNeeded() return try NotificationsScreen() } From 658f071f2bde8ddb6b0de9a9c6a36b03b97269a6 Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 23 Jun 2023 20:29:36 +0800 Subject: [PATCH 013/175] remove throws for functions without try --- .../Screens/Editor/BlockEditorScreen.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift index 9f65ba573a3a..36af0cb19c34 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift @@ -91,7 +91,7 @@ public class BlockEditorScreen: ScreenObject { return self } - public func addVideo() throws -> Self { + public func addVideo() -> Self { addMediaBlockFromUrl( blockType: "Video block", UrlPath: videoUrlPath @@ -100,7 +100,7 @@ public class BlockEditorScreen: ScreenObject { return self } - public func addAudio() throws -> Self { + public func addAudio() -> Self { addMediaBlockFromUrl( blockType: "Audio block", UrlPath: audioUrlPath @@ -117,7 +117,7 @@ public class BlockEditorScreen: ScreenObject { } @discardableResult - public func verifyMediaBlocksDisplayed() throws -> Self { + public func verifyMediaBlocksDisplayed() -> Self { let imagePredicate = NSPredicate(format: "label CONTAINS[c] 'Image block'") let videoPredicate = NSPredicate(format: "label CONTAINS[c] 'Video block'") let audioPredicate = NSPredicate(format: "label CONTAINS[c] 'Audio block'") From 5bfff5ec65bb5fc6eb9452e73468178fd5108704 Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 23 Jun 2023 21:32:27 +0800 Subject: [PATCH 014/175] increase waitTimeout on PrologueScreen --- .../Screens/Login/Unified/PrologueScreen.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/UITestsFoundation/Screens/Login/Unified/PrologueScreen.swift b/WordPress/UITestsFoundation/Screens/Login/Unified/PrologueScreen.swift index 09aaa5754d66..0ce3bbe70e84 100644 --- a/WordPress/UITestsFoundation/Screens/Login/Unified/PrologueScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/Unified/PrologueScreen.swift @@ -18,7 +18,7 @@ public class PrologueScreen: ScreenObject { try super.init( expectedElementGetters: [continueButtonGetter, siteAddressButtonGetter], app: app, - waitTimeout: 3 + waitTimeout: 7 ) } From d6b022d2fdf303256275e500b8d1ef31082d9d63 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 23 Jun 2023 09:49:48 -0400 Subject: [PATCH 015/175] Add a bit of spacing between dashboard cards and fab --- .../Blog/Blog Dashboard/BlogDashboardViewController.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift index 7e0934cef9ed..dff2bafcdeba 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift @@ -238,7 +238,7 @@ extension BlogDashboardViewController { let isQuickActionSection = viewModel.isQuickActionsSection(sectionIndex) let isMigrationSuccessCardSection = viewModel.isMigrationSuccessCardSection(sectionIndex) let horizontalInset = isQuickActionSection ? 0 : Constants.horizontalSectionInset - let bottomInset = isQuickActionSection || isMigrationSuccessCardSection ? 0 : Constants.verticalSectionInset + let bottomInset = isQuickActionSection || isMigrationSuccessCardSection ? 0 : Constants.bottomSectionInset section.contentInsets = NSDirectionalEdgeInsets(top: Constants.verticalSectionInset, leading: horizontalInset, bottom: bottomInset, @@ -321,6 +321,10 @@ extension BlogDashboardViewController { static let estimatedHeight: CGFloat = 44 static let horizontalSectionInset: CGFloat = 20 static let verticalSectionInset: CGFloat = 20 + static var bottomSectionInset: CGFloat { + // Make room for FAB on iPhone + WPDeviceIdentification.isiPad() ? verticalSectionInset : 86 + } static let cellSpacing: CGFloat = 20 } } From c7f4531b346322576afec1c8ff524d3a28a223f3 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Sat, 24 Jun 2023 17:38:05 +0700 Subject: [PATCH 016/175] Add Core Data model for PublicizeInfo --- .../WordPress.xcdatamodeld/.xccurrentversion | 2 +- .../WordPress 151.xcdatamodel/contents | 1039 +++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 4 +- 3 files changed, 1043 insertions(+), 2 deletions(-) create mode 100644 WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents diff --git a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion b/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion index ebc7bef3f617..42d23e05307f 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion +++ b/WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - WordPress 150.xcdatamodel + WordPress 151.xcdatamodel diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents new file mode 100644 index 000000000000..3861be1150c6 --- /dev/null +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents @@ -0,0 +1,1039 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index a23e67eed617..a2a88135a1cf 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -9289,6 +9289,7 @@ FE06AC8226C3BD0900B69DE4 /* ShareAppContentPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppContentPresenter.swift; sourceTree = ""; }; FE06AC8426C3C2F800B69DE4 /* ShareAppTextActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppTextActivityItemSource.swift; sourceTree = ""; }; FE18495727F5ACBA00D26879 /* DashboardPromptsCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPromptsCardCell.swift; sourceTree = ""; }; + FE1E200F2A45ACE900CE7C90 /* WordPress 151.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 151.xcdatamodel"; sourceTree = ""; }; FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = richCommentTemplate.html; path = Resources/HTML/richCommentTemplate.html; sourceTree = ""; }; FE23EB4826E7C91F005A1698 /* richCommentStyle.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = richCommentStyle.css; path = Resources/HTML/richCommentStyle.css; sourceTree = ""; }; FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsNotificationSheetViewController.swift; sourceTree = ""; }; @@ -30669,6 +30670,7 @@ E125443B12BF5A7200D87A0A /* WordPress.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + FE1E200F2A45ACE900CE7C90 /* WordPress 151.xcdatamodel */, FA3A281D2A42049F00206D74 /* WordPress 150.xcdatamodel */, FE5096572A13D5BA00DDD071 /* WordPress 149.xcdatamodel */, FA98B61329A39DA80071AAE8 /* WordPress 148.xcdatamodel */, @@ -30820,7 +30822,7 @@ 8350E15911D28B4A00A7B073 /* WordPress.xcdatamodel */, E125443D12BF5A7200D87A0A /* WordPress 2.xcdatamodel */, ); - currentVersion = FA3A281D2A42049F00206D74 /* WordPress 150.xcdatamodel */; + currentVersion = FE1E200F2A45ACE900CE7C90 /* WordPress 151.xcdatamodel */; name = WordPress.xcdatamodeld; path = Classes/WordPress.xcdatamodeld; sourceTree = ""; From c3c3f82e2af1e32ea169d5d0e1202fee8b204571 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Sat, 24 Jun 2023 17:46:31 +0700 Subject: [PATCH 017/175] Record model changes in MIGRATIONS --- MIGRATIONS.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 78bedcc79b39..6d0be6093697 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -3,6 +3,22 @@ This file documents changes in the data model. Please explain any changes to the data model as well as any custom migrations. +## WordPress 151 + +@dvdchr 2023-06-23 + +- Created a new entity `PublicizeInfo` with: + - `sharedPostsCount` (required, default `0`, `Int 64`) + - `sharesRemaining` (required, default `0`, `Int 64`) + - `sharingLimit` (required, default `0`, `Int 64`) + - `toBePublicizedCount` (required, default `0`, `Int 64`) + +- Created one-to-many relationship between `PublicizeInfo` and `Blog` + - `PublicizeInfo` + - `blog` (optional, to-many, nullify on delete) + - `Blog` + - `publicizeInfo` (required, to-one, cascade on delete) + ## WordPress 150 @momozw 2023-06-20 From a69c568c7ea4e25ee31f888f49f1579633f05d1f Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Sat, 24 Jun 2023 21:17:55 +0700 Subject: [PATCH 018/175] Add NSManagedObject subclass for PublicizeInfo --- .../Models/PublicizeInfo+CoreDataClass.swift | 16 ++++++++++++++++ .../PublicizeInfo+CoreDataProperties.swift | 19 +++++++++++++++++++ .../WordPress 151.xcdatamodel/contents | 2 +- WordPress/WordPress.xcodeproj/project.pbxproj | 12 ++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift create mode 100644 WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift diff --git a/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift b/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift new file mode 100644 index 000000000000..cf5f424ea1dc --- /dev/null +++ b/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift @@ -0,0 +1,16 @@ +import Foundation +import CoreData + +/// `PublicizeInfo` encapsulates the information related to Jetpack Social auto-sharing. +/// +/// WP.com sites will not have a `PublicizeInfo`, and currently doesn't have auto-sharing limitations. +/// Furthermore, sites eligible for unlimited sharing will still return a `PublicizeInfo` along with its sharing +/// limitations, but the numbers should be ignored (at least for now). +/// +public class PublicizeInfo: NSManagedObject { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "PublicizeInfo") + } + +} diff --git a/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift b/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift new file mode 100644 index 000000000000..121070dd15eb --- /dev/null +++ b/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift @@ -0,0 +1,19 @@ +import Foundation +import CoreData + +extension PublicizeInfo { + /// The maximum number of Social shares for the associated `blog`. + @NSManaged public var sharingLimit: Int64 + + /// The number of Social sharing to be published in the future. + @NSManaged public var toBePublicizedCount: Int64 + + /// The number of posts that have been auto-shared. + @NSManaged public var sharedPostsCount: Int64 + + /// The remaining Social shares available for the associated `blog`. + @NSManaged public var sharesRemaining: Int64 + + /// The associated Blog instance. + @NSManaged public var blog: Blog? +} diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents index 3861be1150c6..b9b607573462 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents @@ -666,7 +666,7 @@ - + diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index a2a88135a1cf..ae07e2600175 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -5489,6 +5489,10 @@ FE06AC8326C3BD0900B69DE4 /* ShareAppContentPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE06AC8226C3BD0900B69DE4 /* ShareAppContentPresenter.swift */; }; FE06AC8526C3C2F800B69DE4 /* ShareAppTextActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE06AC8426C3C2F800B69DE4 /* ShareAppTextActivityItemSource.swift */; }; FE18495827F5ACBA00D26879 /* DashboardPromptsCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE18495727F5ACBA00D26879 /* DashboardPromptsCardCell.swift */; }; + FE1E20152A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */; }; + FE1E20162A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */; }; + FE1E20172A47042500CE7C90 /* PublicizeInfo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */; }; + FE1E20182A47042500CE7C90 /* PublicizeInfo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */; }; FE23EB4926E7C91F005A1698 /* richCommentTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */; }; FE23EB4A26E7C91F005A1698 /* richCommentTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */; }; FE23EB4B26E7C91F005A1698 /* richCommentStyle.css in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4826E7C91F005A1698 /* richCommentStyle.css */; }; @@ -9290,6 +9294,8 @@ FE06AC8426C3C2F800B69DE4 /* ShareAppTextActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppTextActivityItemSource.swift; sourceTree = ""; }; FE18495727F5ACBA00D26879 /* DashboardPromptsCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPromptsCardCell.swift; sourceTree = ""; }; FE1E200F2A45ACE900CE7C90 /* WordPress 151.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 151.xcdatamodel"; sourceTree = ""; }; + FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "PublicizeInfo+CoreDataClass.swift"; path = "/Users/dvdchr/code/github/wpios/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift"; sourceTree = ""; }; + FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "PublicizeInfo+CoreDataProperties.swift"; path = "/Users/dvdchr/code/github/wpios/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift"; sourceTree = ""; }; FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = richCommentTemplate.html; path = Resources/HTML/richCommentTemplate.html; sourceTree = ""; }; FE23EB4826E7C91F005A1698 /* richCommentStyle.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = richCommentStyle.css; path = Resources/HTML/richCommentStyle.css; sourceTree = ""; }; FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsNotificationSheetViewController.swift; sourceTree = ""; }; @@ -10693,6 +10699,8 @@ 082AB9DC1C4F035E000CA523 /* PostTag.m */, 08B6D6F01C8F7DCE0052C52B /* PostType.h */, 08B6D6F11C8F7DCE0052C52B /* PostType.m */, + FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */, + FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */, E6374DBD1C444D8B00F24720 /* PublicizeConnection.swift */, 4A1E77C82988997C006281CC /* PublicizeConnection+Creation.swift */, E6374DBE1C444D8B00F24720 /* PublicizeService.swift */, @@ -21342,6 +21350,7 @@ 3F8B45A9292C1F2C00730FA4 /* DashboardMigrationSuccessCell.swift in Sources */, 8B5E1DD827EA5929002EBEE3 /* PostCoordinator+Dashboard.swift in Sources */, 98458CB821A39D350025D232 /* StatsNoDataRow.swift in Sources */, + FE1E20152A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift in Sources */, 3234BB172530DFCA0068DA40 /* ReaderTableCardCell.swift in Sources */, 7462BFD42028CD4400B552D8 /* ShareNoticeNavigationCoordinator.swift in Sources */, 803BB97C2959559500B3F6D6 /* RootViewPresenter.swift in Sources */, @@ -21349,6 +21358,7 @@ 178810B52611D25600A98BD8 /* Text+BoldSubString.swift in Sources */, 3FA53E9C256571D800F4D9A2 /* HomeWidgetCache.swift in Sources */, FAB8AB5F25AFFD0600F9F8A0 /* JetpackRestoreStatusCoordinator.swift in Sources */, + FE1E20172A47042500CE7C90 /* PublicizeInfo+CoreDataClass.swift in Sources */, F913BB0E24B3C58B00C19032 /* EventLoggingDelegate.swift in Sources */, 4089C51022371B120031CE78 /* TodayStatsRecordValue+CoreDataClass.swift in Sources */, 3F8B45A8292C1F2500730FA4 /* MigrationSuccessCardView.swift in Sources */, @@ -24855,6 +24865,7 @@ C79C307D26EA919F00E88514 /* ReferrerDetailsViewModel.swift in Sources */, 8BBC778C27B5531700DBA087 /* BlogDashboardPersistence.swift in Sources */, FABB24172602FC2C00C8785C /* BlogSettings.swift in Sources */, + FE1E20162A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift in Sources */, FABB24182602FC2C00C8785C /* WKWebView+UserAgent.swift in Sources */, FABB24192602FC2C00C8785C /* JetpackCapabilitiesService.swift in Sources */, FABB241A2602FC2C00C8785C /* PostToPost30To31.m in Sources */, @@ -25029,6 +25040,7 @@ FABB24942602FC2C00C8785C /* PluginViewController.swift in Sources */, 011F52C92A16551A00B04114 /* FreeToPaidPlansCoordinator.swift in Sources */, 179A70F12729834B006DAC0A /* Binding+OnChange.swift in Sources */, + FE1E20182A47042500CE7C90 /* PublicizeInfo+CoreDataClass.swift in Sources */, 17F11EDC268623BA00D1BBA7 /* BloggingRemindersScheduleFormatter.swift in Sources */, FABB24952602FC2C00C8785C /* ReaderSiteTopic.swift in Sources */, FABB24962602FC2C00C8785C /* JetpackBackupCompleteViewController.swift in Sources */, From 4421f2fb7a32705983360d635eb31f0bb3cf3faf Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Sat, 24 Jun 2023 21:30:47 +0700 Subject: [PATCH 019/175] Update WordPressKit to 8.4.0-beta.2 --- Podfile | 4 ++-- Podfile.lock | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Podfile b/Podfile index 193e38d4708c..221e4666d016 100644 --- a/Podfile +++ b/Podfile @@ -50,8 +50,8 @@ def wordpress_ui end def wordpress_kit - # pod 'WordPressKit', '~> 8.4-beta' - pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: 'trunk' + pod 'WordPressKit', '~> 8.4-beta' + # 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', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '' # pod 'WordPressKit', path: '../WordPressKit-iOS' diff --git a/Podfile.lock b/Podfile.lock index 1147dce88e81..2ad86bd8e90c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -512,7 +512,7 @@ PODS: - WordPressKit (~> 8.0-beta) - WordPressShared (~> 2.1-beta) - WordPressUI (~> 1.7-beta) - - WordPressKit (8.4.0-beta.1): + - WordPressKit (8.4.0-beta.2): - Alamofire (~> 4.8.0) - NSObject-SafeExpectations (~> 0.0.4) - UIDeviceIdentifier (~> 2.0) @@ -612,7 +612,7 @@ DEPENDENCIES: - SwiftLint (~> 0.50) - WordPress-Editor-iOS (~> 1.19.8) - WordPressAuthenticator (~> 6.1-beta) - - WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, branch `trunk`) + - WordPressKit (~> 8.4-beta) - WordPressShared (from `https://github.com/wordpress-mobile/WordPress-iOS-Shared.git`, branch `trunk`) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) @@ -661,6 +661,7 @@ SPEC REPOS: - UIDeviceIdentifier - WordPress-Aztec-iOS - WordPress-Editor-iOS + - WordPressKit - WordPressUI - WPMediaPicker - wpxmlrpc @@ -775,9 +776,6 @@ EXTERNAL SOURCES: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true :tag: v1.98.0 - WordPressKit: - :branch: trunk - :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git WordPressShared: :branch: trunk :git: https://github.com/wordpress-mobile/WordPress-iOS-Shared.git @@ -796,9 +794,6 @@ CHECKOUT OPTIONS: :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true :tag: v1.98.0 - WordPressKit: - :commit: 058f63431202315b9002f3cd29703eccf802c05c - :git: https://github.com/wordpress-mobile/WordPressKit-iOS.git WordPressShared: :commit: 9a010fdab8d31f9e1fa0511f231e7068ef0170b1 :git: https://github.com/wordpress-mobile/WordPress-iOS-Shared.git @@ -890,7 +885,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 WordPress-Editor-iOS: 9eb9f12f21a5209cb837908d81ffe1e31cb27345 WordPressAuthenticator: b0b900696de5129a215adcd1e9ae6eb89da36ac8 - WordPressKit: 45b04f55d471f1edde248b84614d3208b9e2e88a + WordPressKit: 3f3a45e11a21e39f500c73362e10149a7322ed67 WordPressShared: 87f3ee89b0a3e83106106f13a8b71605fb8eb6d2 WordPressUI: c5be816f6c7b3392224ac21de9e521e89fa108ac WPMediaPicker: 0d40b8d66b6dfdaa2d6a41e3be51249ff5898775 @@ -905,6 +900,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 -PODFILE CHECKSUM: 90c3e9c419d04dcf47e28a9ffe4ac34e0a9382bb +PODFILE CHECKSUM: 3f47ee0c345c8439e137d677752d9c4166d2c695 COCOAPODS: 1.12.1 From f8b0b1fd64f07881fa3bdba7f7950243e37f7008 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Sun, 25 Jun 2023 22:10:29 +0700 Subject: [PATCH 020/175] Update property name sharingLimit to shareLimit --- MIGRATIONS.md | 2 +- WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift | 2 +- .../WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 6d0be6093697..63e60f8a0f45 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -10,7 +10,7 @@ data model as well as any custom migrations. - Created a new entity `PublicizeInfo` with: - `sharedPostsCount` (required, default `0`, `Int 64`) - `sharesRemaining` (required, default `0`, `Int 64`) - - `sharingLimit` (required, default `0`, `Int 64`) + - `shareLimit` (required, default `0`, `Int 64`) - `toBePublicizedCount` (required, default `0`, `Int 64`) - Created one-to-many relationship between `PublicizeInfo` and `Blog` diff --git a/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift b/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift index 121070dd15eb..ac8f223f7b06 100644 --- a/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift +++ b/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift @@ -3,7 +3,7 @@ import CoreData extension PublicizeInfo { /// The maximum number of Social shares for the associated `blog`. - @NSManaged public var sharingLimit: Int64 + @NSManaged public var shareLimit: Int64 /// The number of Social sharing to be published in the future. @NSManaged public var toBePublicizedCount: Int64 diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents index b9b607573462..faa35233dac7 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents @@ -668,8 +668,8 @@ + - From ca5192d1e50ce0a1f97cd6fb27a67ceb813245fa Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Sun, 25 Jun 2023 22:34:25 +0700 Subject: [PATCH 021/175] Add publicizeInfo property to the Blog NSManagedObject subclass --- WordPress/Classes/Models/Blog.h | 6 ++++++ WordPress/Classes/Models/Blog.m | 2 +- WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/Models/Blog.h b/WordPress/Classes/Models/Blog.h index 5d59bd92d52f..f42e20f4f0d6 100644 --- a/WordPress/Classes/Models/Blog.h +++ b/WordPress/Classes/Models/Blog.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @class SiteSuggestion; @class PageTemplateCategory; @class JetpackFeaturesRemovalCoordinator; +@class PublicizeInfo; extern NSString * const BlogEntityName; extern NSString * const PostFormatStandard; @@ -181,6 +182,11 @@ typedef NS_ENUM(NSInteger, SiteVisibility) { */ @property (nonatomic, strong, readwrite, nullable) BlogSettings *settings; +/** + * @details Maps to a PublicizeInfo instance, which contains Jetpack Social auto-sharing information. + */ +@property (nonatomic, strong, readwrite, nullable) PublicizeInfo *publicizeInfo; + /** * @details Flags whether the current user is an admin on the blog. */ diff --git a/WordPress/Classes/Models/Blog.m b/WordPress/Classes/Models/Blog.m index 454c81022ebe..e53e978e72e4 100644 --- a/WordPress/Classes/Models/Blog.m +++ b/WordPress/Classes/Models/Blog.m @@ -4,7 +4,6 @@ #import "NSURL+IDN.h" #import "CoreDataStack.h" #import "Constants.h" -#import "WordPress-Swift.h" #import "WPUserAgent.h" #import "WordPress-Swift.h" @@ -91,6 +90,7 @@ @implementation Blog @dynamic quotaSpaceAllowed; @dynamic quotaSpaceUsed; @dynamic pageTemplateCategories; +@dynamic publicizeInfo; @synthesize isSyncingPosts; @synthesize isSyncingPages; diff --git a/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift b/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift index cf5f424ea1dc..9579ab583ed3 100644 --- a/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift +++ b/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift @@ -7,7 +7,7 @@ import CoreData /// Furthermore, sites eligible for unlimited sharing will still return a `PublicizeInfo` along with its sharing /// limitations, but the numbers should be ignored (at least for now). /// -public class PublicizeInfo: NSManagedObject { +@objc public class PublicizeInfo: NSManagedObject { @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "PublicizeInfo") From 43e280a1e59a00122e7d728bf9ac550c87691c0d Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Sun, 25 Jun 2023 21:57:40 +0700 Subject: [PATCH 022/175] Add service class for Jetpack Social (WIP) --- .../Services/JetpackSocialService.swift | 41 +++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 6 +++ 2 files changed, 47 insertions(+) create mode 100644 WordPress/Classes/Services/JetpackSocialService.swift diff --git a/WordPress/Classes/Services/JetpackSocialService.swift b/WordPress/Classes/Services/JetpackSocialService.swift new file mode 100644 index 000000000000..f7e60a1a11fe --- /dev/null +++ b/WordPress/Classes/Services/JetpackSocialService.swift @@ -0,0 +1,41 @@ +import WordPressKit +import CoreData + +class JetpackSocialService: CoreDataService { + + // TODO: (dvdchr) Is this testable? + private lazy var remote: JetpackSocialServiceRemote = { + let api = coreDataStack.performQuery { context in + return WordPressComRestApi.defaultV2Api(in: context) + } + return .init(wordPressComRestApi: api) + }() + + // TODO: (dvdchr) Docs + /// + /// - Parameter siteID: Int + /// - Returns: PublicizeInfo + func fetchPublicizeInfo(for siteID: Int) async -> Result { + await withCheckedContinuation { continuation in + remote.fetchPublicizeInfo(for: siteID) { result in + switch result { + case .success(let remotePublicizeInfo): + // TODO: Convert RemotePublicizeInfo to PublicizeInfo. + // TODO: If it's nil, delete the existing entry from Core Data. + break + + case .failure(let error): + continuation.resume(returning: .failure(error)) + } + } + } + } + +} + +// MARK: - Private Methods + +private extension JetpackSocialService { + + +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index ae07e2600175..efebfd9a71b3 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -5493,6 +5493,8 @@ FE1E20162A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */; }; FE1E20172A47042500CE7C90 /* PublicizeInfo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */; }; FE1E20182A47042500CE7C90 /* PublicizeInfo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */; }; + FE1E201A2A473E0800CE7C90 /* JetpackSocialService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20192A473E0800CE7C90 /* JetpackSocialService.swift */; }; + FE1E201B2A473E0800CE7C90 /* JetpackSocialService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20192A473E0800CE7C90 /* JetpackSocialService.swift */; }; FE23EB4926E7C91F005A1698 /* richCommentTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */; }; FE23EB4A26E7C91F005A1698 /* richCommentTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */; }; FE23EB4B26E7C91F005A1698 /* richCommentStyle.css in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4826E7C91F005A1698 /* richCommentStyle.css */; }; @@ -9296,6 +9298,7 @@ FE1E200F2A45ACE900CE7C90 /* WordPress 151.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 151.xcdatamodel"; sourceTree = ""; }; FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "PublicizeInfo+CoreDataClass.swift"; path = "/Users/dvdchr/code/github/wpios/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift"; sourceTree = ""; }; FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "PublicizeInfo+CoreDataProperties.swift"; path = "/Users/dvdchr/code/github/wpios/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift"; sourceTree = ""; }; + FE1E20192A473E0800CE7C90 /* JetpackSocialService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackSocialService.swift; sourceTree = ""; }; FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = richCommentTemplate.html; path = Resources/HTML/richCommentTemplate.html; sourceTree = ""; }; FE23EB4826E7C91F005A1698 /* richCommentStyle.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = richCommentStyle.css; path = Resources/HTML/richCommentStyle.css; sourceTree = ""; }; FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsNotificationSheetViewController.swift; sourceTree = ""; }; @@ -14016,6 +14019,7 @@ 8B749E7125AF522900023F03 /* JetpackCapabilitiesService.swift */, FAB8AB8A25AFFE7500F9F8A0 /* JetpackRestoreService.swift */, 8C6A22E325783D2000A79950 /* JetpackScanService.swift */, + FE1E20192A473E0800CE7C90 /* JetpackSocialService.swift */, C7A09A4928401B7B003096ED /* QRLoginService.swift */, 9A2D0B35225E2511009E585F /* JetpackService.swift */, 1751E5901CE0E552000CA08D /* KeyValueDatabase.swift */, @@ -22648,6 +22652,7 @@ 08D345501CD7F50900358E8C /* MenusSelectionDetailView.m in Sources */, 8B0732F0242BF7E800E7FBD3 /* Blog+Title.swift in Sources */, C94C0B1B25DCFA0100F2F69B /* FilterableCategoriesViewController.swift in Sources */, + FE1E201A2A473E0800CE7C90 /* JetpackSocialService.swift in Sources */, 591AA5021CEF9BF20074934F /* Post+CoreDataProperties.swift in Sources */, E19B17AE1E5C6944007517C6 /* BasePost.swift in Sources */, C71AF533281064DE00F9E99E /* OnboardingQuestionsCoordinator.swift in Sources */, @@ -24141,6 +24146,7 @@ FABB21F72602FC2C00C8785C /* GridCell.swift in Sources */, FA4F383727D766020068AAF5 /* MySiteSettings.swift in Sources */, C72A4F68264088E4009CA633 /* JetpackNotFoundErrorViewModel.swift in Sources */, + FE1E201B2A473E0800CE7C90 /* JetpackSocialService.swift in Sources */, FABB21F82602FC2C00C8785C /* AdaptiveNavigationController.swift in Sources */, FABB21F92602FC2C00C8785C /* RemotePostCategory+Extensions.swift in Sources */, FABB21FA2602FC2C00C8785C /* PostAutoUploadMessages.swift in Sources */, From 3ddec5d0ba7500c18edef9149e409928aaec8a3e Mon Sep 17 00:00:00 2001 From: Hassaan El-Garem Date: Mon, 26 Jun 2023 03:54:16 +0300 Subject: [PATCH 023/175] Refactor: turn `BlazeWebViewModel` into a protocol So we can reuse BlazeWebViewController for campaign details --- ... => BlazeCreateCampaignWebViewModel.swift} | 13 ++--- .../Webview/BlazeWebViewController.swift | 23 ++++++++- WordPress/WordPress.xcodeproj/project.pbxproj | 22 ++++---- ...lazeCreateCampaignWebViewModelTests.swift} | 50 +++++++++---------- 4 files changed, 59 insertions(+), 49 deletions(-) rename WordPress/Classes/ViewRelated/Blaze/Webview/{BlazeWebViewModel.swift => BlazeCreateCampaignWebViewModel.swift} (95%) rename WordPress/WordPressTest/Blaze/{BlazeWebViewModelTests.swift => BlazeCreateCampaignWebViewModelTests.swift} (76%) diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewModel.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift similarity index 95% rename from WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewModel.swift rename to WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift index 445643f26040..77995c7240d1 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift @@ -1,13 +1,6 @@ import Foundation -protocol BlazeWebView { - func load(request: URLRequest) - func reloadNavBar() - func dismissView() - var cookieJar: CookieJar { get } -} - -class BlazeWebViewModel { +class BlazeCreateCampaignWebViewModel: BlazeWebViewModel { // MARK: Public Variables @@ -148,13 +141,13 @@ class BlazeWebViewModel { } } -extension BlazeWebViewModel: WebKitAuthenticatable { +extension BlazeCreateCampaignWebViewModel: WebKitAuthenticatable { var authenticator: RequestAuthenticator? { RequestAuthenticator(blog: blog) } } -private extension BlazeWebViewModel { +private extension BlazeCreateCampaignWebViewModel { enum Constants { static let baseURLFormat = "https://wordpress.com/advertising/%@" static let blazeSiteURLFormat = "https://wordpress.com/advertising/%@?source=%@" diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift index e4352647d868..b94d473c6fa4 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift @@ -5,14 +5,33 @@ import WebKit func dismissBlazeWebViewController(_ controller: BlazeWebViewController) } +protocol BlazeWebViewModel { + func startBlazeFlow() + func dismissTapped() + func webViewDidFail(with error: Error) + func isCurrentStepDismissible() -> Bool + func shouldNavigate(to request: URLRequest, with type: WKNavigationType) -> WKNavigationActionPolicy + var isFlowCompleted: Bool { get } +} + +protocol BlazeWebView { + func load(request: URLRequest) + func reloadNavBar() + func dismissView() + var cookieJar: CookieJar { get } +} + class BlazeWebViewController: UIViewController, BlazeWebView { + // MARK: Public Variables + + var viewModel: BlazeWebViewModel? + // MARK: Private Variables private weak var delegate: BlazeWebViewControllerDelegate? private let webView: WKWebView - private var viewModel: BlazeWebViewModel? private let progressView = WebProgressView() private var reachabilityObserver: Any? private var currentRequestURL: URL? @@ -36,7 +55,7 @@ class BlazeWebViewController: UIViewController, BlazeWebView { self.delegate = delegate self.webView = WKWebView(frame: .zero) super.init(nibName: nil, bundle: nil) - viewModel = BlazeWebViewModel(source: source, blog: blog, postID: postID, view: self) + viewModel = BlazeCreateCampaignWebViewModel(source: source, blog: blog, postID: postID, view: self) } required init?(coder: NSCoder) { diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index cda319bef51e..d6ec891267bf 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1913,9 +1913,9 @@ 80B016D22803AB9F00D15566 /* DashboardPostsListCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B016D02803AB9F00D15566 /* DashboardPostsListCardCell.swift */; }; 80C523A429959DE000B1C14B /* BlazeWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C523A329959DE000B1C14B /* BlazeWebViewController.swift */; }; 80C523A529959DE000B1C14B /* BlazeWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C523A329959DE000B1C14B /* BlazeWebViewController.swift */; }; - 80C523A72995D73C00B1C14B /* BlazeWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C523A62995D73C00B1C14B /* BlazeWebViewModel.swift */; }; - 80C523A82995D73C00B1C14B /* BlazeWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C523A62995D73C00B1C14B /* BlazeWebViewModel.swift */; }; - 80C523AB29AE6C2200B1C14B /* BlazeWebViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C523AA29AE6C2200B1C14B /* BlazeWebViewModelTests.swift */; }; + 80C523A72995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C523A62995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift */; }; + 80C523A82995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C523A62995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift */; }; + 80C523AB29AE6C2200B1C14B /* BlazeCreateCampaignWebViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C523AA29AE6C2200B1C14B /* BlazeCreateCampaignWebViewModelTests.swift */; }; 80C740FB2989FC4600199027 /* PostStatsTableViewController+JetpackBannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C740FA2989FC4600199027 /* PostStatsTableViewController+JetpackBannerViewController.swift */; }; 80C740FC2989FC4600199027 /* PostStatsTableViewController+JetpackBannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C740FA2989FC4600199027 /* PostStatsTableViewController+JetpackBannerViewController.swift */; }; 80D9CFF429DCA53E00FE3400 /* DashboardPagesListCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D9CFF329DCA53E00FE3400 /* DashboardPagesListCardCell.swift */; }; @@ -7325,8 +7325,8 @@ 80B016CE27FEBDC900D15566 /* DashboardCardTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardCardTests.swift; sourceTree = ""; }; 80B016D02803AB9F00D15566 /* DashboardPostsListCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPostsListCardCell.swift; sourceTree = ""; }; 80C523A329959DE000B1C14B /* BlazeWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeWebViewController.swift; sourceTree = ""; }; - 80C523A62995D73C00B1C14B /* BlazeWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeWebViewModel.swift; sourceTree = ""; }; - 80C523AA29AE6C2200B1C14B /* BlazeWebViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeWebViewModelTests.swift; sourceTree = ""; }; + 80C523A62995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCreateCampaignWebViewModel.swift; sourceTree = ""; }; + 80C523AA29AE6C2200B1C14B /* BlazeCreateCampaignWebViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCreateCampaignWebViewModelTests.swift; sourceTree = ""; }; 80C740FA2989FC4600199027 /* PostStatsTableViewController+JetpackBannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostStatsTableViewController+JetpackBannerViewController.swift"; sourceTree = ""; }; 80D65C1129CC0813008E69D5 /* JetpackUITests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JetpackUITests-Info.plist"; sourceTree = ""; }; 80D9CFF329DCA53E00FE3400 /* DashboardPagesListCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPagesListCardCell.swift; sourceTree = ""; }; @@ -13197,7 +13197,7 @@ 80C523A929AE6BF300B1C14B /* Blaze */ = { isa = PBXGroup; children = ( - 80C523AA29AE6C2200B1C14B /* BlazeWebViewModelTests.swift */, + 80C523AA29AE6C2200B1C14B /* BlazeCreateCampaignWebViewModelTests.swift */, ); path = Blaze; sourceTree = ""; @@ -17908,7 +17908,7 @@ children = ( FA4B202E29A619130089FE68 /* BlazeFlowCoordinator.swift */, 80C523A329959DE000B1C14B /* BlazeWebViewController.swift */, - 80C523A62995D73C00B1C14B /* BlazeWebViewModel.swift */, + 80C523A62995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift */, ); path = Webview; sourceTree = ""; @@ -18094,8 +18094,6 @@ FE43DAAD26DFAD1C00CFF595 /* CommentContentTableViewCell.swift */, FE43DAAE26DFAD1C00CFF595 /* CommentContentTableViewCell.xib */, FEA7948C26DD136700CEC520 /* CommentHeaderTableViewCell.swift */, - 9839CEB926FAA0510097406E /* CommentModerationBar.swift */, - 98F9FB2D270282C100ADF552 /* CommentModerationBar.xib */, FE50965B2A20D0F300DDD071 /* CommentTableHeaderView.swift */, ); name = Detail; @@ -20910,7 +20908,7 @@ F1C197A62670DDB100DE1FF7 /* BloggingRemindersTracker.swift in Sources */, 17E24F5420FCF1D900BD70A3 /* Routes+MySites.swift in Sources */, 0828D7FA1E6E09AE00C7C7D4 /* WPAppAnalytics+Media.swift in Sources */, - 80C523A72995D73C00B1C14B /* BlazeWebViewModel.swift in Sources */, + 80C523A72995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift in Sources */, C7A09A4D28403A34003096ED /* QRLoginURLParser.swift in Sources */, 591AA5011CEF9BF20074934F /* Post.swift in Sources */, 1E0FF01E242BC572008DA898 /* GutenbergWebViewController.swift in Sources */, @@ -23368,7 +23366,7 @@ 3FDDFE9627C8178C00606933 /* SiteStatsInformationTests.swift in Sources */, 74B335EC1F06F9520053A184 /* MockWordPressComRestApi.swift in Sources */, 80EF92932810FA5A0064A971 /* QuickStartFactoryTests.swift in Sources */, - 80C523AB29AE6C2200B1C14B /* BlazeWebViewModelTests.swift in Sources */, + 80C523AB29AE6C2200B1C14B /* BlazeCreateCampaignWebViewModelTests.swift in Sources */, D848CBFF20FF010F00A9038F /* FormattableCommentContentTests.swift in Sources */, 9123471B221449E200BD9F97 /* GutenbergInformativeDialogTests.swift in Sources */, 8332DD2829259BEB00802F7D /* DataMigratorTests.swift in Sources */, @@ -24401,7 +24399,7 @@ FABB22B62602FC2C00C8785C /* WPAndDeviceMediaLibraryDataSource.m in Sources */, 011896A329D5AF0700D34BA9 /* BlogDashboardCardConfigurable.swift in Sources */, FABB22B72602FC2C00C8785C /* SettingsListEditorViewController.swift in Sources */, - 80C523A82995D73C00B1C14B /* BlazeWebViewModel.swift in Sources */, + 80C523A82995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift in Sources */, 982DDF97263238A6002B3904 /* LikeUserPreferredBlog+CoreDataProperties.swift in Sources */, FABB22B82602FC2C00C8785C /* QuickStartChecklistManager.swift in Sources */, FABB22B92602FC2C00C8785C /* NoResultsTenorConfiguration.swift in Sources */, diff --git a/WordPress/WordPressTest/Blaze/BlazeWebViewModelTests.swift b/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift similarity index 76% rename from WordPress/WordPressTest/Blaze/BlazeWebViewModelTests.swift rename to WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift index b5604a134771..8d6a8f67e6cb 100644 --- a/WordPress/WordPressTest/Blaze/BlazeWebViewModelTests.swift +++ b/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift @@ -1,7 +1,7 @@ import XCTest @testable import WordPress -final class BlazeWebViewModelTests: CoreDataTestCase { +final class BlazeCreateCampaignWebViewModelTests: CoreDataTestCase { // MARK: Private Variables @@ -27,7 +27,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testPostsListStep() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?source=menu_item")) let request = URLRequest(url: url) @@ -40,7 +40,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testPostsListStepWithPostsPath() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/posts?source=menu_item")) let request = URLRequest(url: url) @@ -53,7 +53,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testCampaignsStep() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/campaigns?source=menu_item")) let request = URLRequest(url: url) @@ -66,7 +66,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testDefaultWidgetStep() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?blazepress-widget=post-2&source=menu_item")) let request = URLRequest(url: url) @@ -79,7 +79,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testDefaultWidgetStepWithPostsPath() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/posts?blazepress-widget=post-2&source=menu_item")) let request = URLRequest(url: url) @@ -92,7 +92,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testExtractStepFromFragment() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?blazepress-widget=post-2&source=menu_item#step-2")) let request = URLRequest(url: url) @@ -105,7 +105,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testExtractStepFromFragmentPostsPath() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/posts?blazepress-widget=post-2&source=menu_item#step-3")) let request = URLRequest(url: url) @@ -118,7 +118,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testPostsListStepWithoutQuery() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com")) let request = URLRequest(url: url) @@ -131,7 +131,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testPostsListStepWithPostsPathWithoutQuery() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/posts")) let request = URLRequest(url: url) @@ -144,7 +144,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testCampaignsStepWithoutQuery() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/campaigns")) let request = URLRequest(url: url) @@ -157,7 +157,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testDefaultWidgetStepWithoutQuery() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?blazepress-widget=post-2")) let request = URLRequest(url: url) @@ -170,7 +170,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testDefaultWidgetStepWithPostsPathWithoutQuery() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/posts?blazepress-widget=post-2")) let request = URLRequest(url: url) @@ -183,7 +183,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testExtractStepFromFragmentWithoutQuery() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?blazepress-widget=post-2#step-2")) let request = URLRequest(url: url) @@ -196,7 +196,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testExtractStepFromFragmentPostsPathWithoutQuery() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/posts?blazepress-widget=post-2#step-3")) let request = URLRequest(url: url) @@ -209,7 +209,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testInitialStep() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) // Then XCTAssertEqual(viewModel.currentStep, "unspecified") @@ -217,7 +217,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testCurrentStepMaintainedIfExtractionFails() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let postsListURL = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?source=menu_item")) let postsListRequest = URLRequest(url: postsListURL) let invalidURL = try XCTUnwrap(URL(string: "https://test.com/test?example=test")) @@ -238,7 +238,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testInternalURLsAllowed() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let validURL = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?source=menu_item")) var validRequest = URLRequest(url: validURL) validRequest.mainDocumentURL = validURL @@ -253,7 +253,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testExternalURLsBlocked() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let invalidURL = try XCTUnwrap(URL(string: "https://test.com/test?example=test")) var invalidRequest = URLRequest(url: invalidURL) invalidRequest.mainDocumentURL = invalidURL @@ -269,7 +269,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testCallingShouldNavigateReloadsTheNavBar() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) let url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?source=menu_item")) let request = URLRequest(url: url) @@ -282,7 +282,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testCallingDismissTappedDismissesTheView() { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) // When viewModel.dismissTapped() @@ -293,7 +293,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testCallingStartBlazeSiteFlowLoadsTheView() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, externalURLHandler: externalURLHandler) // When viewModel.startBlazeFlow() @@ -305,7 +305,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testCallingStartBlazePostFlowLoadsTheView() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: 1, view: view, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: 1, view: view, externalURLHandler: externalURLHandler) // When viewModel.startBlazeFlow() @@ -317,7 +317,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testIsCurrentStepDismissible() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, remoteConfigStore: remoteConfigStore, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, remoteConfigStore: remoteConfigStore, externalURLHandler: externalURLHandler) // When var url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/posts?blazepress-widget=post-2#step-1")) @@ -346,7 +346,7 @@ final class BlazeWebViewModelTests: CoreDataTestCase { func testIsFlowCompleted() throws { // Given - let viewModel = BlazeWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, remoteConfigStore: remoteConfigStore, externalURLHandler: externalURLHandler) + let viewModel = BlazeCreateCampaignWebViewModel(source: .menuItem, blog: blog, postID: nil, view: view, remoteConfigStore: remoteConfigStore, externalURLHandler: externalURLHandler) // When var url = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com/posts?blazepress-widget=post-2#step-1")) From 31e5f8fe4c1c706627fc034725dec430e597806a Mon Sep 17 00:00:00 2001 From: Hassaan El-Garem Date: Mon, 26 Jun 2023 05:08:46 +0300 Subject: [PATCH 024/175] Add: new `BlazeCampaignDetailsWebViewModel` class --- .../BlazeCampaignDetailsWebViewModel.swift | 106 ++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 6 + 2 files changed, 112 insertions(+) create mode 100644 WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift new file mode 100644 index 000000000000..347b81d10716 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift @@ -0,0 +1,106 @@ +import Foundation + +class BlazeCampaignDetailsWebViewModel: BlazeWebViewModel { + + // MARK: Private Variables + + private let source: BlazeSource + private let blog: Blog + private let campaignID: Int + private let view: BlazeWebView + private let externalURLHandler: ExternalURLHandler + private var linkBehavior: LinkBehavior = .all + + // MARK: Initializer + + init(source: BlazeSource, + blog: Blog, + campaignID: Int, + view: BlazeWebView, + externalURLHandler: ExternalURLHandler = UIApplication.shared) { + self.source = source + self.blog = blog + self.campaignID = campaignID + self.view = view + self.externalURLHandler = externalURLHandler + setLinkBehavior() + } + + // MARK: Computed Variables + + private var initialURL: URL? { + guard let siteURL = blog.displayURL else { + return nil + } + let urlString = String(format: Constants.campaignDetailsURLFormat, siteURL, campaignID, source.description) + return URL(string: urlString) + } + + private var baseURLString: String? { + guard let siteURL = blog.displayURL else { + return nil + } + return String(format: Constants.baseURLFormat, siteURL) + } + + // MARK: Public Functions + + var isFlowCompleted: Bool { + return true + } + + func startBlazeFlow() { + guard let initialURL else { + // TODO: Track Analytics Error Event + view.dismissView() + return + } + authenticatedRequest(for: initialURL, with: view.cookieJar) { [weak self] (request) in + guard let weakSelf = self else { + return + } + weakSelf.view.load(request: request) + // TODO: Track Analytics Event + } + } + + func dismissTapped() { + view.dismissView() + // TODO: Track Analytics Event + } + + func shouldNavigate(to request: URLRequest, with type: WKNavigationType) -> WKNavigationActionPolicy { + return linkBehavior.handle(request: request, with: type, externalURLHandler: externalURLHandler) + } + + func isCurrentStepDismissible() -> Bool { + return true + } + + func webViewDidFail(with error: Error) { + // TODO: Track Analytics Error Event + } + + // MARK: Helpers + + private func setLinkBehavior() { + guard let baseURLString else { + return + } + self.linkBehavior = .withBaseURLOnly(baseURLString) + } +} + +extension BlazeCampaignDetailsWebViewModel: WebKitAuthenticatable { + var authenticator: RequestAuthenticator? { + RequestAuthenticator(blog: blog) + } +} + +private extension BlazeCampaignDetailsWebViewModel { + enum Constants { + // TODO: Replace these constants with remote config params + static let baseURLFormat = "https://wordpress.com/advertising/%@" + static let campaignDetailsURLFormat = "https://wordpress.com/advertising/%@/campaigns/%d?source=%@" + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index d6ec891267bf..088fd8aa67b5 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1741,6 +1741,8 @@ 805CC0C2296CF3B3002941DC /* JetpackNewUsersOverlaySecondaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 805CC0C0296CF3B3002941DC /* JetpackNewUsersOverlaySecondaryView.swift */; }; 8067340A27E3A50900ABC95E /* UIViewController+RemoveQuickStart.m in Sources */ = {isa = PBXBuildFile; fileRef = 8067340927E3A50900ABC95E /* UIViewController+RemoveQuickStart.m */; }; 8067340B27E3A50900ABC95E /* UIViewController+RemoveQuickStart.m in Sources */ = {isa = PBXBuildFile; fileRef = 8067340927E3A50900ABC95E /* UIViewController+RemoveQuickStart.m */; }; + 806BA1192A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806BA1182A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift */; }; + 806BA11A2A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806BA1182A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift */; }; 806E53E127E01C7F0064315E /* DashboardStatsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806E53E027E01C7F0064315E /* DashboardStatsViewModel.swift */; }; 806E53E227E01C7F0064315E /* DashboardStatsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806E53E027E01C7F0064315E /* DashboardStatsViewModel.swift */; }; 806E53E427E01CFE0064315E /* DashboardStatsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806E53E327E01CFE0064315E /* DashboardStatsViewModelTests.swift */; }; @@ -7302,6 +7304,7 @@ 805CC0C0296CF3B3002941DC /* JetpackNewUsersOverlaySecondaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackNewUsersOverlaySecondaryView.swift; sourceTree = ""; }; 8067340827E3A50900ABC95E /* UIViewController+RemoveQuickStart.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+RemoveQuickStart.h"; sourceTree = ""; }; 8067340927E3A50900ABC95E /* UIViewController+RemoveQuickStart.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+RemoveQuickStart.m"; sourceTree = ""; }; + 806BA1182A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignDetailsWebViewModel.swift; sourceTree = ""; }; 806E53E027E01C7F0064315E /* DashboardStatsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardStatsViewModel.swift; sourceTree = ""; }; 806E53E327E01CFE0064315E /* DashboardStatsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardStatsViewModelTests.swift; sourceTree = ""; }; 8070EB3D28D807CB005C6513 /* InMemoryUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemoryUserDefaults.swift; sourceTree = ""; }; @@ -17909,6 +17912,7 @@ FA4B202E29A619130089FE68 /* BlazeFlowCoordinator.swift */, 80C523A329959DE000B1C14B /* BlazeWebViewController.swift */, 80C523A62995D73C00B1C14B /* BlazeCreateCampaignWebViewModel.swift */, + 806BA1182A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift */, ); path = Webview; sourceTree = ""; @@ -22038,6 +22042,7 @@ 8B74A9A8268E3C68003511CE /* RewindStatus+multiSite.swift in Sources */, 3F5B3EAF23A851330060FF1F /* ReaderReblogPresenter.swift in Sources */, 7E3E7A6220E44E6A0075D159 /* BodyContentGroup.swift in Sources */, + 806BA1192A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift in Sources */, FEA088012696E7F600193358 /* ListTableHeaderView.swift in Sources */, 17AD36D51D36C1A60044B10D /* WPStyleGuide+Search.swift in Sources */, F1E3536B25B9F74C00992E3A /* WindowManager.swift in Sources */, @@ -24713,6 +24718,7 @@ 8313B9FB2995A03C000AF26E /* JetpackRemoteInstallCardView.swift in Sources */, FABB23962602FC2C00C8785C /* StatsTableFooter.swift in Sources */, FABB23972602FC2C00C8785C /* BlogSyncFacade.m in Sources */, + 806BA11A2A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift in Sources */, FABB23982602FC2C00C8785C /* PrepublishingNavigationController.swift in Sources */, 8384C64228AAC82600EABE26 /* KeychainUtils.swift in Sources */, FABB23992602FC2C00C8785C /* UINavigationController+KeyboardFix.m in Sources */, From 48d9545957ad7a206013e1263875ae35b1a0b4e9 Mon Sep 17 00:00:00 2001 From: Hassaan El-Garem Date: Mon, 26 Jun 2023 05:24:19 +0300 Subject: [PATCH 025/175] Tests: add tests for `BlazeCampaignDetailsWebViewModel` --- WordPress/WordPress.xcodeproj/project.pbxproj | 4 + ...lazeCampaignDetailsWebViewModelTests.swift | 109 ++++++++++++++++++ ...BlazeCreateCampaignWebViewModelTests.swift | 2 +- 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 WordPress/WordPressTest/Blaze/BlazeCampaignDetailsWebViewModelTests.swift diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 088fd8aa67b5..57a0e7bfaf05 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1743,6 +1743,7 @@ 8067340B27E3A50900ABC95E /* UIViewController+RemoveQuickStart.m in Sources */ = {isa = PBXBuildFile; fileRef = 8067340927E3A50900ABC95E /* UIViewController+RemoveQuickStart.m */; }; 806BA1192A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806BA1182A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift */; }; 806BA11A2A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806BA1182A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift */; }; + 806BA11C2A492B0F00052422 /* BlazeCampaignDetailsWebViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806BA11B2A492B0F00052422 /* BlazeCampaignDetailsWebViewModelTests.swift */; }; 806E53E127E01C7F0064315E /* DashboardStatsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806E53E027E01C7F0064315E /* DashboardStatsViewModel.swift */; }; 806E53E227E01C7F0064315E /* DashboardStatsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806E53E027E01C7F0064315E /* DashboardStatsViewModel.swift */; }; 806E53E427E01CFE0064315E /* DashboardStatsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806E53E327E01CFE0064315E /* DashboardStatsViewModelTests.swift */; }; @@ -7305,6 +7306,7 @@ 8067340827E3A50900ABC95E /* UIViewController+RemoveQuickStart.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+RemoveQuickStart.h"; sourceTree = ""; }; 8067340927E3A50900ABC95E /* UIViewController+RemoveQuickStart.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+RemoveQuickStart.m"; sourceTree = ""; }; 806BA1182A492A2700052422 /* BlazeCampaignDetailsWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignDetailsWebViewModel.swift; sourceTree = ""; }; + 806BA11B2A492B0F00052422 /* BlazeCampaignDetailsWebViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignDetailsWebViewModelTests.swift; sourceTree = ""; }; 806E53E027E01C7F0064315E /* DashboardStatsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardStatsViewModel.swift; sourceTree = ""; }; 806E53E327E01CFE0064315E /* DashboardStatsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardStatsViewModelTests.swift; sourceTree = ""; }; 8070EB3D28D807CB005C6513 /* InMemoryUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemoryUserDefaults.swift; sourceTree = ""; }; @@ -13201,6 +13203,7 @@ isa = PBXGroup; children = ( 80C523AA29AE6C2200B1C14B /* BlazeCreateCampaignWebViewModelTests.swift */, + 806BA11B2A492B0F00052422 /* BlazeCampaignDetailsWebViewModelTests.swift */, ); path = Blaze; sourceTree = ""; @@ -23554,6 +23557,7 @@ D81C2F5820F86CEA002AE1F1 /* NetworkStatus.swift in Sources */, E1C545801C6C79BB001CEB0E /* MediaSettingsTests.swift in Sources */, C3439B5F27FE3A3C0058DA55 /* SiteCreationWizardLauncherTests.swift in Sources */, + 806BA11C2A492B0F00052422 /* BlazeCampaignDetailsWebViewModelTests.swift in Sources */, 7E987F5A2108122A00CAFB88 /* NotificationUtility.swift in Sources */, FE32E7F12844971000744D80 /* ReminderScheduleCoordinatorTests.swift in Sources */, 4688E6CC26AB571D00A5D894 /* RequestAuthenticatorTests.swift in Sources */, diff --git a/WordPress/WordPressTest/Blaze/BlazeCampaignDetailsWebViewModelTests.swift b/WordPress/WordPressTest/Blaze/BlazeCampaignDetailsWebViewModelTests.swift new file mode 100644 index 000000000000..7df37b968ded --- /dev/null +++ b/WordPress/WordPressTest/Blaze/BlazeCampaignDetailsWebViewModelTests.swift @@ -0,0 +1,109 @@ +import XCTest +@testable import WordPress + +final class BlazeCampaignDetailsWebViewModelTests: CoreDataTestCase { + + // MARK: Private Variables + + private var view: BlazeWebViewMock! + private var externalURLHandler: ExternalURLHandlerMock! + private var blog: Blog! + private static let blogURL = "test.blog.com" + + // MARK: Setup + + override func setUp() { + super.setUp() + view = BlazeWebViewMock() + externalURLHandler = ExternalURLHandlerMock() + contextManager.useAsSharedInstance(untilTestFinished: self) + blog = BlogBuilder(mainContext).with(url: Self.blogURL).build() + } + + // MARK: Tests + + func testInternalURLsAllowed() throws { + // Given + let viewModel = BlazeCampaignDetailsWebViewModel(source: .campaignsList, blog: blog, campaignID: 0, view: view, externalURLHandler: externalURLHandler) + let validURL = try XCTUnwrap(URL(string: "https://wordpress.com/advertising/test.blog.com?source=menu_item")) + var validRequest = URLRequest(url: validURL) + validRequest.mainDocumentURL = validURL + + // When + let policy = viewModel.shouldNavigate(to: validRequest, with: .linkActivated) + + // Then + XCTAssertEqual(policy, .allow) + XCTAssertFalse(externalURLHandler.openURLCalled) + } + + func testExternalURLsBlocked() throws { + // Given + let viewModel = BlazeCampaignDetailsWebViewModel(source: .campaignsList, blog: blog, campaignID: 0, view: view, externalURLHandler: externalURLHandler) + let invalidURL = try XCTUnwrap(URL(string: "https://test.com/test?example=test")) + var invalidRequest = URLRequest(url: invalidURL) + invalidRequest.mainDocumentURL = invalidURL + + // When + let policy = viewModel.shouldNavigate(to: invalidRequest, with: .linkActivated) + + // Then + XCTAssertEqual(policy, .cancel) + XCTAssertTrue(externalURLHandler.openURLCalled) + XCTAssertEqual(externalURLHandler.urlOpened?.absoluteString, invalidURL.absoluteString) + } + + func testCallingDismissTappedDismissesTheView() { + // Given + let viewModel = BlazeCampaignDetailsWebViewModel(source: .campaignsList, blog: blog, campaignID: 0, view: view, externalURLHandler: externalURLHandler) + + // When + viewModel.dismissTapped() + + // Then + XCTAssertTrue(view.dismissViewCalled) + } + + func testCallingStartBlazeSiteFlowLoadsTheView() throws { + // Given + let viewModel = BlazeCampaignDetailsWebViewModel(source: .campaignsList, blog: blog, campaignID: 0, view: view, externalURLHandler: externalURLHandler) + + // When + viewModel.startBlazeFlow() + + // Then + XCTAssertTrue(view.loadCalled) + XCTAssertEqual(view.requestLoaded?.url?.absoluteString, "https://wordpress.com/advertising/test.blog.com/campaigns/0?source=campaigns_list") + } +} + +private class BlazeWebViewMock: BlazeWebView { + + var loadCalled = false + var requestLoaded: URLRequest? + var dismissViewCalled = false + + func load(request: URLRequest) { + loadCalled = true + requestLoaded = request + } + + func reloadNavBar() {} + + func dismissView() { + dismissViewCalled = true + } + + var cookieJar: WordPress.CookieJar = MockCookieJar() +} + +private class ExternalURLHandlerMock: ExternalURLHandler { + + var openURLCalled = false + var urlOpened: URL? + + func open(_ url: URL) { + self.openURLCalled = true + self.urlOpened = url + } +} diff --git a/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift b/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift index 8d6a8f67e6cb..780deda13ecc 100644 --- a/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift +++ b/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift @@ -9,7 +9,7 @@ final class BlazeCreateCampaignWebViewModelTests: CoreDataTestCase { private var externalURLHandler: ExternalURLHandlerMock! private var remoteConfigStore = RemoteConfigStoreMock() private var blog: Blog! - private static let blogURL = "test.blog.com" + private static let blogURL = "test.blog.com" // MARK: Setup From 6891d87bf10d0c894c7e2504acc959009c1c99c8 Mon Sep 17 00:00:00 2001 From: Hassaan El-Garem Date: Mon, 26 Jun 2023 05:27:31 +0300 Subject: [PATCH 026/175] Add: new API to `BlazeFlowCoordinator` for displaying campaign details --- .../Blaze/Webview/BlazeFlowCoordinator.swift | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeFlowCoordinator.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeFlowCoordinator.swift index 8f6e0619f43a..6fdcfa1a71bb 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeFlowCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeFlowCoordinator.swift @@ -73,7 +73,12 @@ import UIKit blog: Blog, postID: NSNumber? = nil, delegate: BlazeWebViewControllerDelegate? = nil) { - let blazeViewController = BlazeWebViewController(source: source, blog: blog, postID: postID, delegate: delegate) + let blazeViewController = BlazeWebViewController(delegate: delegate) + let viewModel = BlazeCreateCampaignWebViewModel(source: source, + blog: blog, + postID: postID, + view: blazeViewController) + blazeViewController.viewModel = viewModel let navigationViewController = UINavigationController(rootViewController: blazeViewController) navigationViewController.overrideUserInterfaceStyle = .light navigationViewController.modalPresentationStyle = .formSheet @@ -108,4 +113,27 @@ import UIKit let campaignsViewController = BlazeCampaignsViewController(blog: blog) viewController.navigationController?.pushViewController(campaignsViewController, animated: true) } + + /// Used to display the blaze campaign details web view. + /// - Parameters: + /// - viewController: The view controller where the web view should be presented in. + /// - source: The source that triggers the display of the blaze web view. + /// - blog: `Blog` object representing the site that is being blazed + /// - campaignID: `Int` representing the ID of the campaign whose details is being accessed. + @objc(presentBlazeCampaignDetailsInViewController:source:blog:campaignID:) + static func presentBlazeCampaignDetails(in viewController: UIViewController, + source: BlazeSource, + blog: Blog, + campaignID: Int) { + let blazeViewController = BlazeWebViewController(delegate: nil) + let viewModel = BlazeCampaignDetailsWebViewModel(source: source, + blog: blog, + campaignID: campaignID, + view: blazeViewController) + blazeViewController.viewModel = viewModel + let navigationViewController = UINavigationController(rootViewController: blazeViewController) + navigationViewController.overrideUserInterfaceStyle = .light + navigationViewController.modalPresentationStyle = .formSheet + viewController.present(navigationViewController, animated: true) + } } From c254b5a8e1cf4f03453fa00cb29b8b15771e2cab Mon Sep 17 00:00:00 2001 From: Hassaan El-Garem Date: Mon, 26 Jun 2023 05:49:43 +0300 Subject: [PATCH 027/175] Update: implement tap action in the campaigns list --- .../Blaze Campaigns/BlazeCampaignsViewController.swift | 9 +++++++++ .../Blaze/Webview/BlazeWebViewController.swift | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index ccb5b44f4d8e..867074ee5ca8 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -120,6 +120,15 @@ extension BlazeCampaignsViewController: UITableViewDataSource, UITableViewDelega cell.configure(with: viewModel, blog: blog) return cell } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + guard let campaign = campaigns[safe: indexPath.row] else { + return + } + + BlazeFlowCoordinator.presentBlazeCampaignDetails(in: self, source: .campaignsList, blog: blog, campaignID: campaign.campaignID) + } } // MARK: - No results diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift index b94d473c6fa4..b1c25fb6490e 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift @@ -51,11 +51,10 @@ class BlazeWebViewController: UIViewController, BlazeWebView { // MARK: Initializers - init(source: BlazeSource, blog: Blog, postID: NSNumber?, delegate: BlazeWebViewControllerDelegate?) { + init(delegate: BlazeWebViewControllerDelegate?) { self.delegate = delegate self.webView = WKWebView(frame: .zero) super.init(nibName: nil, bundle: nil) - viewModel = BlazeCreateCampaignWebViewModel(source: source, blog: blog, postID: postID, view: self) } required init?(coder: NSCoder) { From 2a3f2c7c929b8208baf2a6dc2efc1ea4f740d75e Mon Sep 17 00:00:00 2001 From: Hassaan El-Garem Date: Mon, 26 Jun 2023 06:17:08 +0300 Subject: [PATCH 028/175] Update: change campaign details web view nav bar title --- .../Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift | 9 +++++++++ .../Blaze/Webview/BlazeCreateCampaignWebViewModel.swift | 9 +++++++++ .../Blaze/Webview/BlazeWebViewController.swift | 6 ++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift index 347b81d10716..a90efa1fb5de 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift @@ -49,6 +49,10 @@ class BlazeCampaignDetailsWebViewModel: BlazeWebViewModel { return true } + var navigationTitle: String { + return Strings.navigationTitle + } + func startBlazeFlow() { guard let initialURL else { // TODO: Track Analytics Error Event @@ -98,6 +102,11 @@ extension BlazeCampaignDetailsWebViewModel: WebKitAuthenticatable { } private extension BlazeCampaignDetailsWebViewModel { + enum Strings { + static let navigationTitle = NSLocalizedString("feature.blaze.campaignDetails.title", + value: "Campaign Details", + comment: "Title of screen the displays the details of an advertisement campaign.") + } enum Constants { // TODO: Replace these constants with remote config params static let baseURLFormat = "https://wordpress.com/advertising/%@" diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift index 77995c7240d1..a7d1a1d8067b 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift @@ -59,6 +59,10 @@ class BlazeCreateCampaignWebViewModel: BlazeWebViewModel { // MARK: Public Functions + var navigationTitle: String { + return Strings.navigationTitle + } + func startBlazeFlow() { guard let initialURL else { BlazeEventsTracker.trackBlazeFlowError(for: source, currentStep: currentStep) @@ -148,6 +152,11 @@ extension BlazeCreateCampaignWebViewModel: WebKitAuthenticatable { } private extension BlazeCreateCampaignWebViewModel { + enum Strings { + static let navigationTitle = NSLocalizedString("feature.blaze.title", + value: "Blaze", + comment: "Name of a feature that allows the user to promote their posts.") + } enum Constants { static let baseURLFormat = "https://wordpress.com/advertising/%@" static let blazeSiteURLFormat = "https://wordpress.com/advertising/%@?source=%@" diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift index b1c25fb6490e..4b2baf8963a8 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift @@ -12,6 +12,7 @@ protocol BlazeWebViewModel { func isCurrentStepDismissible() -> Bool func shouldNavigate(to request: URLRequest, with type: WKNavigationType) -> WKNavigationActionPolicy var isFlowCompleted: Bool { get } + var navigationTitle: String { get } } protocol BlazeWebView { @@ -95,7 +96,7 @@ class BlazeWebViewController: UIViewController, BlazeWebView { } private func configureNavBar() { - title = Strings.navigationTitle + title = viewModel?.navigationTitle navigationItem.rightBarButtonItem = dismissButton configureNavBarAppearance() reloadNavBar() @@ -219,9 +220,6 @@ extension BlazeWebViewController: WKNavigationDelegate { private extension BlazeWebViewController { enum Strings { - static let navigationTitle = NSLocalizedString("feature.blaze.title", - value: "Blaze", - comment: "Name of a feature that allows the user to promote their posts.") static let cancelButtonTitle = NSLocalizedString("Cancel", comment: "Cancel. Action.") static let doneButtonTitle = NSLocalizedString("Done", comment: "Done. Action.") } From 0b6083f97d29501258be0c92a257250288317877 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Mon, 26 Jun 2023 21:14:07 +0700 Subject: [PATCH 029/175] Add syncSharingLimit for JetpackSocialService --- .../Models/PublicizeInfo+CoreDataClass.swift | 24 ++++++ .../Services/JetpackSocialService.swift | 79 +++++++++++++------ 2 files changed, 81 insertions(+), 22 deletions(-) diff --git a/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift b/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift index 9579ab583ed3..2cb7b29f3a48 100644 --- a/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift +++ b/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift @@ -1,5 +1,6 @@ import Foundation import CoreData +import WordPressKit /// `PublicizeInfo` encapsulates the information related to Jetpack Social auto-sharing. /// @@ -9,8 +10,31 @@ import CoreData /// @objc public class PublicizeInfo: NSManagedObject { + var sharingLimit: SharingLimit { + SharingLimit(remaining: Int(sharesRemaining), limit: Int(shareLimit)) + } + @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "PublicizeInfo") } + @nonobjc public class func newObject(in context: NSManagedObjectContext) -> PublicizeInfo? { + return NSEntityDescription.insertNewObject(forEntityName: Self.classNameWithoutNamespaces(), into: context) as? PublicizeInfo + } + + func configure(with remote: RemotePublicizeInfo) { + self.shareLimit = Int64(remote.shareLimit) + self.toBePublicizedCount = Int64(remote.toBePublicizedCount) + self.sharedPostsCount = Int64(remote.sharedPostsCount) + self.sharesRemaining = Int64(remote.sharesRemaining) + } + + /// A value-type representation for Publicize auto-sharing usage. + struct SharingLimit { + /// The remaining shares available for use. + let remaining: Int + + /// Maximum number of shares allowed for the site. + let limit: Int + } } diff --git a/WordPress/Classes/Services/JetpackSocialService.swift b/WordPress/Classes/Services/JetpackSocialService.swift index f7e60a1a11fe..7a226a56084b 100644 --- a/WordPress/Classes/Services/JetpackSocialService.swift +++ b/WordPress/Classes/Services/JetpackSocialService.swift @@ -1,9 +1,12 @@ import WordPressKit import CoreData -class JetpackSocialService: CoreDataService { +class JetpackSocialService { + + // MARK: Properties + + private let coreDataStack: CoreDataStackSwift - // TODO: (dvdchr) Is this testable? private lazy var remote: JetpackSocialServiceRemote = { let api = coreDataStack.performQuery { context in return WordPressComRestApi.defaultV2Api(in: context) @@ -11,31 +14,63 @@ class JetpackSocialService: CoreDataService { return .init(wordPressComRestApi: api) }() - // TODO: (dvdchr) Docs + // MARK: Methods + + init(coreDataStack: CoreDataStackSwift = ContextManager.shared) { + self.coreDataStack = coreDataStack + } + + /// Fetches and updates the Publicize information for the site associated with the `blogID`. + /// The method returns a value type that contains the remaining usage of Social auto-sharing and the maximum limit for the associated site. /// - /// - Parameter siteID: Int - /// - Returns: PublicizeInfo - func fetchPublicizeInfo(for siteID: Int) async -> Result { - await withCheckedContinuation { continuation in - remote.fetchPublicizeInfo(for: siteID) { result in - switch result { - case .success(let remotePublicizeInfo): - // TODO: Convert RemotePublicizeInfo to PublicizeInfo. - // TODO: If it's nil, delete the existing entry from Core Data. - break - - case .failure(let error): - continuation.resume(returning: .failure(error)) - } + /// - Note: If the returned result is a success with nil sharing limit, it's likely that the blog is hosted on WP.com, and has no Social sharing limitations. + /// + /// Furthermore, even if the sharing limit exists, it may not be applicable for the blog since the user might have purchased a product that ignores this limitation. + /// + /// - Parameters: + /// - blogID: The ID of the blog. + /// - completion: Closure that's called after the sync process completes. + func syncSharingLimit(for blogID: Int, completion: @escaping (Result) -> Void) { + remote.fetchPublicizeInfo(for: blogID) { [weak self] result in + switch result { + case .success(let remotePublicizeInfo): + self?.coreDataStack.performAndSave({ context -> PublicizeInfo.SharingLimit? in + guard let blog = try Blog.lookup(withID: blogID, in: context) else { + throw ServiceError.blogNotFound(id: blogID) + } + + if let remotePublicizeInfo, + let newOrExistingInfo = blog.publicizeInfo ?? PublicizeInfo.newObject(in: context) { + // add or update the publicizeInfo for the blog. + newOrExistingInfo.configure(with: remotePublicizeInfo) + blog.publicizeInfo = newOrExistingInfo + + } else if let existingPublicizeInfo = blog.publicizeInfo { + // if the remote object is nil, delete the blog's publicizeInfo if it exists. + context.delete(existingPublicizeInfo) + blog.publicizeInfo = nil + } + + return blog.publicizeInfo?.sharingLimit + + }, completion: { completion($0) }, on: .main) + + case .failure(let error): + completion(.failure(error)) } } } -} - -// MARK: - Private Methods - -private extension JetpackSocialService { + // MARK: Errors + enum ServiceError: LocalizedError { + case blogNotFound(id: Int) + var errorDescription: String? { + switch self { + case .blogNotFound(let id): + return "Blog with id: \(id) was unexpectedly not found." + } + } + } } From df0214e85b65c25d9aed0fdb7198fad0cfd00c02 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Mon, 26 Jun 2023 22:57:58 +0700 Subject: [PATCH 030/175] Add tests for JetpackSocialService --- .../Services/JetpackSocialService.swift | 1 + WordPress/WordPress.xcodeproj/project.pbxproj | 4 + .../JetpackSocialServiceTests.swift | 189 ++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 WordPress/WordPressTest/JetpackSocialServiceTests.swift diff --git a/WordPress/Classes/Services/JetpackSocialService.swift b/WordPress/Classes/Services/JetpackSocialService.swift index 7a226a56084b..6f2a6fd1a9f4 100644 --- a/WordPress/Classes/Services/JetpackSocialService.swift +++ b/WordPress/Classes/Services/JetpackSocialService.swift @@ -36,6 +36,7 @@ class JetpackSocialService { case .success(let remotePublicizeInfo): self?.coreDataStack.performAndSave({ context -> PublicizeInfo.SharingLimit? in guard let blog = try Blog.lookup(withID: blogID, in: context) else { + // unexpected to fall into this case, since the API should return an error response. throw ServiceError.blogNotFound(id: blogID) } diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index efebfd9a71b3..024e70f2998f 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -5495,6 +5495,7 @@ FE1E20182A47042500CE7C90 /* PublicizeInfo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */; }; FE1E201A2A473E0800CE7C90 /* JetpackSocialService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20192A473E0800CE7C90 /* JetpackSocialService.swift */; }; FE1E201B2A473E0800CE7C90 /* JetpackSocialService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E20192A473E0800CE7C90 /* JetpackSocialService.swift */; }; + FE1E201E2A49D59400CE7C90 /* JetpackSocialServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E201D2A49D59400CE7C90 /* JetpackSocialServiceTests.swift */; }; FE23EB4926E7C91F005A1698 /* richCommentTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */; }; FE23EB4A26E7C91F005A1698 /* richCommentTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */; }; FE23EB4B26E7C91F005A1698 /* richCommentStyle.css in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4826E7C91F005A1698 /* richCommentStyle.css */; }; @@ -9299,6 +9300,7 @@ FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "PublicizeInfo+CoreDataClass.swift"; path = "/Users/dvdchr/code/github/wpios/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift"; sourceTree = ""; }; FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "PublicizeInfo+CoreDataProperties.swift"; path = "/Users/dvdchr/code/github/wpios/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift"; sourceTree = ""; }; FE1E20192A473E0800CE7C90 /* JetpackSocialService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackSocialService.swift; sourceTree = ""; }; + FE1E201D2A49D59400CE7C90 /* JetpackSocialServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackSocialServiceTests.swift; sourceTree = ""; }; FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = richCommentTemplate.html; path = Resources/HTML/richCommentTemplate.html; sourceTree = ""; }; FE23EB4826E7C91F005A1698 /* richCommentStyle.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = richCommentStyle.css; path = Resources/HTML/richCommentStyle.css; sourceTree = ""; }; FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsNotificationSheetViewController.swift; sourceTree = ""; }; @@ -15329,6 +15331,7 @@ FAB4F32624EDE12A00F259BA /* FollowCommentsServiceTests.swift */, B5772AC51C9C84900031F97E /* GravatarServiceTests.swift */, 8B749E8F25AF8D2E00023F03 /* JetpackCapabilitiesServiceTests.swift */, + FE1E201D2A49D59400CE7C90 /* JetpackSocialServiceTests.swift */, F1BB660B274E704D00A319BE /* LikeUserHelperTests.swift */, 59A9AB391B4C3ECD00A433DC /* LocalCoreDataServiceTests.m */, F11023A0231863CE00C4E84A /* MediaServiceTests.swift */, @@ -23655,6 +23658,7 @@ 3F50945F245537A700C4470B /* ReaderTabViewModelTests.swift in Sources */, 0885A3671E837AFE00619B4D /* URLIncrementalFilenameTests.swift in Sources */, D848CBF920FEF82100A9038F /* NotificationsContentFactoryTests.swift in Sources */, + FE1E201E2A49D59400CE7C90 /* JetpackSocialServiceTests.swift in Sources */, 575802132357C41200E4C63C /* MediaCoordinatorTests.swift in Sources */, F18B43781F849F580089B817 /* PostAttachmentTests.swift in Sources */, 400A2C972217B883000A8A59 /* VisitsSummaryStatsRecordValueTests.swift in Sources */, diff --git a/WordPress/WordPressTest/JetpackSocialServiceTests.swift b/WordPress/WordPressTest/JetpackSocialServiceTests.swift new file mode 100644 index 000000000000..5054b11b1187 --- /dev/null +++ b/WordPress/WordPressTest/JetpackSocialServiceTests.swift @@ -0,0 +1,189 @@ +import Foundation +import XCTest +import OHHTTPStubs + +@testable import WordPress + +class JetpackSocialServiceTests: CoreDataTestCase { + + private let timeout: TimeInterval = 1.0 + private let blogID = 1001 + + private var jetpackSocialPath: String { + "/wpcom/v2/sites/\(blogID)/jetpack-social" + } + + private lazy var service: JetpackSocialService = { + .init(coreDataStack: contextManager) + }() + + override func setUp() { + super.setUp() + + BlogBuilder(mainContext).with(blogID: blogID).build() + contextManager.saveContextAndWait(mainContext) + } + + override func tearDown() { + HTTPStubs.removeAllStubs() + super.tearDown() + } + + // MARK: syncSharingLimit + + // non-existing PublicizeInfo + some RemotePublicizeInfo -> insert + func testSyncSharingLimitWithNewPublicizeInfo() throws { + stub(condition: isPath(jetpackSocialPath)) { _ in + HTTPStubsResponse(jsonObject: ["share_limit": 30, + "to_be_publicized_count": 15, + "shared_posts_count": 15, + "shares_remaining": 14] as [String: Any], + statusCode: 200, + headers: nil) + } + + let expectation = expectation(description: "syncSharingLimit should succeed") + service.syncSharingLimit(for: blogID) { result in + guard case .success(let sharingLimit) = result else { + XCTFail("syncSharingLimit unexpectedly failed") + return expectation.fulfill() + } + + XCTAssertNotNil(sharingLimit) + XCTAssertEqual(sharingLimit?.remaining, 14) + XCTAssertEqual(sharingLimit?.limit, 30) + expectation.fulfill() + } + wait(for: [expectation], timeout: timeout) + } + + // non-existing PublicizeInfo + nil RemotePublicizeInfo -> nothing changes + func testSyncSharingLimitWithNilPublicizeInfo() { + stub(condition: isPath(jetpackSocialPath)) { _ in + HTTPStubsResponse(jsonObject: [String: Any](), + statusCode: 200, + headers: nil) + } + + let expectation = expectation(description: "syncSharingLimit should succeed") + service.syncSharingLimit(for: blogID) { result in + guard case .success(let sharingLimit) = result else { + XCTFail("syncSharingLimit unexpectedly failed") + return expectation.fulfill() + } + + XCTAssertNil(sharingLimit) + expectation.fulfill() + } + wait(for: [expectation], timeout: timeout) + } + + // pre-existing PublicizeInfo + some RemotePublicizeInfo -> update + func testSyncSharingLimitWithNewPublicizeInfoGivenPreExistingData() throws { + try addPreExistingPublicizeInfo() + stub(condition: isPath(jetpackSocialPath)) { _ in + HTTPStubsResponse(jsonObject: ["share_limit": 30, + "to_be_publicized_count": 15, + "shared_posts_count": 15, + "shares_remaining": 14] as [String: Any], + statusCode: 200, + headers: nil) + } + + let expectation = expectation(description: "syncSharingLimit should succeed") + service.syncSharingLimit(for: blogID) { result in + guard case .success(let sharingLimit) = result else { + XCTFail("syncSharingLimit unexpectedly failed") + return expectation.fulfill() + } + + // the sharing limit fields should be updated according to the newest data. + XCTAssertNotNil(sharingLimit) + XCTAssertEqual(sharingLimit?.remaining, 14) + XCTAssertEqual(sharingLimit?.limit, 30) + expectation.fulfill() + } + wait(for: [expectation], timeout: timeout) + } + + // pre-existing PublicizeInfo + nil RemotePublicizeInfo -> delete + func testSyncSharingLimitWithNilPublicizeInfoGivenPreExistingData() throws { + try addPreExistingPublicizeInfo() + stub(condition: isPath(jetpackSocialPath)) { _ in + HTTPStubsResponse(jsonObject: [String: Any](), + statusCode: 200, + headers: nil) + } + + let expectation = expectation(description: "syncSharingLimit should succeed") + service.syncSharingLimit(for: blogID) { result in + guard case .success(let sharingLimit) = result else { + XCTFail("syncSharingLimit unexpectedly failed") + return expectation.fulfill() + } + + // the pre-existing sharing limit should've been deleted. + XCTAssertNil(sharingLimit) + expectation.fulfill() + } + wait(for: [expectation], timeout: timeout) + } + + // non-existing blog ID + some RemotePublicizeInfo + func testSyncSharingLimitWithNewPublicizeInfoGivenInvalidBlogID() { + let invalidBlogID = 1002 + stub(condition: isPath("/wpcom/v2/sites/\(invalidBlogID)/jetpack-social")) { _ in + HTTPStubsResponse(jsonObject: [String: Any](), + statusCode: 200, + headers: nil) + } + + let expectation = expectation(description: "syncSharingLimit should fail") + service.syncSharingLimit(for: invalidBlogID) { result in + guard case .failure(let error) = result, + case .blogNotFound(let id) = error as? JetpackSocialService.ServiceError else { + XCTFail("Expected JetpackSocialService.ServiceError to occur") + return expectation.fulfill() + } + + XCTAssertEqual(id, invalidBlogID) + expectation.fulfill() + } + wait(for: [expectation], timeout: timeout) + } + + func testSyncSharingLimitRemoteFetchFailure() { + stub(condition: isPath(jetpackSocialPath)) { _ in + HTTPStubsResponse(jsonObject: [String: Any](), + statusCode: 500, + headers: nil) + } + + let expectation = expectation(description: "syncSharingLimit should fail") + service.syncSharingLimit(for: blogID) { result in + guard case .failure = result else { + XCTFail("syncSharingLimit unexpectedly succeeded") + return expectation.fulfill() + } + + expectation.fulfill() + } + wait(for: [expectation], timeout: timeout) + } + +} + +// MARK: - Helpers + +private extension JetpackSocialServiceTests { + + func addPreExistingPublicizeInfo() throws { + let blog = try Blog.lookup(withID: blogID, in: mainContext) + let info = PublicizeInfo(context: mainContext) + info.sharesRemaining = 550 + info.shareLimit = 1000 + blog?.publicizeInfo = info + contextManager.saveContextAndWait(mainContext) + } + +} From 462deaa3a8cf6abe4bcadb9fd604485242dc7fcf Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Thu, 8 Jun 2023 21:13:25 -0400 Subject: [PATCH 031/175] Add icon download for social media icons that aren't bundled --- .../JetpackSocialNoConnectionView.swift | 79 ++++++++++++++++--- ...SettingsViewController+JetpackSocial.swift | 15 +++- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift index a74082783c53..db1cce4ddf88 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift @@ -2,15 +2,14 @@ import SwiftUI struct JetpackSocialNoConnectionView: View { - private let viewModel: JetpackSocialNoConnectionViewModel + @StateObject private var viewModel: JetpackSocialNoConnectionViewModel var body: some View { VStack(alignment: .leading, spacing: 12.0) { HStack(spacing: -5.0) { - iconImage("icon-tumblr") - iconImage("icon-facebook") - iconImage("icon-twitter") - iconImage("icon-linkedin") + ForEach(viewModel.icons, id: \.self) { icon in + iconImage(icon) + } } .accessibilityElement() .accessibilityLabel(Constants.iconGroupAccessibilityLabel) @@ -38,10 +37,11 @@ struct JetpackSocialNoConnectionView: View { .background(Color(UIColor.listForeground)) } - func iconImage(_ image: String) -> some View { - Image(image) + func iconImage(_ image: UIImage) -> some View { + Image(uiImage: image) .resizable() .frame(width: 32.0, height: 32.0) + .background(Color(UIColor.listForeground)) .clipShape(Circle()) .overlay(Circle().stroke(Color(UIColor.listForeground), lineWidth: 2.0)) } @@ -59,13 +59,15 @@ extension JetpackSocialNoConnectionView { // MARK: - View model -struct JetpackSocialNoConnectionViewModel { +class JetpackSocialNoConnectionViewModel: ObservableObject { let padding: EdgeInsets let hideNotNow: Bool let onConnectTap: (() -> Void)? let onNotNowTap: (() -> Void)? + @MainActor @Published var icons: [UIImage] = [UIImage()] - init(padding: EdgeInsets = Constants.defaultPadding, + init(services: [PublicizeService] = [], + padding: EdgeInsets = Constants.defaultPadding, hideNotNow: Bool = false, onConnectTap: (() -> Void)? = nil, onNotNowTap: (() -> Void)? = nil) { @@ -73,6 +75,65 @@ struct JetpackSocialNoConnectionViewModel { self.hideNotNow = hideNotNow self.onConnectTap = onConnectTap self.onNotNowTap = onNotNowTap + updateIcons(services) + } + + enum JetpackSocialService: String { + case facebook + case twitter + case tumblr + case linkedin + case unknown + + var image: UIImage? { + switch self { + case .facebook: + return UIImage(named: "icon-facebook") + case .twitter: + return UIImage(named: "icon-twitter") + case .tumblr: + return UIImage(named: "icon-tumblr") + case .linkedin: + return UIImage(named: "icon-linkedin") + case .unknown: + return UIImage(named: "social-default")?.withRenderingMode(.alwaysTemplate) + } + } + } + + private func updateIcons(_ services: [PublicizeService]) { + var icons: [UIImage] = [] + var downloadTasks: [(url: URL, index: Int)] = [] + for (index, service) in services.enumerated() { + let serviceType = JetpackSocialService(rawValue: service.serviceID) ?? .unknown + let icon = serviceType.image ?? UIImage() + icons.append(icon) + + if serviceType == .unknown { + guard let iconUrl = URL(string: service.icon) else { + continue + } + downloadTasks.append((url: iconUrl, index: index)) + } + } + + DispatchQueue.main.async { + self.icons = icons + + for task in downloadTasks { + let (url, index) = task + WPImageSource.shared().downloadImage(for: url) { image in + guard let image else { + return + } + DispatchQueue.main.async { + self.icons[index] = image + } + } failure: { error in + DDLogError("Error downloading icon: \(String(describing: error))") + } + } + } } } diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift index b06e52d05248..efe288788110 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift @@ -5,11 +5,18 @@ extension PostSettingsViewController { let isJetpackSocialEnabled = FeatureFlag.jetpackSocial.enabled let blogSupportsPublicize = apost.blog.supportsPublicize() let blogHasNoConnections = publicizeConnections.count == 0 - return isJetpackSocialEnabled && blogSupportsPublicize && blogHasNoConnections + let blogHasServices = availableServices().count > 0 + + return isJetpackSocialEnabled + && blogSupportsPublicize + && blogHasNoConnections + && blogHasServices } @objc func createNoConnectionView() -> UIView { + let services = availableServices() let viewModel = JetpackSocialNoConnectionViewModel( + services: services, onConnectTap: { // TODO: Open the social screen print("Connect tap") @@ -25,4 +32,10 @@ extension PostSettingsViewController { return viewController.view } + private func availableServices() -> [PublicizeService] { + let context = apost.managedObjectContext ?? ContextManager.shared.mainContext + let services = try? PublicizeService.allPublicizeServices(in: context) + return services ?? [] + } + } From 7ebf53681da209855c3f3c099f2af6c79a8c516c Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Mon, 26 Jun 2023 18:19:52 -0400 Subject: [PATCH 032/175] Update Tumblr's dark icon --- .../Social/icon-tumblr.imageset/icon-tumblr-dark.svg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-tumblr.imageset/icon-tumblr-dark.svg b/WordPress/Jetpack/AppImages.xcassets/Social/icon-tumblr.imageset/icon-tumblr-dark.svg index 34eed67cc0b9..868a5c122aa7 100644 --- a/WordPress/Jetpack/AppImages.xcassets/Social/icon-tumblr.imageset/icon-tumblr-dark.svg +++ b/WordPress/Jetpack/AppImages.xcassets/Social/icon-tumblr.imageset/icon-tumblr-dark.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + From 986811cde481460b1ba3c7189e18b63875737c4d Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Mon, 26 Jun 2023 18:20:18 -0400 Subject: [PATCH 033/175] Remove additional space --- .../Post/PostSettingsViewController+JetpackSocial.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift index efe288788110..9b31b87bd7a9 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift @@ -35,7 +35,7 @@ extension PostSettingsViewController { private func availableServices() -> [PublicizeService] { let context = apost.managedObjectContext ?? ContextManager.shared.mainContext let services = try? PublicizeService.allPublicizeServices(in: context) - return services ?? [] + return services ?? [] } } From ace86f0c100a8152f1453915f7ff42e82fde8035 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 27 Jun 2023 13:45:34 +1200 Subject: [PATCH 034/175] Don't save synced blogs after signed out --- WordPress/Classes/Services/BlogService.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WordPress/Classes/Services/BlogService.m b/WordPress/Classes/Services/BlogService.m index 5065924ee101..e704ca5cb6b5 100644 --- a/WordPress/Classes/Services/BlogService.m +++ b/WordPress/Classes/Services/BlogService.m @@ -424,6 +424,11 @@ - (void)mergeBlogs:(NSArray *)blogs withAccountID:(NSManagedObject { // Nuke dead blogs WPAccount *account = [context existingObjectWithID:accountID error:nil]; + if (account == nil) { + DDLogInfo(@"Can't find the account. User may have signed out."); + return; + } + NSSet *remoteSet = [NSSet setWithArray:[blogs valueForKey:@"blogID"]]; NSSet *localSet = [account.blogs valueForKey:@"dotComID"]; NSMutableSet *toDelete = [localSet mutableCopy]; From 1cf2cb1cc9fcf14645e84244ef6f34be950653af Mon Sep 17 00:00:00 2001 From: Hassaan El-Garem Date: Tue, 27 Jun 2023 07:02:26 +0300 Subject: [PATCH 035/175] Update: make the `view` variable in the view models weak --- .../BlazeCampaignDetailsWebViewModel.swift | 13 +++++++------ .../Webview/BlazeCreateCampaignWebViewModel.swift | 15 ++++++++------- .../Blaze/Webview/BlazeWebViewController.swift | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift index a90efa1fb5de..c3cc14a177f6 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCampaignDetailsWebViewModel.swift @@ -7,7 +7,7 @@ class BlazeCampaignDetailsWebViewModel: BlazeWebViewModel { private let source: BlazeSource private let blog: Blog private let campaignID: Int - private let view: BlazeWebView + private weak var view: BlazeWebView? private let externalURLHandler: ExternalURLHandler private var linkBehavior: LinkBehavior = .all @@ -54,22 +54,23 @@ class BlazeCampaignDetailsWebViewModel: BlazeWebViewModel { } func startBlazeFlow() { - guard let initialURL else { + guard let initialURL, + let cookieJar = view?.cookieJar else { // TODO: Track Analytics Error Event - view.dismissView() + view?.dismissView() return } - authenticatedRequest(for: initialURL, with: view.cookieJar) { [weak self] (request) in + authenticatedRequest(for: initialURL, with: cookieJar) { [weak self] (request) in guard let weakSelf = self else { return } - weakSelf.view.load(request: request) + weakSelf.view?.load(request: request) // TODO: Track Analytics Event } } func dismissTapped() { - view.dismissView() + view?.dismissView() // TODO: Track Analytics Event } diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift index a7d1a1d8067b..30c6a3a73255 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeCreateCampaignWebViewModel.swift @@ -12,7 +12,7 @@ class BlazeCreateCampaignWebViewModel: BlazeWebViewModel { private let source: BlazeSource private let blog: Blog private let postID: NSNumber? - private let view: BlazeWebView + private weak var view: BlazeWebView? private let remoteConfigStore: RemoteConfigStore private let externalURLHandler: ExternalURLHandler private var linkBehavior: LinkBehavior = .all @@ -64,22 +64,23 @@ class BlazeCreateCampaignWebViewModel: BlazeWebViewModel { } func startBlazeFlow() { - guard let initialURL else { + guard let initialURL, + let cookieJar = view?.cookieJar else { BlazeEventsTracker.trackBlazeFlowError(for: source, currentStep: currentStep) - view.dismissView() + view?.dismissView() return } - authenticatedRequest(for: initialURL, with: view.cookieJar) { [weak self] (request) in + authenticatedRequest(for: initialURL, with: cookieJar) { [weak self] (request) in guard let weakSelf = self else { return } - weakSelf.view.load(request: request) + weakSelf.view?.load(request: request) BlazeEventsTracker.trackBlazeFlowStarted(for: weakSelf.source) } } func dismissTapped() { - view.dismissView() + view?.dismissView() if isFlowCompleted { BlazeEventsTracker.trackBlazeFlowCompleted(for: source, currentStep: currentStep) } else { @@ -90,7 +91,7 @@ class BlazeCreateCampaignWebViewModel: BlazeWebViewModel { func shouldNavigate(to request: URLRequest, with type: WKNavigationType) -> WKNavigationActionPolicy { currentStep = extractCurrentStep(from: request) ?? currentStep updateIsFlowCompleted() - view.reloadNavBar() + view?.reloadNavBar() return linkBehavior.handle(request: request, with: type, externalURLHandler: externalURLHandler) } diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift index 4b2baf8963a8..328afee5ce2c 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewController.swift @@ -15,7 +15,7 @@ protocol BlazeWebViewModel { var navigationTitle: String { get } } -protocol BlazeWebView { +protocol BlazeWebView: NSObjectProtocol { func load(request: URLRequest) func reloadNavBar() func dismissView() From 1a79fff8753a5b5a1aae791e3bcc8ddca60c9d0f Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 27 Jun 2023 16:15:12 +1200 Subject: [PATCH 036/175] Add an unit test to test account deletion during syncing blogs --- .../WordPressTest/BlogJetpackTests.swift | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/WordPress/WordPressTest/BlogJetpackTests.swift b/WordPress/WordPressTest/BlogJetpackTests.swift index 10234b71b2cb..5490efb97fab 100644 --- a/WordPress/WordPressTest/BlogJetpackTests.swift +++ b/WordPress/WordPressTest/BlogJetpackTests.swift @@ -151,6 +151,43 @@ class BlogJetpackTests: CoreDataTestCase { XCTAssertEqual(2, Blog.count(in: mainContext)) } + /// Verify an account's blogs won't be saved if the account is deleted during a blog sync. + func testSyncBlogsAndSignOut() throws { + let wpComAccount = try createOrUpdateAccount(username: "user", authToken: "token") + XCTAssertEqual(Blog.count(in: mainContext), 0) + var deleted = false + + // Blog sync makes a series of HTTP requests: the first one fetchs all blogs, followed by a few + // requests to get blog capabilities (one for each blog). + HTTPStubs.stubRequest(forEndpoint: "me/sites", + withFileAtPath: OHPathForFile("me-sites-with-jetpack.json", Self.self)!) + HTTPStubs.stubRequests { request in + (request.url?.path.matches(regex: "sites/\\d+/rewind/capabilities").count ?? 0) > 0 + } withStubResponse: { _ in + // We can't delete the `Account` instance until the first API request completes. Because the URLSession instance + // used in the `me/sites` API request will be invalidated upon account deletion (see `WPAccount.prepareForDeletion` method). + self.mainContext.performAndWait { + // Delete the account to simulate user signing out of the app. + guard !deleted else { return } + self.mainContext.delete(wpComAccount) + try! self.mainContext.save() + deleted = true + } + return HTTPStubsResponse(jsonObject: [String: Int](), statusCode: 200, headers: nil) + } + + let syncExpectation = expectation(description: "Blogs sync") + blogService.syncBlogs(for: wpComAccount) { + syncExpectation.fulfill() + } failure: { error in + XCTFail("Sync blogs shouldn't fail: \(error)") + } + + // No blogs should be saved after the sync blogs operation finishes. + wait(for: [syncExpectation], timeout: 1.0) + XCTAssertEqual(Blog.count(in: mainContext), 0) + } + // MARK: Jetpack Individual Plugins func testJetpackIsConnectedWithoutFullPluginGivenIndividualPluginOnlyReturnsTrue() { From 175f615bbad6d79eeb9e4d77dc99044e905d533d Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 27 Jun 2023 16:18:42 +1200 Subject: [PATCH 037/175] Add a release note --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 811221163b1e..33e9bc268f0f 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,6 @@ 22.8 ----- +* [**] [internal] Do not save synced blogs if the app has signed out. [#20959] 22.7 From 9b01df7d8cfadf59759ee0deb7c1567b99a3aae1 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 27 Jun 2023 16:45:44 +1200 Subject: [PATCH 038/175] Make sure context is saved before calling the completion callback --- WordPress/Classes/Services/PostService.m | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Services/PostService.m b/WordPress/Classes/Services/PostService.m index eabb2e04dc5d..b446b2cc0064 100644 --- a/WordPress/Classes/Services/PostService.m +++ b/WordPress/Classes/Services/PostService.m @@ -701,10 +701,15 @@ - (void)mergePosts:(NSArray *)remotePosts } } - [[ContextManager sharedInstance] saveContext:self.managedObjectContext]; - if (completion) { - completion(posts); - } + [[ContextManager sharedInstance] saveContext:self.managedObjectContext withCompletionBlock:^{ + // Call the completion block after context is saved. The callback is called on the context queue because `posts` + // contains models that are bound to the `self.managedObjectContext` object. + [self.managedObjectContext performBlock:^{ + if (completion) { + completion(posts); + } + }]; + } onQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; } - (NSDictionary *)remoteSyncParametersDictionaryForRemote:(nonnull id )remote From 093b4b37771d6c0dfac85b5e43d348fb96896a16 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 27 Jun 2023 16:53:42 +1200 Subject: [PATCH 039/175] Add a release note --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 811221163b1e..bf57fbbd82ac 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,6 @@ 22.8 ----- +* [**] [internal] Make sure synced posts are saved before calling completion block. [#20960] 22.7 From cc116c96e4f77da13c238495ba5f1090953c7442 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Tue, 27 Jun 2023 13:08:14 +0700 Subject: [PATCH 040/175] Switch PublicizeInfo subclass file path to relative --- WordPress/WordPress.xcodeproj/project.pbxproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index d65a37af1c98..5b8ec56e6b3c 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -351,6 +351,8 @@ 0C391E622A3002950040EA91 /* BlazeCampaignStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391E602A3002950040EA91 /* BlazeCampaignStatusView.swift */; }; 0C391E642A312DB20040EA91 /* BlazeCampaignViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */; }; 0C63266F2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C63266E2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift */; }; + 0C71959B2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; + 0C71959C2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; 0C7E09202A4286A00052324C /* PostMetaButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E091F2A4286A00052324C /* PostMetaButton.m */; }; 0C7E09212A4286A00052324C /* PostMetaButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E091F2A4286A00052324C /* PostMetaButton.m */; }; 0C7E09242A4286F40052324C /* PostMetaButton+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */; }; @@ -362,8 +364,6 @@ 0C896DE42A3A7BDC00D7D4E7 /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DDF2A3A763400D7D4E7 /* SettingsCell.swift */; }; 0C896DE52A3A7C1F00D7D4E7 /* SiteVisibility+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */; }; 0C896DE72A3A832B00D7D4E7 /* SiteVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DE62A3A832B00D7D4E7 /* SiteVisibilityTests.swift */; }; - 0C71959B2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; - 0C71959C2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; 0CB4056B29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */; }; 0CB4056C29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */; }; 0CB4056E29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */; }; @@ -6058,6 +6058,7 @@ 0C391E602A3002950040EA91 /* BlazeCampaignStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignStatusView.swift; sourceTree = ""; }; 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignViewModelTests.swift; sourceTree = ""; }; 0C63266E2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergFilesAppMediaSourceTests.swift; sourceTree = ""; }; + 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingsRelatedPostsView.swift; 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 = ""; }; 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostMetaButton+Swift.swift"; sourceTree = ""; }; @@ -6065,7 +6066,6 @@ 0C896DDF2A3A763400D7D4E7 /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteVisibility+Extensions.swift"; sourceTree = ""; }; 0C896DE62A3A832B00D7D4E7 /* SiteVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteVisibilityTests.swift; sourceTree = ""; }; - 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingsRelatedPostsView.swift; sourceTree = ""; }; 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationService.swift; sourceTree = ""; }; 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationServiceTests.swift; sourceTree = ""; }; 0CB4057029C8DCF4008EED0A /* BlogDashboardPersonalizationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationViewModel.swift; sourceTree = ""; }; @@ -9301,8 +9301,8 @@ FE06AC8426C3C2F800B69DE4 /* ShareAppTextActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppTextActivityItemSource.swift; sourceTree = ""; }; FE18495727F5ACBA00D26879 /* DashboardPromptsCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPromptsCardCell.swift; sourceTree = ""; }; FE1E200F2A45ACE900CE7C90 /* WordPress 151.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 151.xcdatamodel"; sourceTree = ""; }; - FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "PublicizeInfo+CoreDataClass.swift"; path = "/Users/dvdchr/code/github/wpios/WordPress/Classes/Models/PublicizeInfo+CoreDataClass.swift"; sourceTree = ""; }; - FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "PublicizeInfo+CoreDataProperties.swift"; path = "/Users/dvdchr/code/github/wpios/WordPress/Classes/Models/PublicizeInfo+CoreDataProperties.swift"; sourceTree = ""; }; + FE1E20132A47042400CE7C90 /* PublicizeInfo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicizeInfo+CoreDataClass.swift"; sourceTree = ""; }; + FE1E20142A47042500CE7C90 /* PublicizeInfo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicizeInfo+CoreDataProperties.swift"; sourceTree = ""; }; FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = richCommentTemplate.html; path = Resources/HTML/richCommentTemplate.html; sourceTree = ""; }; FE23EB4826E7C91F005A1698 /* richCommentStyle.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = richCommentStyle.css; path = Resources/HTML/richCommentStyle.css; sourceTree = ""; }; FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsNotificationSheetViewController.swift; sourceTree = ""; }; From 38cfe772d63c46bb4a4884ef78b415e4862f2576 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Tue, 27 Jun 2023 13:12:05 +0700 Subject: [PATCH 041/175] Fix migrations.md and some model properties --- MIGRATIONS.md | 4 ++-- .../WordPress 151.xcdatamodel/contents | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 63e60f8a0f45..4270609fc962 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -15,9 +15,9 @@ data model as well as any custom migrations. - Created one-to-many relationship between `PublicizeInfo` and `Blog` - `PublicizeInfo` - - `blog` (optional, to-many, nullify on delete) + - `blog` (optional, to-one, nullify on delete) - `Blog` - - `publicizeInfo` (required, to-one, cascade on delete) + - `publicizeInfo` (optional, to-one, cascade on delete) ## WordPress 150 diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents index faa35233dac7..80b80cc781af 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents @@ -667,10 +667,10 @@ - - + + - + From 6be84eb9437c4422caf6588d35c95980407a3ed0 Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Tue, 27 Jun 2023 14:19:50 -0400 Subject: [PATCH 042/175] Add Instagram and Mastodon social icons --- .../Social/JetpackSocialNoConnectionView.swift | 6 ++++++ .../Social/icon-instagram.imageset/Contents.json | 12 ++++++++++++ .../icon-instagram.imageset/icon-instagram.svg | 1 + .../Social/icon-mastodon.imageset/Contents.json | 12 ++++++++++++ .../Social/icon-mastodon.imageset/icon-mastodon.svg | 1 + 5 files changed, 32 insertions(+) create mode 100644 WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/Contents.json create mode 100644 WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/icon-instagram.svg create mode 100644 WordPress/Jetpack/AppImages.xcassets/Social/icon-mastodon.imageset/Contents.json create mode 100644 WordPress/Jetpack/AppImages.xcassets/Social/icon-mastodon.imageset/icon-mastodon.svg diff --git a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift index db1cce4ddf88..8e209285c50e 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift @@ -83,6 +83,8 @@ class JetpackSocialNoConnectionViewModel: ObservableObject { case twitter case tumblr case linkedin + case instagram + case mastodon case unknown var image: UIImage? { @@ -95,6 +97,10 @@ class JetpackSocialNoConnectionViewModel: ObservableObject { return UIImage(named: "icon-tumblr") case .linkedin: return UIImage(named: "icon-linkedin") + case .instagram: + return UIImage(named: "icon-instagram") + case .mastodon: + return UIImage(named: "icon-mastodon") case .unknown: return UIImage(named: "social-default")?.withRenderingMode(.alwaysTemplate) } diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/Contents.json b/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/Contents.json new file mode 100644 index 000000000000..16fc84e28443 --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-instagram.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/icon-instagram.svg b/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/icon-instagram.svg new file mode 100644 index 000000000000..69c10588109f --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/icon-instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-mastodon.imageset/Contents.json b/WordPress/Jetpack/AppImages.xcassets/Social/icon-mastodon.imageset/Contents.json new file mode 100644 index 000000000000..65dfffd6be37 --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/Social/icon-mastodon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-mastodon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-mastodon.imageset/icon-mastodon.svg b/WordPress/Jetpack/AppImages.xcassets/Social/icon-mastodon.imageset/icon-mastodon.svg new file mode 100644 index 000000000000..fcea8ed22762 --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/Social/icon-mastodon.imageset/icon-mastodon.svg @@ -0,0 +1 @@ + \ No newline at end of file From 007df21d17df580e178043ec3e9eef505d1ef1be Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Tue, 27 Jun 2023 14:20:13 -0400 Subject: [PATCH 043/175] Update Tumblr's dark social icon --- .../Social/icon-tumblr.imageset/icon-tumblr-dark.svg | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-tumblr.imageset/icon-tumblr-dark.svg b/WordPress/Jetpack/AppImages.xcassets/Social/icon-tumblr.imageset/icon-tumblr-dark.svg index 868a5c122aa7..57292a204027 100644 --- a/WordPress/Jetpack/AppImages.xcassets/Social/icon-tumblr.imageset/icon-tumblr-dark.svg +++ b/WordPress/Jetpack/AppImages.xcassets/Social/icon-tumblr.imageset/icon-tumblr-dark.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file From f9a8bd950f2cef6d4e73a01dc7725e75a8157148 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 23 Jun 2023 11:38:23 -0400 Subject: [PATCH 044/175] Integrate Blaze campaigns API --- WordPress/Classes/Services/BlazeService.swift | 5 ++ .../DashboardBlazeCampaignsCardView.swift | 37 +---------- .../Cards/Blaze/DashboardBlazeCardCell.swift | 66 +++++++++++++++++-- .../ViewModel/BlogDashboardViewModel.swift | 7 +- 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/WordPress/Classes/Services/BlazeService.swift b/WordPress/Classes/Services/BlazeService.swift index 60215938b754..0e56e3160cc4 100644 --- a/WordPress/Classes/Services/BlazeService.swift +++ b/WordPress/Classes/Services/BlazeService.swift @@ -26,6 +26,10 @@ import WordPressKit func getRecentCampaigns(for blog: Blog, completion: @escaping (Result) -> Void) { + guard blog.canBlaze else { + completion(.failure(BlazeServiceError.notEligibleForBlaze)) + return + } guard let siteId = blog.dotComID?.intValue else { DDLogError("Invalid site ID for Blaze") completion(.failure(BlazeServiceError.missingBlogId)) @@ -36,5 +40,6 @@ import WordPressKit } enum BlazeServiceError: Error { + case notEligibleForBlaze case missingBlogId } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift index 683a5036a41c..a35a9343b188 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift @@ -88,7 +88,7 @@ final class DashboardBlazeCampaignsCardView: UIView { BlazeFlowCoordinator.presentBlaze(in: presentingViewController, source: .dashboardCard, blog: blog) } - func configure(blog: Blog, viewController: BlogDashboardViewController?) { + func configure(blog: Blog, viewController: BlogDashboardViewController?, campaign: BlazeCampaign) { self.blog = blog self.presentingViewController = viewController @@ -101,7 +101,7 @@ final class DashboardBlazeCampaignsCardView: UIView { ]) ], card: .blaze) - let viewModel = BlazeCampaignViewModel(campaign: mockResponse.campaigns!.first!) + let viewModel = BlazeCampaignViewModel(campaign: campaign) campaignView.configure(with: viewModel, blog: blog) } } @@ -118,36 +118,3 @@ private extension DashboardBlazeCampaignsCardView { static let createCampaignInsets = UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16) } } - -private let mockResponse: BlazeCampaignsSearchResponse = { - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - decoder.dateDecodingStrategy = .iso8601 - return try! decoder.decode(BlazeCampaignsSearchResponse.self, from: """ - { - "totalItems": 3, - "campaigns": [ - { - "campaign_id": 26916, - "name": "Test Post - don't approve", - "start_date": "2023-06-13T00:00:00Z", - "end_date": "2023-06-01T19:15:45Z", - "status": "finished", - "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", - "budget_cents": 500, - "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "content_config": { - "title": "Test Post - don't approve", - "snippet": "Test Post Empty Empty", - "clickUrl": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "imageUrl": "https://i0.wp.com/public-api.wordpress.com/wpcom/v2/wordads/dsp/api/v1/dsp/creatives/56259/image?w=600&zoom=2" - }, - "campaign_stats": { - "impressions_total": 1000, - "clicks_total": 235 - } - } - ] - } - """.data(using: .utf8)!) -}() diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift index 0be8fe967cef..75e3e00ec3cb 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift @@ -1,18 +1,41 @@ import UIKit +import WordPressKit final class DashboardBlazeCardCell: DashboardCollectionViewCell { + private var blog: Blog? + private var viewController: BlogDashboardViewController? + private var viewModel: DashboardBlazeCardCellViewModel? + func configure(blog: Blog, viewController: BlogDashboardViewController?, apiResponse: BlogDashboardRemoteEntity?) { + self.blog = blog + self.viewController = viewController + BlazeEventsTracker.trackEntryPointDisplayed(for: .dashboardCard) + } + + func configure(_ viewModel: DashboardBlazeCardCellViewModel) { + guard viewModel !== self.viewModel else { return } + self.viewModel = viewModel + + viewModel.onRefreshNeeded = { [weak self] in + self?.update(with: $0) + self?.viewController?.collectionView.collectionViewLayout.invalidateLayout() + } + update(with: viewModel) + } + + private func update(with viewModel: DashboardBlazeCardCellViewModel) { + guard let blog, let viewController else { return } - if RemoteFeatureFlag.blazeManageCampaigns.enabled() { + if let campaign = viewModel.lastestCampaign { // Display campaigns let cardView = DashboardBlazeCampaignsCardView() - cardView.configure(blog: blog, viewController: viewController) - setCardView(cardView, subtype: .campaigns) + cardView.configure(blog: blog, viewController: viewController, campaign: campaign) + self.setCardView(cardView, subtype: .campaigns) } else { // Display promo let cardView = DashboardBlazePromoCardView(.make(with: blog, viewController: viewController)) - setCardView(cardView, subtype: .promo) + self.setCardView(cardView, subtype: .promo) } } @@ -30,6 +53,41 @@ final class DashboardBlazeCardCell: DashboardCollectionViewCell { } } +final class DashboardBlazeCardCellViewModel { + private(set) var lastestCampaign: BlazeCampaign? + + private let blog: Blog + private let service = BlazeService() + private var isRefreshing = false + + var onRefreshNeeded: ((DashboardBlazeCardCellViewModel) -> Void)? + + init(blog: Blog) { + self.blog = blog + } + + func refresh() { + guard RemoteFeatureFlag.blazeManageCampaigns.enabled() else { + return // Continue showing the default `Promo` card + } + + guard !isRefreshing, let service else { return } + isRefreshing = true + + service.getRecentCampaigns(for: blog) { [weak self] in + self?.didRefresh(with: $0) + } + } + + private func didRefresh(with result: Result) { + isRefreshing = false + if case .success(let response) = result { + lastestCampaign = response.campaigns?.first + } + onRefreshNeeded?(self) + } +} + enum DashboardBlazeCardSubtype: String { case promo = "no_campaigns" case campaigns = "campaigns" diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift index ca49a2f3e0e5..9f7335f6ce2b 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/ViewModel/BlogDashboardViewModel.swift @@ -67,6 +67,7 @@ class BlogDashboardViewModel { cellConfigurable.row = indexPath.row cellConfigurable.configure(blog: blog, viewController: viewController, apiResponse: cardModel.apiResponse) } + (cell as? DashboardBlazeCardCell)?.configure(blazeViewModel) return cell case .migrationSuccess: let cellType = DashboardMigrationSuccessCell.self @@ -74,14 +75,16 @@ class BlogDashboardViewModel { cell?.configure(with: viewController) return cell } - } }() + private let blazeViewModel: DashboardBlazeCardCellViewModel + init(viewController: BlogDashboardViewController, managedObjectContext: NSManagedObjectContext = ContextManager.shared.mainContext, blog: Blog) { self.viewController = viewController self.managedObjectContext = managedObjectContext self.blog = blog + self.blazeViewModel = DashboardBlazeCardCellViewModel(blog: blog) registerNotifications() } @@ -105,6 +108,8 @@ class BlogDashboardViewModel { completion?(cards) }) + + blazeViewModel.refresh() } @objc func loadCardsFromCache() { From 7cd235dc4a58d8188f3775a1dc0fcd4b03d27698 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 23 Jun 2023 13:10:53 -0400 Subject: [PATCH 045/175] Refresh the campaign card automatically after campaign is created --- .../ViewRelated/Blaze/Webview/BlazeWebViewModel.swift | 5 +++++ .../Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewModel.swift b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewModel.swift index 445643f26040..33a6020a0817 100644 --- a/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blaze/Webview/BlazeWebViewModel.swift @@ -84,6 +84,7 @@ class BlazeWebViewModel { func dismissTapped() { view.dismissView() if isFlowCompleted { + NotificationCenter.default.post(name: .blazeCampaignCreated, object: nil) BlazeEventsTracker.trackBlazeFlowCompleted(for: source, currentStep: currentStep) } else { BlazeEventsTracker.trackBlazeFlowCanceled(for: source, currentStep: currentStep) @@ -148,6 +149,10 @@ class BlazeWebViewModel { } } +extension Foundation.Notification.Name { + static let blazeCampaignCreated = Foundation.Notification.Name("BlazeWebFlowBlazeCampaignCreated") +} + extension BlazeWebViewModel: WebKitAuthenticatable { var authenticator: RequestAuthenticator? { RequestAuthenticator(blog: blog) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift index 75e3e00ec3cb..c562113bed0c 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift @@ -64,9 +64,11 @@ final class DashboardBlazeCardCellViewModel { init(blog: Blog) { self.blog = blog + + NotificationCenter.default.addObserver(self, selector: #selector(refresh), name: .blazeCampaignCreated, object: nil) } - func refresh() { + @objc func refresh() { guard RemoteFeatureFlag.blazeManageCampaigns.enabled() else { return // Continue showing the default `Promo` card } From f4114c98e835551840a2cd80c9e6d9e2ce644387 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 14:48:11 -0400 Subject: [PATCH 046/175] Update WordPressKit to 8.5-beta --- Podfile | 2 +- Podfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Podfile b/Podfile index a3b0285d7bae..e3bc85766801 100644 --- a/Podfile +++ b/Podfile @@ -50,7 +50,7 @@ def wordpress_ui end def wordpress_kit - pod 'WordPressKit', '~> 8.4-beta' + pod 'WordPressKit', '~> 8.5-beta' # 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', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '' diff --git a/Podfile.lock b/Podfile.lock index 606137ac1bd2..a20d7ded9b5a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -512,7 +512,7 @@ PODS: - WordPressKit (~> 8.0-beta) - WordPressShared (~> 2.1-beta) - WordPressUI (~> 1.7-beta) - - WordPressKit (8.4.0): + - WordPressKit (8.5.0-beta.1): - Alamofire (~> 4.8.0) - NSObject-SafeExpectations (~> 0.0.4) - UIDeviceIdentifier (~> 2.0) @@ -612,7 +612,7 @@ DEPENDENCIES: - SwiftLint (~> 0.50) - WordPress-Editor-iOS (~> 1.19.8) - WordPressAuthenticator (~> 6.1-beta) - - WordPressKit (~> 8.4-beta) + - WordPressKit (~> 8.5-beta) - WordPressShared (~> 2.2-beta) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) @@ -623,7 +623,6 @@ DEPENDENCIES: SPEC REPOS: https://github.com/wordpress-mobile/cocoapods-specs.git: - WordPressAuthenticator - - WordPressKit trunk: - Alamofire - AlamofireImage @@ -662,6 +661,7 @@ SPEC REPOS: - UIDeviceIdentifier - WordPress-Aztec-iOS - WordPress-Editor-iOS + - WordPressKit - WordPressShared - WordPressUI - WPMediaPicker @@ -880,7 +880,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 WordPress-Editor-iOS: 9eb9f12f21a5209cb837908d81ffe1e31cb27345 WordPressAuthenticator: b0b900696de5129a215adcd1e9ae6eb89da36ac8 - WordPressKit: f445e6fc3c63ddf611513a435408f86fdf3808b3 + WordPressKit: 25fabcaaa9961461a896cdd976ab43b10f91658a WordPressShared: 87f3ee89b0a3e83106106f13a8b71605fb8eb6d2 WordPressUI: c5be816f6c7b3392224ac21de9e521e89fa108ac WPMediaPicker: 0d40b8d66b6dfdaa2d6a41e3be51249ff5898775 @@ -895,6 +895,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 -PODFILE CHECKSUM: 623e3fe64f67d2c0352e26d1ac2bcd58c06b3494 +PODFILE CHECKSUM: dd2923ba0655d06a0e85bdf9aa0d38304e8f087e COCOAPODS: 1.12.1 From 1fe2cc8c80a42b39ba2f53c6d71d3b1ea88f3cb5 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 14:51:30 -0400 Subject: [PATCH 047/175] Update the campaign view to use ui_status --- .../Cards/Blaze/BlazeCampaignStatusView.swift | 13 ++++++++++--- .../Cards/Blaze/DashboardBlazeCampaignView.swift | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift index ff564b0c0f3d..54e79092b649 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift @@ -43,6 +43,10 @@ struct BlazeCampaignStatusViewModel { let textColor: UIColor let backgroundColor: UIColor + init(campaign: BlazeCampaign) { + self.init(status: campaign.uiStatus) + } + init(status: BlazeCampaign.Status) { self.isHidden = status == .unknown self.title = status.localizedTitle @@ -94,14 +98,17 @@ struct BlazeCampaignStatusViewModel { extension BlazeCampaign.Status { var localizedTitle: String { switch self { + case .created: + // There is no dedicated status for `In Moderation` on the backend. + // The app assumes that the campaign goes into moderation after creation. + return NSLocalizedString("blazeCampaign.status.inmoderation", value: "In Moderation", comment: "Short status description") case .scheduled: return NSLocalizedString("blazeCampaign.status.scheduled", value: "Scheduled", comment: "Short status description") - case .created: - return NSLocalizedString("blazeCampaign.status.created", value: "Created", comment: "Short status description") case .approved: return NSLocalizedString("blazeCampaign.status.approved", value: "Approved", comment: "Short status description") case .processing: - return NSLocalizedString("blazeCampaign.status.inmoderation", value: "In Moderation", comment: "Short status description") + // Should never be returned by `ui_status`. + return NSLocalizedString("blazeCampaign.status.processing", value: "Processing", comment: "Short status description") case .rejected: return NSLocalizedString("blazeCampaign.status.rejected", value: "Rejected", comment: "Short status description") case .active: diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift index a43fbb422277..202df4cfce06 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift @@ -103,7 +103,7 @@ struct BlazeCampaignViewModel { let impressions: Int let clicks: Int let budget: String - var status: BlazeCampaignStatusViewModel { .init(status: campaign.status) } + var status: BlazeCampaignStatusViewModel { .init(campaign: campaign) } var isShowingStats: Bool { switch campaign.status { From 79775912a8347961356c4cf5dc3b6c8bf60a1341 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 15:02:13 -0400 Subject: [PATCH 048/175] Move DashboardBlazeCardCellViewModel to a separate file --- .../Cards/Blaze/DashboardBlazeCardCell.swift | 37 ------------------ .../DashboardBlazeCardCellViewModel.swift | 39 +++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 12 ++++-- 3 files changed, 48 insertions(+), 40 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift index c562113bed0c..d3cd35c4a692 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift @@ -53,43 +53,6 @@ final class DashboardBlazeCardCell: DashboardCollectionViewCell { } } -final class DashboardBlazeCardCellViewModel { - private(set) var lastestCampaign: BlazeCampaign? - - private let blog: Blog - private let service = BlazeService() - private var isRefreshing = false - - var onRefreshNeeded: ((DashboardBlazeCardCellViewModel) -> Void)? - - init(blog: Blog) { - self.blog = blog - - NotificationCenter.default.addObserver(self, selector: #selector(refresh), name: .blazeCampaignCreated, object: nil) - } - - @objc func refresh() { - guard RemoteFeatureFlag.blazeManageCampaigns.enabled() else { - return // Continue showing the default `Promo` card - } - - guard !isRefreshing, let service else { return } - isRefreshing = true - - service.getRecentCampaigns(for: blog) { [weak self] in - self?.didRefresh(with: $0) - } - } - - private func didRefresh(with result: Result) { - isRefreshing = false - if case .success(let response) = result { - lastestCampaign = response.campaigns?.first - } - onRefreshNeeded?(self) - } -} - enum DashboardBlazeCardSubtype: String { case promo = "no_campaigns" case campaigns = "campaigns" diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift new file mode 100644 index 000000000000..6ceb56e5767b --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift @@ -0,0 +1,39 @@ +import Foundation +import WordPressKit + +final class DashboardBlazeCardCellViewModel { + private(set) var lastestCampaign: BlazeCampaign? + + private let blog: Blog + private let service = BlazeService() + private var isRefreshing = false + + var onRefreshNeeded: ((DashboardBlazeCardCellViewModel) -> Void)? + + init(blog: Blog) { + self.blog = blog + + NotificationCenter.default.addObserver(self, selector: #selector(refresh), name: .blazeCampaignCreated, object: nil) + } + + @objc func refresh() { + guard RemoteFeatureFlag.blazeManageCampaigns.enabled() else { + return // Continue showing the default `Promo` card + } + + guard !isRefreshing, let service else { return } + isRefreshing = true + + service.getRecentCampaigns(for: blog) { [weak self] in + self?.didRefresh(with: $0) + } + } + + private func didRefresh(with result: Result) { + isRefreshing = false + if case .success(let response) = result { + lastestCampaign = response.campaigns?.first + } + onRefreshNeeded?(self) + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index c0d8d4afe999..90e803827bf7 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -351,6 +351,8 @@ 0C391E622A3002950040EA91 /* BlazeCampaignStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391E602A3002950040EA91 /* BlazeCampaignStatusView.swift */; }; 0C391E642A312DB20040EA91 /* BlazeCampaignViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */; }; 0C63266F2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C63266E2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift */; }; + 0C71959B2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; + 0C71959C2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; 0C7E09202A4286A00052324C /* PostMetaButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E091F2A4286A00052324C /* PostMetaButton.m */; }; 0C7E09212A4286A00052324C /* PostMetaButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E091F2A4286A00052324C /* PostMetaButton.m */; }; 0C7E09242A4286F40052324C /* PostMetaButton+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */; }; @@ -362,8 +364,6 @@ 0C896DE42A3A7BDC00D7D4E7 /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DDF2A3A763400D7D4E7 /* SettingsCell.swift */; }; 0C896DE52A3A7C1F00D7D4E7 /* SiteVisibility+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */; }; 0C896DE72A3A832B00D7D4E7 /* SiteVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DE62A3A832B00D7D4E7 /* SiteVisibilityTests.swift */; }; - 0C71959B2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; - 0C71959C2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; 0CB4056B29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */; }; 0CB4056C29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */; }; 0CB4056E29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */; }; @@ -373,6 +373,8 @@ 0CB4057A29C8DDEE008EED0A /* BlogDashboardPersonalizationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057029C8DCF4008EED0A /* BlogDashboardPersonalizationViewModel.swift */; }; 0CB4057D29C8DF83008EED0A /* BlogDashboardPersonalizeCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */; }; 0CB4057E29C8DF84008EED0A /* BlogDashboardPersonalizeCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */; }; + 0CD382832A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD382822A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift */; }; + 0CD382842A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD382822A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift */; }; 0CDEC40C2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDEC40B2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift */; }; 0CDEC40D2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDEC40B2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift */; }; 1702BBDC1CEDEA6B00766A33 /* BadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1702BBDB1CEDEA6B00766A33 /* BadgeLabel.swift */; }; @@ -6052,6 +6054,7 @@ 0C391E602A3002950040EA91 /* BlazeCampaignStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignStatusView.swift; sourceTree = ""; }; 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignViewModelTests.swift; sourceTree = ""; }; 0C63266E2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergFilesAppMediaSourceTests.swift; sourceTree = ""; }; + 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingsRelatedPostsView.swift; 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 = ""; }; 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostMetaButton+Swift.swift"; sourceTree = ""; }; @@ -6059,12 +6062,12 @@ 0C896DDF2A3A763400D7D4E7 /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteVisibility+Extensions.swift"; sourceTree = ""; }; 0C896DE62A3A832B00D7D4E7 /* SiteVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteVisibilityTests.swift; sourceTree = ""; }; - 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingsRelatedPostsView.swift; sourceTree = ""; }; 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationService.swift; sourceTree = ""; }; 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationServiceTests.swift; sourceTree = ""; }; 0CB4057029C8DCF4008EED0A /* BlogDashboardPersonalizationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationViewModel.swift; sourceTree = ""; }; 0CB4057229C8DD01008EED0A /* BlogDashboardPersonalizationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationView.swift; sourceTree = ""; }; 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizeCardCell.swift; sourceTree = ""; }; + 0CD382822A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardBlazeCardCellViewModel.swift; sourceTree = ""; }; 0CDEC40B2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardBlazeCampaignsCardView.swift; sourceTree = ""; }; 131D0EE49695795ECEDAA446 /* Pods-WordPressTest.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressTest.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressTest/Pods-WordPressTest.release-alpha.xcconfig"; sourceTree = ""; }; 150B6590614A28DF9AD25491 /* Pods-Apps-Jetpack.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Apps-Jetpack.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-Apps-Jetpack/Pods-Apps-Jetpack.release-alpha.xcconfig"; sourceTree = ""; }; @@ -17801,6 +17804,7 @@ isa = PBXGroup; children = ( FA98B61529A3B76A0071AAE8 /* DashboardBlazeCardCell.swift */, + 0CD382822A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift */, FA98B61829A3BF050071AAE8 /* DashboardBlazePromoCardView.swift */, 0CDEC40B2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift */, 0C391E5D2A2FE5350040EA91 /* DashboardBlazeCampaignView.swift */, @@ -22033,6 +22037,7 @@ 37EAAF4D1A11799A006D6306 /* CircularImageView.swift in Sources */, 837966A2299E9C85004A92B9 /* JetpackInstallPluginHelper.swift in Sources */, 3FD0316F24201E08005C0993 /* GravatarButtonView.swift in Sources */, + 0CD382832A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift in Sources */, 7EBB4126206C388100012D98 /* StockPhotosService.swift in Sources */, E17FEADA221494B2006E1D2D /* Blog+Analytics.swift in Sources */, E69551F61B8B6AE200CB8E4F /* ReaderStreamViewController+Helper.swift in Sources */, @@ -25564,6 +25569,7 @@ FABB26182602FC2C00C8785C /* RegisterDomainDetailsViewModel+SectionDefinitions.swift in Sources */, FABB26192602FC2C00C8785C /* ActionRow.swift in Sources */, C395FB272822148400AE7C11 /* RemoteSiteDesign+Thumbnail.swift in Sources */, + 0CD382842A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift in Sources */, 9815D0B426B49A0600DF7226 /* Comment+CoreDataProperties.swift in Sources */, FABB261A2602FC2C00C8785C /* AppSettingsViewController.swift in Sources */, 4A82C43228D321A300486CFF /* Blog+Post.swift in Sources */, From 0857eeade5a758c1d6e345ff482e441c0e1955fb Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 15:26:48 -0400 Subject: [PATCH 049/175] Add blaze campaign cache on dashboard --- .../Cards/Blaze/DashboardBlazeCardCell.swift | 2 +- .../DashboardBlazeCardCellViewModel.swift | 23 +++++++++++++--- .../Service/BlogDashboardPersistence.swift | 27 +++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift index d3cd35c4a692..2ed4354525cc 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift @@ -17,7 +17,7 @@ final class DashboardBlazeCardCell: DashboardCollectionViewCell { guard viewModel !== self.viewModel else { return } self.viewModel = viewModel - viewModel.onRefreshNeeded = { [weak self] in + viewModel.onRefresh = { [weak self] in self?.update(with: $0) self?.viewController?.collectionView.collectionViewLayout.invalidateLayout() } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift index 6ceb56e5767b..4ee758abddca 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift @@ -5,13 +5,22 @@ final class DashboardBlazeCardCellViewModel { private(set) var lastestCampaign: BlazeCampaign? private let blog: Blog + private let store: DashboardBlazeStoreProtocol private let service = BlazeService() private var isRefreshing = false - var onRefreshNeeded: ((DashboardBlazeCardCellViewModel) -> Void)? + var onRefresh: ((DashboardBlazeCardCellViewModel) -> Void)? - init(blog: Blog) { + init(blog: Blog, + store: DashboardBlazeStoreProtocol = BlogDashboardPersistence()) { self.blog = blog + self.store = store + + if RemoteFeatureFlag.blazeManageCampaigns.enabled() { + self.lastestCampaign = blog.dotComID.flatMap { + store.getBlazeCampaign(forBlogID: $0.intValue) + } + } NotificationCenter.default.addObserver(self, selector: #selector(refresh), name: .blazeCampaignCreated, object: nil) } @@ -33,7 +42,15 @@ final class DashboardBlazeCardCellViewModel { isRefreshing = false if case .success(let response) = result { lastestCampaign = response.campaigns?.first + if let campaign = response.campaigns?.first, let blogID = blog.dotComID?.intValue { + store.storeBlazeCampaign(campaign, forBlogID: blogID) + } } - onRefreshNeeded?(self) + onRefresh?(self) } } + +protocol DashboardBlazeStoreProtocol { + func getBlazeCampaign(forBlogID blogID: Int) -> BlazeCampaign? + func storeBlazeCampaign(_ campaign: BlazeCampaign, forBlogID blogID: Int) +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersistence.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersistence.swift index 950911e7c18e..a61ffa8ec808 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersistence.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersistence.swift @@ -27,3 +27,30 @@ class BlogDashboardPersistence { "cards_\(blogID).json" } } + +extension BlogDashboardPersistence: DashboardBlazeStoreProtocol { + func getBlazeCampaign(forBlogID blogID: Int) -> BlazeCampaign? { + do { + let url = try makeBlazeCampaignURL(for: blogID) + let data = try Data(contentsOf: url) + return try JSONDecoder().decode(BlazeCampaign.self, from: data) + } catch { + DDLogError("Failed to retrieve blaze campaign: \(error)") + return nil + } + } + + func storeBlazeCampaign(_ campaign: BlazeCampaign, forBlogID blogID: Int) { + do { + let url = try makeBlazeCampaignURL(for: blogID) + try JSONEncoder().encode(campaign).write(to: url) + } catch { + DDLogError("Failed to store blaze campaign: \(error)") + } + } + + private func makeBlazeCampaignURL(for blogID: Int) throws -> URL { + try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false) + .appendingPathComponent("recent_blaze_campaign_\(blogID).json", isDirectory: false) + } +} From b1adf3abb299fef84b384a40fa1f753b87b3b622 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 15:28:04 -0400 Subject: [PATCH 050/175] Update Blaze campaign mocks to include ui_status --- .../Blaze Campaigns/BlazeCampaignsViewController.swift | 3 +++ .../WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift | 1 + 2 files changed, 4 insertions(+) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index ccb5b44f4d8e..0b6ba363d968 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -202,6 +202,7 @@ private let mockResponse: BlazeCampaignsSearchResponse = { "start_date": "2023-06-13T00:00:00Z", "end_date": "2023-06-01T19:15:45Z", "status": "finished", + "ui_status": "finished", "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", "budget_cents": 500, "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", @@ -222,6 +223,7 @@ private let mockResponse: BlazeCampaignsSearchResponse = { "start_date": "2023-06-13T00:00:00Z", "end_date": "2023-06-01T19:15:45Z", "status": "rejected", + "ui_status": "rejected", "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", "budget_cents": 5000, "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", @@ -242,6 +244,7 @@ private let mockResponse: BlazeCampaignsSearchResponse = { "start_date": "2023-06-13T00:00:00Z", "end_date": "2023-06-01T19:15:45Z", "status": "active", + "ui_status": "active", "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", "budget_cents": 1000, "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", diff --git a/WordPress/WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift b/WordPress/WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift index ce1fd73c4b62..6c9651a91d22 100644 --- a/WordPress/WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift +++ b/WordPress/WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift @@ -29,6 +29,7 @@ private let campaign: BlazeCampaign = { "start_date": "2023-06-13T00:00:00Z", "end_date": "2023-06-01T19:15:45Z", "status": "finished", + "ui_status": "finished", "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", "budget_cents": 500, "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", From 766fb817c151832ac80afdfa85e704b8db8eaa40 Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Tue, 27 Jun 2023 15:53:16 -0400 Subject: [PATCH 051/175] Add tap actions to the no connections social view on the post settings --- ...SettingsViewController+JetpackSocial.swift | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift index 9b31b87bd7a9..02ea5050796d 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift @@ -3,11 +3,13 @@ extension PostSettingsViewController { @objc func showNoConnection() -> Bool { let isJetpackSocialEnabled = FeatureFlag.jetpackSocial.enabled + let isNoConnectionViewHidden = UserPersistentStoreFactory.instance().bool(forKey: Constants.hideNoConnectionViewKey) let blogSupportsPublicize = apost.blog.supportsPublicize() let blogHasNoConnections = publicizeConnections.count == 0 let blogHasServices = availableServices().count > 0 return isJetpackSocialEnabled + && !isNoConnectionViewHidden && blogSupportsPublicize && blogHasNoConnections && blogHasServices @@ -15,16 +17,9 @@ extension PostSettingsViewController { @objc func createNoConnectionView() -> UIView { let services = availableServices() - let viewModel = JetpackSocialNoConnectionViewModel( - services: services, - onConnectTap: { - // TODO: Open the social screen - print("Connect tap") - }, onNotNowTap: { - // TODO: Add condition to hide the connection view after not now is tapped - print("Not now tap") - } - ) + let viewModel = JetpackSocialNoConnectionViewModel(services: services, + onConnectTap: onConnectTap(), + onNotNowTap: onNotNowTap()) let viewController = JetpackSocialNoConnectionView.createHostController(with: viewModel) // Returning just the view means the view controller will deallocate but we don't need a @@ -32,10 +27,43 @@ extension PostSettingsViewController { return viewController.view } + private func onConnectTap() -> () -> Void { + return { [weak self] in + guard let blog = self?.apost.blog, + let controller = SharingViewController(blog: blog, delegate: self) else { + return + } + self?.navigationController?.pushViewController(controller, animated: true) + } + } + + private func onNotNowTap() -> () -> Void { + return { [weak self] in + UserPersistentStoreFactory.instance().set(true, forKey: Constants.hideNoConnectionViewKey) + self?.tableView.reloadData() + } + } + private func availableServices() -> [PublicizeService] { let context = apost.managedObjectContext ?? ContextManager.shared.mainContext let services = try? PublicizeService.allPublicizeServices(in: context) return services ?? [] } + // MARK: - Constants + + private struct Constants { + static let hideNoConnectionViewKey = "post-settings-social-no-connection-view-hidden" + } + +} + +// MARK: - SharingViewControllerDelegate + +extension PostSettingsViewController: SharingViewControllerDelegate { + + public func didChangePublicizeServices() { + tableView.reloadData() + } + } From 3ae9bb798449c277588dd7ba3370c52088adee3a Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 16:16:53 -0400 Subject: [PATCH 052/175] Add DashboardBlazeCardCellViewModel unit tests --- WordPress/Classes/Services/BlazeService.swift | 5 +- .../DashboardBlazeCampaignsCardView.swift | 5 +- .../Cards/Blaze/DashboardBlazeCardCell.swift | 11 +- .../DashboardBlazeCardCellViewModel.swift | 43 +++-- .../Service/BlogDashboardPersistence.swift | 8 +- WordPress/WordPress.xcodeproj/project.pbxproj | 4 + .../DashboardBlazeCardCellViewModelTest.swift | 154 ++++++++++++++++++ 7 files changed, 206 insertions(+), 24 deletions(-) create mode 100644 WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift diff --git a/WordPress/Classes/Services/BlazeService.swift b/WordPress/Classes/Services/BlazeService.swift index 0e56e3160cc4..987873304fe0 100644 --- a/WordPress/Classes/Services/BlazeService.swift +++ b/WordPress/Classes/Services/BlazeService.swift @@ -1,8 +1,11 @@ import Foundation import WordPressKit -@objc final class BlazeService: NSObject { +protocol BlazeServiceProtocol { + func getRecentCampaigns(for blog: Blog, completion: @escaping (Result) -> Void) +} +@objc final class BlazeService: NSObject, BlazeServiceProtocol { private let contextManager: CoreDataStackSwift private let remote: BlazeServiceRemote diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift index a35a9343b188..a80debbd3238 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift @@ -88,7 +88,7 @@ final class DashboardBlazeCampaignsCardView: UIView { BlazeFlowCoordinator.presentBlaze(in: presentingViewController, source: .dashboardCard, blog: blog) } - func configure(blog: Blog, viewController: BlogDashboardViewController?, campaign: BlazeCampaign) { + func configure(blog: Blog, viewController: BlogDashboardViewController?, campaign: BlazeCampaignViewModel) { self.blog = blog self.presentingViewController = viewController @@ -101,8 +101,7 @@ final class DashboardBlazeCampaignsCardView: UIView { ]) ], card: .blaze) - let viewModel = BlazeCampaignViewModel(campaign: campaign) - campaignView.configure(with: viewModel, blog: blog) + campaignView.configure(with: campaign, blog: blog) } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift index 2ed4354525cc..35a47c94fb16 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCell.swift @@ -27,15 +27,14 @@ final class DashboardBlazeCardCell: DashboardCollectionViewCell { private func update(with viewModel: DashboardBlazeCardCellViewModel) { guard let blog, let viewController else { return } - if let campaign = viewModel.lastestCampaign { - // Display campaigns + switch viewModel.state { + case .promo: + let cardView = DashboardBlazePromoCardView(.make(with: blog, viewController: viewController)) + self.setCardView(cardView, subtype: .promo) + case .campaign(let campaign): let cardView = DashboardBlazeCampaignsCardView() cardView.configure(blog: blog, viewController: viewController, campaign: campaign) self.setCardView(cardView, subtype: .campaigns) - } else { - // Display promo - let cardView = DashboardBlazePromoCardView(.make(with: blog, viewController: viewController)) - self.setCardView(cardView, subtype: .promo) } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift index 4ee758abddca..e119f0bdc798 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift @@ -2,31 +2,43 @@ import Foundation import WordPressKit final class DashboardBlazeCardCellViewModel { - private(set) var lastestCampaign: BlazeCampaign? + private(set) var state: State = .promo private let blog: Blog + private let service: BlazeServiceProtocol? private let store: DashboardBlazeStoreProtocol - private let service = BlazeService() private var isRefreshing = false + private let isBlazeCampaignsFlagEnabled: Bool + + enum State { + /// Showing "Promote you content with Blaze" promo card. + case promo + /// Showing the latest Blaze campaign. + case campaign(BlazeCampaignViewModel) + } var onRefresh: ((DashboardBlazeCardCellViewModel) -> Void)? init(blog: Blog, - store: DashboardBlazeStoreProtocol = BlogDashboardPersistence()) { + service: BlazeServiceProtocol? = BlazeService(), + store: DashboardBlazeStoreProtocol = BlogDashboardPersistence(), + isBlazeCampaignsFlagEnabled: Bool = RemoteFeatureFlag.blazeManageCampaigns.enabled()) { self.blog = blog + self.service = service self.store = store + self.isBlazeCampaignsFlagEnabled = isBlazeCampaignsFlagEnabled - if RemoteFeatureFlag.blazeManageCampaigns.enabled() { - self.lastestCampaign = blog.dotComID.flatMap { - store.getBlazeCampaign(forBlogID: $0.intValue) - } + if isBlazeCampaignsFlagEnabled, + let blogID = blog.dotComID?.intValue, + let campaign = store.getBlazeCampaign(forBlogID: blogID) { + self.state = .campaign(BlazeCampaignViewModel(campaign: campaign)) } NotificationCenter.default.addObserver(self, selector: #selector(refresh), name: .blazeCampaignCreated, object: nil) } @objc func refresh() { - guard RemoteFeatureFlag.blazeManageCampaigns.enabled() else { + guard isBlazeCampaignsFlagEnabled else { return // Continue showing the default `Promo` card } @@ -41,9 +53,16 @@ final class DashboardBlazeCardCellViewModel { private func didRefresh(with result: Result) { isRefreshing = false if case .success(let response) = result { - lastestCampaign = response.campaigns?.first - if let campaign = response.campaigns?.first, let blogID = blog.dotComID?.intValue { - store.storeBlazeCampaign(campaign, forBlogID: blogID) + if let campaign = response.campaigns?.first { + self.state = .campaign(BlazeCampaignViewModel(campaign: campaign)) + if let blogID = blog.dotComID?.intValue { + store.setBlazeCampaign(campaign, forBlogID: blogID) + } + } else { + if let blogID = blog.dotComID?.intValue { + store.setBlazeCampaign(nil, forBlogID: blogID) + } + self.state = .promo } } onRefresh?(self) @@ -52,5 +71,5 @@ final class DashboardBlazeCardCellViewModel { protocol DashboardBlazeStoreProtocol { func getBlazeCampaign(forBlogID blogID: Int) -> BlazeCampaign? - func storeBlazeCampaign(_ campaign: BlazeCampaign, forBlogID blogID: Int) + func setBlazeCampaign(_ campaign: BlazeCampaign?, forBlogID blogID: Int) } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersistence.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersistence.swift index a61ffa8ec808..1b9712fb1480 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersistence.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersistence.swift @@ -40,10 +40,14 @@ extension BlogDashboardPersistence: DashboardBlazeStoreProtocol { } } - func storeBlazeCampaign(_ campaign: BlazeCampaign, forBlogID blogID: Int) { + func setBlazeCampaign(_ campaign: BlazeCampaign?, forBlogID blogID: Int) { do { let url = try makeBlazeCampaignURL(for: blogID) - try JSONEncoder().encode(campaign).write(to: url) + if let campaign { + try JSONEncoder().encode(campaign).write(to: url) + } else { + try? FileManager.default.removeItem(at: url) + } } catch { DDLogError("Failed to store blaze campaign: \(error)") } diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 90e803827bf7..98e166382cb6 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -375,6 +375,7 @@ 0CB4057E29C8DF84008EED0A /* BlogDashboardPersonalizeCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */; }; 0CD382832A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD382822A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift */; }; 0CD382842A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD382822A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift */; }; + 0CD382862A4B6FCF00612173 /* DashboardBlazeCardCellViewModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD382852A4B6FCE00612173 /* DashboardBlazeCardCellViewModelTest.swift */; }; 0CDEC40C2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDEC40B2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift */; }; 0CDEC40D2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDEC40B2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift */; }; 1702BBDC1CEDEA6B00766A33 /* BadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1702BBDB1CEDEA6B00766A33 /* BadgeLabel.swift */; }; @@ -6068,6 +6069,7 @@ 0CB4057229C8DD01008EED0A /* BlogDashboardPersonalizationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationView.swift; sourceTree = ""; }; 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizeCardCell.swift; sourceTree = ""; }; 0CD382822A4B699E00612173 /* DashboardBlazeCardCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardBlazeCardCellViewModel.swift; sourceTree = ""; }; + 0CD382852A4B6FCE00612173 /* DashboardBlazeCardCellViewModelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardBlazeCardCellViewModelTest.swift; sourceTree = ""; }; 0CDEC40B2A2FAF0500BB3A91 /* DashboardBlazeCampaignsCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardBlazeCampaignsCardView.swift; sourceTree = ""; }; 131D0EE49695795ECEDAA446 /* Pods-WordPressTest.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressTest.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressTest/Pods-WordPressTest.release-alpha.xcconfig"; sourceTree = ""; }; 150B6590614A28DF9AD25491 /* Pods-Apps-Jetpack.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Apps-Jetpack.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-Apps-Jetpack/Pods-Apps-Jetpack.release-alpha.xcconfig"; sourceTree = ""; }; @@ -13695,6 +13697,7 @@ 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */, 011F52C52A15413800B04114 /* FreeToPaidPlansDashboardCardHelperTests.swift */, FA3FBF8D2A2777E00012FC90 /* DashboardActivityLogViewModelTests.swift */, + 0CD382852A4B6FCE00612173 /* DashboardBlazeCardCellViewModelTest.swift */, 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */, ); path = Dashboard; @@ -23581,6 +23584,7 @@ E6843840221F5A2200752258 /* PostListExcessiveLoadMoreTests.swift in Sources */, 325D3B3D23A8376400766DF6 /* FullScreenCommentReplyViewControllerTests.swift in Sources */, 805CC0B9296680F7002941DC /* RemoteConfigStoreMock.swift in Sources */, + 0CD382862A4B6FCF00612173 /* DashboardBlazeCardCellViewModelTest.swift in Sources */, 0147D651294B6EA600AA6410 /* StatsRevampStoreTests.swift in Sources */, 57D6C83E22945A10003DDC7E /* PostCompactCellTests.swift in Sources */, B030FE0A27EBF0BC000F6F5E /* SiteCreationIntentTracksEventTests.swift in Sources */, diff --git a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift new file mode 100644 index 000000000000..82dbb63ce149 --- /dev/null +++ b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift @@ -0,0 +1,154 @@ +import XCTest + +@testable import WordPress + +final class DashboardBlazeCardCellViewModelTest: CoreDataTestCase { + private let service = MockBlazeService() + private let store = MockDashboardBlazeStore() + private var blog: Blog! + private var sut: DashboardBlazeCardCellViewModel! + private var isBlazeCampaignsFlagEnabled = true + + override func setUp() { + super.setUp() + + blog = ModelTestHelper.insertDotComBlog(context: mainContext) + blog.dotComID = 1 + + createSUT() + } + + private func createSUT() { + sut = DashboardBlazeCardCellViewModel( + blog: blog, + service: service, + store: store, + isBlazeCampaignsFlagEnabled: isBlazeCampaignsFlagEnabled + ) + } + + func testInitialState() { + switch sut.state { + case .promo: + break // Expected + case .campaign: + XCTFail("The card should show promo before the app fetches the data") + } + } + + func testCampaignRefresh() { + let expectation = self.expectation(description: "didRefresh") + sut.onRefresh = { _ in + expectation.fulfill() + } + + // When + sut.refresh() + XCTAssertTrue(service.didPerformRequest) + wait(for: [expectation], timeout: 1) + + // Then + switch sut.state { + case .promo: + XCTFail("The card should display the latest campaign") + case .campaign(let viewModel): + XCTAssertEqual(viewModel.title, "Test Post - don't approve") + } + } + + func testThatCampaignIsCached() { + // Given + let expectation = self.expectation(description: "didRefresh") + sut.onRefresh = { _ in expectation.fulfill() } + sut.refresh() + wait(for: [expectation], timeout: 1) + + // When the ViewModel is re-created + createSUT() + + // Then it shows the cached campaign + switch sut.state { + case .promo: + XCTFail("The card should display the latest campaign") + case .campaign(let viewModel): + XCTAssertEqual(viewModel.title, "Test Post - don't approve") + } + } + + func testThatNoRequestsAreMadeWhenFlagDisabled() { + // Given + isBlazeCampaignsFlagEnabled = false + createSUT() + + // When + sut.refresh() + + // Then + XCTAssertFalse(service.didPerformRequest) + + // Then still shows promo + switch sut.state { + case .promo: + break // Expected + case .campaign: + XCTFail("The card should show promo before the app fetches the data") + } + } +} + +private final class MockBlazeService: BlazeServiceProtocol { + var didPerformRequest = false + + func getRecentCampaigns(for blog: Blog, completion: @escaping (Result) -> Void) { + didPerformRequest = true + DispatchQueue.main.async { + completion(.success(response)) + } + } +} + +private final class MockDashboardBlazeStore: DashboardBlazeStoreProtocol { + private var campaigns: [Int: BlazeCampaign] = [:] + + func getBlazeCampaign(forBlogID blogID: Int) -> BlazeCampaign? { + campaigns[blogID] + } + + func setBlazeCampaign(_ campaign: BlazeCampaign?, forBlogID blogID: Int) { + campaigns[blogID] = campaign + } +} + +private let response: BlazeCampaignsSearchResponse = { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .iso8601 + return try! decoder.decode(BlazeCampaignsSearchResponse.self, from: """ + { + "totalItems": 3, + "campaigns": [ + { + "campaign_id": 26916, + "name": "Test Post - don't approve", + "start_date": "2023-06-13T00:00:00Z", + "end_date": "2023-06-01T19:15:45Z", + "status": "finished", + "ui_status": "finished", + "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", + "budget_cents": 500, + "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", + "content_config": { + "title": "Test Post - don't approve", + "snippet": "Test Post Empty Empty", + "clickUrl": "https://alextest9123.wordpress.com/2023/06/01/test-post/", + "imageUrl": "https://i0.wp.com/public-api.wordpress.com/wpcom/v2/wordads/dsp/api/v1/dsp/creatives/56259/image?w=600&zoom=2" + }, + "campaign_stats": { + "impressions_total": 1000, + "clicks_total": 235 + } + } + ] + } + """.data(using: .utf8)!) +}() From 84c91c7c0ab5f6831453a9439bba95b212ac370c Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 16:21:05 -0400 Subject: [PATCH 053/175] Fix an issue where stats view was not removed on reload --- .../Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift index 202df4cfce06..ea8261f58283 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift @@ -69,8 +69,8 @@ final class DashboardBlazeCampaignView: UIView { } statsView.isHidden = !viewModel.isShowingStats - if viewModel.isShowingStats { statsView.arrangedSubviews.forEach { $0.removeFromSuperview() } + if viewModel.isShowingStats { makeStatsViews(for: viewModel).forEach(statsView.addArrangedSubview) } } From 9e9a59cf3e366e44d92b210ebfb411c0bd0909f6 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 16:28:48 -0400 Subject: [PATCH 054/175] Update campaign stats to use ui_status --- .../Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift index ea8261f58283..84ba63790dac 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift @@ -106,7 +106,7 @@ struct BlazeCampaignViewModel { var status: BlazeCampaignStatusViewModel { .init(campaign: campaign) } var isShowingStats: Bool { - switch campaign.status { + switch campaign.uiStatus { case .created, .processing, .canceled, .approved, .rejected, .scheduled, .unknown: return false case .active, .finished: From a54c7763e0f7ee089d781aaed7c505e034abbdd2 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 27 Jun 2023 16:33:52 -0400 Subject: [PATCH 055/175] Cleanup DashboardBlazeCardCellViewModel/didRefresh --- .../DashboardBlazeCardCellViewModel.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift index e119f0bdc798..e8f02acf0973 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift @@ -51,20 +51,19 @@ final class DashboardBlazeCardCellViewModel { } private func didRefresh(with result: Result) { - isRefreshing = false if case .success(let response) = result { - if let campaign = response.campaigns?.first { - self.state = .campaign(BlazeCampaignViewModel(campaign: campaign)) - if let blogID = blog.dotComID?.intValue { - store.setBlazeCampaign(campaign, forBlogID: blogID) - } + let campaign = response.campaigns?.first + if let blogID = blog.dotComID?.intValue { + store.setBlazeCampaign(campaign, forBlogID: blogID) + } + if let campaign { + state = .campaign(BlazeCampaignViewModel(campaign: campaign)) } else { - if let blogID = blog.dotComID?.intValue { - store.setBlazeCampaign(nil, forBlogID: blogID) - } - self.state = .promo + state = .promo } } + + isRefreshing = false onRefresh?(self) } } From 8224bba762a536bcd1287ebaec1691b33f9658a9 Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Wed, 28 Jun 2023 10:22:00 +0800 Subject: [PATCH 056/175] move test assets to test level, updated predicates --- .../UITests/Tests/EditorGutenbergTests.swift | 6 ++++-- .../Screens/Editor/BlockEditorScreen.swift | 17 +++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/WordPress/UITests/Tests/EditorGutenbergTests.swift b/WordPress/UITests/Tests/EditorGutenbergTests.swift index d127324fbc61..0dabfb3c009d 100644 --- a/WordPress/UITests/Tests/EditorGutenbergTests.swift +++ b/WordPress/UITests/Tests/EditorGutenbergTests.swift @@ -21,6 +21,8 @@ class EditorGutenbergTests: XCTestCase { let title = "Rich post title" let content = "Some text, and more text" + let videoUrlPath = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" + let audioUrlPath = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" func testTextPostPublish() throws { @@ -80,8 +82,8 @@ class EditorGutenbergTests: XCTestCase { func testAddMediaBlocks() throws { try BlockEditorScreen() .addImage() - .addVideo() - .addAudio() + .addVideoFromUrl(urlPath: videoUrlPath) + .addAudioFromUrl(urlPath: audioUrlPath) .verifyMediaBlocksDisplayed() } } diff --git a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift index 36af0cb19c34..e1808fe8ba66 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift @@ -30,9 +30,6 @@ public class BlockEditorScreen: ScreenObject { var insertFromUrlButton: XCUIElement { insertFromUrlButtonGetter(app) } var applyButton: XCUIElement { applyButtonGetter(app) } - var videoUrlPath = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" - var audioUrlPath = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" - public init(app: XCUIApplication = XCUIApplication()) throws { // The block editor has _many_ elements but most are loaded on-demand. To verify the screen // is loaded, we rely only on the button to add a new block and on the navigation bar we @@ -91,19 +88,19 @@ public class BlockEditorScreen: ScreenObject { return self } - public func addVideo() -> Self { + public func addVideoFromUrl(urlPath: String) -> Self { addMediaBlockFromUrl( blockType: "Video block", - UrlPath: videoUrlPath + UrlPath: urlPath ) return self } - public func addAudio() -> Self { + public func addAudioFromUrl(urlPath: String) -> Self { addMediaBlockFromUrl( blockType: "Audio block", - UrlPath: audioUrlPath + UrlPath: urlPath ) return self @@ -118,9 +115,9 @@ public class BlockEditorScreen: ScreenObject { @discardableResult public func verifyMediaBlocksDisplayed() -> Self { - let imagePredicate = NSPredicate(format: "label CONTAINS[c] 'Image block'") - let videoPredicate = NSPredicate(format: "label CONTAINS[c] 'Video block'") - let audioPredicate = NSPredicate(format: "label CONTAINS[c] 'Audio block'") + let imagePredicate = NSPredicate(format: "label == 'Image Block. Row 1'") + let videoPredicate = NSPredicate(format: "label == 'Video Block. Row 2'") + let audioPredicate = NSPredicate(format: "label == 'Audio Block. Row 3'") XCTAssertTrue(app.buttons.containing(imagePredicate).firstMatch.exists) XCTAssertTrue(app.buttons.containing(videoPredicate).firstMatch.exists) From 96bcab62d4ea04e7649941478586ffc50e128415 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 28 Jun 2023 16:40:24 +1000 Subject: [PATCH 057/175] Use Xcode 14.3.1 in CI --- .buildkite/cache-builder.yml | 2 +- .buildkite/pipeline.yml | 2 +- .buildkite/release-builds.yml | 2 +- .xcode-version | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.buildkite/cache-builder.yml b/.buildkite/cache-builder.yml index 0b44a369a583..3e11fb963796 100644 --- a/.buildkite/cache-builder.yml +++ b/.buildkite/cache-builder.yml @@ -14,7 +14,7 @@ common_params: # Common environment values to use with the `env` key. - &common_env # Be sure to also update the `.xcode-version` file when updating the Xcode image/version here - IMAGE_ID: xcode-14.2 + IMAGE_ID: xcode-14.3.1 steps: diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 0cf2db6a8a2e..2db9e6d19145 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -9,7 +9,7 @@ common_params: # Common environment values to use with the `env` key. - &common_env # Be sure to also update the `.xcode-version` file when updating the Xcode image/version here - IMAGE_ID: xcode-14.2 + IMAGE_ID: xcode-14.3.1 # This is the default pipeline – it will build and test the app steps: diff --git a/.buildkite/release-builds.yml b/.buildkite/release-builds.yml index 120d158d6dc8..1003df4508be 100644 --- a/.buildkite/release-builds.yml +++ b/.buildkite/release-builds.yml @@ -11,7 +11,7 @@ common_params: # Common environment values to use with the `env` key. - &common_env # Be sure to also update the `.xcode-version` file when updating the Xcode image/version here - IMAGE_ID: xcode-14.2 + IMAGE_ID: xcode-14.3.1 steps: diff --git a/.xcode-version b/.xcode-version index 6b5bab0678ab..6dfe8b1298c0 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -14.2 +14.3.1 From 9ed27f35c50e3bb92e4ca7c3ac03262ad60d0c26 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:26:25 +0700 Subject: [PATCH 058/175] Update WordPressKit to 8.5.0-beta.2 --- Podfile | 2 +- Podfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Podfile b/Podfile index a3b0285d7bae..e3bc85766801 100644 --- a/Podfile +++ b/Podfile @@ -50,7 +50,7 @@ def wordpress_ui end def wordpress_kit - pod 'WordPressKit', '~> 8.4-beta' + pod 'WordPressKit', '~> 8.5-beta' # 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', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '' diff --git a/Podfile.lock b/Podfile.lock index 606137ac1bd2..d9a03e87fc47 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -512,7 +512,7 @@ PODS: - WordPressKit (~> 8.0-beta) - WordPressShared (~> 2.1-beta) - WordPressUI (~> 1.7-beta) - - WordPressKit (8.4.0): + - WordPressKit (8.5.0-beta.2): - Alamofire (~> 4.8.0) - NSObject-SafeExpectations (~> 0.0.4) - UIDeviceIdentifier (~> 2.0) @@ -612,7 +612,7 @@ DEPENDENCIES: - SwiftLint (~> 0.50) - WordPress-Editor-iOS (~> 1.19.8) - WordPressAuthenticator (~> 6.1-beta) - - WordPressKit (~> 8.4-beta) + - WordPressKit (~> 8.5-beta) - WordPressShared (~> 2.2-beta) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) @@ -623,7 +623,6 @@ DEPENDENCIES: SPEC REPOS: https://github.com/wordpress-mobile/cocoapods-specs.git: - WordPressAuthenticator - - WordPressKit trunk: - Alamofire - AlamofireImage @@ -662,6 +661,7 @@ SPEC REPOS: - UIDeviceIdentifier - WordPress-Aztec-iOS - WordPress-Editor-iOS + - WordPressKit - WordPressShared - WordPressUI - WPMediaPicker @@ -880,7 +880,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 WordPress-Editor-iOS: 9eb9f12f21a5209cb837908d81ffe1e31cb27345 WordPressAuthenticator: b0b900696de5129a215adcd1e9ae6eb89da36ac8 - WordPressKit: f445e6fc3c63ddf611513a435408f86fdf3808b3 + WordPressKit: 31f5a9809b9c732e0da517967d8a94de725c15e6 WordPressShared: 87f3ee89b0a3e83106106f13a8b71605fb8eb6d2 WordPressUI: c5be816f6c7b3392224ac21de9e521e89fa108ac WPMediaPicker: 0d40b8d66b6dfdaa2d6a41e3be51249ff5898775 @@ -895,6 +895,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 -PODFILE CHECKSUM: 623e3fe64f67d2c0352e26d1ac2bcd58c06b3494 +PODFILE CHECKSUM: dd2923ba0655d06a0e85bdf9aa0d38304e8f087e COCOAPODS: 1.12.1 From 6ec6c9c15a2d081c90e092071877b752e0dc2e0e Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Wed, 28 Jun 2023 23:29:15 +0700 Subject: [PATCH 059/175] Add planActiveFeatures to Blog - Also parsed `planActiveFeatures` in BlogService. - Added a convenient Blog+JetpackSocial extension to check sharing limits. --- .../Classes/Models/Blog+JetpackSocial.swift | 31 +++++++++++++++++++ WordPress/Classes/Models/Blog.h | 1 + WordPress/Classes/Models/Blog.m | 1 + WordPress/Classes/Services/BlogService.m | 1 + .../WordPress 151.xcdatamodel/contents | 1 + WordPress/WordPress.xcodeproj/project.pbxproj | 6 ++++ 6 files changed, 41 insertions(+) create mode 100644 WordPress/Classes/Models/Blog+JetpackSocial.swift diff --git a/WordPress/Classes/Models/Blog+JetpackSocial.swift b/WordPress/Classes/Models/Blog+JetpackSocial.swift new file mode 100644 index 000000000000..18cb9ffb0a20 --- /dev/null +++ b/WordPress/Classes/Models/Blog+JetpackSocial.swift @@ -0,0 +1,31 @@ +import Foundation + +/// Blog extension for methods related to Jetpack Social. +extension Blog { + // MARK: - Publicize + + /// Whether the blog has Social auto-sharing limited. + /// Note that sites hosted at WP.com has no Social sharing limitations. + var isSocialSharingLimited: Bool { + let features = planActiveFeatures ?? [] + return !isHostedAtWPcom && !features.contains(Constants.socialSharingFeature) + } + + /// The auto-sharing limit information for the blog. + var sharingLimit: PublicizeInfo.SharingLimit? { + // For blogs with unlimited shares, return nil early. + // This is because the endpoint will still return sharing limits as if the blog doesn't have unlimited sharing. + guard isSocialSharingLimited else { + return nil + } + return publicizeInfo?.sharingLimit + } + + // MARK: - Private constants + + private enum Constants { + /// The feature key listed in the blog's plan's features. At the moment, `social-shares-1000` means unlimited + /// sharing, but in the future we might introduce a proper differentiation between 1000 and unlimited. + static let socialSharingFeature = "social-shares-1000" + } +} diff --git a/WordPress/Classes/Models/Blog.h b/WordPress/Classes/Models/Blog.h index f42e20f4f0d6..5e0a202659b9 100644 --- a/WordPress/Classes/Models/Blog.h +++ b/WordPress/Classes/Models/Blog.h @@ -164,6 +164,7 @@ typedef NS_ENUM(NSInteger, SiteVisibility) { @property (nonatomic, assign, readwrite) SiteVisibility siteVisibility; @property (nonatomic, strong, readwrite, nullable) NSNumber *planID; @property (nonatomic, strong, readwrite, nullable) NSString *planTitle; +@property (nonatomic, strong, readwrite, nullable) NSArray *planActiveFeatures; @property (nonatomic, assign, readwrite) BOOL hasPaidPlan; @property (nonatomic, strong, readwrite, nullable) NSSet *sharingButtons; @property (nonatomic, strong, readwrite, nullable) NSDictionary *capabilities; diff --git a/WordPress/Classes/Models/Blog.m b/WordPress/Classes/Models/Blog.m index e53e978e72e4..187a21f3b3dd 100644 --- a/WordPress/Classes/Models/Blog.m +++ b/WordPress/Classes/Models/Blog.m @@ -81,6 +81,7 @@ @implementation Blog @dynamic settings; @dynamic planID; @dynamic planTitle; +@dynamic planActiveFeatures; @dynamic hasPaidPlan; @dynamic sharingButtons; @dynamic capabilities; diff --git a/WordPress/Classes/Services/BlogService.m b/WordPress/Classes/Services/BlogService.m index 5065924ee101..c2cc9c29aba0 100644 --- a/WordPress/Classes/Services/BlogService.m +++ b/WordPress/Classes/Services/BlogService.m @@ -499,6 +499,7 @@ - (void)updateBlog:(Blog *)blog withRemoteBlog:(RemoteBlog *)remoteBlog blog.options = remoteBlog.options; blog.planID = remoteBlog.planID; blog.planTitle = remoteBlog.planTitle; + blog.planActiveFeatures = remoteBlog.planActiveFeatures; blog.hasPaidPlan = remoteBlog.hasPaidPlan; blog.quotaSpaceAllowed = remoteBlog.quotaSpaceAllowed; blog.quotaSpaceUsed = remoteBlog.quotaSpaceUsed; diff --git a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents index 80b80cc781af..6ec523b96192 100644 --- a/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents +++ b/WordPress/Classes/WordPress.xcdatamodeld/WordPress 151.xcdatamodel/contents @@ -171,6 +171,7 @@ + diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 6da87d27fe40..2c20e253c697 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -5586,6 +5586,8 @@ FEDA1AD9269D475D0038EC98 /* ListTableViewCell+Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEDA1AD7269D475D0038EC98 /* ListTableViewCell+Comments.swift */; }; FEDDD46F26A03DE900F8942B /* ListTableViewCell+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEDDD46E26A03DE900F8942B /* ListTableViewCell+Notifications.swift */; }; FEDDD47026A03DE900F8942B /* ListTableViewCell+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEDDD46E26A03DE900F8942B /* ListTableViewCell+Notifications.swift */; }; + FEE48EFC2A4C8312008A48E0 /* Blog+JetpackSocial.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE48EFB2A4C8312008A48E0 /* Blog+JetpackSocial.swift */; }; + FEE48EFD2A4C8312008A48E0 /* Blog+JetpackSocial.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE48EFB2A4C8312008A48E0 /* Blog+JetpackSocial.swift */; }; FEF4DC5528439357003806BE /* ReminderScheduleCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEF4DC5428439357003806BE /* ReminderScheduleCoordinator.swift */; }; FEF4DC5628439357003806BE /* ReminderScheduleCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEF4DC5428439357003806BE /* ReminderScheduleCoordinator.swift */; }; FEFA263E26C58427009CCB7E /* ShareAppTextActivityItemSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFA263D26C58427009CCB7E /* ShareAppTextActivityItemSourceTests.swift */; }; @@ -9362,6 +9364,7 @@ FED77257298BC5B300C2346E /* PluginJetpackProxyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginJetpackProxyService.swift; sourceTree = ""; }; FEDA1AD7269D475D0038EC98 /* ListTableViewCell+Comments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListTableViewCell+Comments.swift"; sourceTree = ""; }; FEDDD46E26A03DE900F8942B /* ListTableViewCell+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListTableViewCell+Notifications.swift"; sourceTree = ""; }; + FEE48EFB2A4C8312008A48E0 /* Blog+JetpackSocial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Blog+JetpackSocial.swift"; sourceTree = ""; }; FEF4DC5428439357003806BE /* ReminderScheduleCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderScheduleCoordinator.swift; sourceTree = ""; }; FEFA263D26C58427009CCB7E /* ShareAppTextActivityItemSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppTextActivityItemSourceTests.swift; sourceTree = ""; }; FEFC0F872730510F001F7F1D /* WordPress 136.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 136.xcdatamodel"; sourceTree = ""; }; @@ -14906,6 +14909,7 @@ F10D634E26F0B78E00E46CC7 /* Blog+Organization.swift */, 8B4EDADC27DF9D5E004073B6 /* Blog+MySite.swift */, 4AD5656E28E413160054C676 /* Blog+History.swift */, + FEE48EFB2A4C8312008A48E0 /* Blog+JetpackSocial.swift */, ); name = Blog; sourceTree = ""; @@ -21777,6 +21781,7 @@ D816C1E920E0880400C4D82F /* NotificationAction.swift in Sources */, E19B17B01E5C69A5007517C6 /* NSManagedObject.swift in Sources */, FF355D981FB492DD00244E6D /* ExportableAsset.swift in Sources */, + FEE48EFC2A4C8312008A48E0 /* Blog+JetpackSocial.swift in Sources */, E15644EF1CE0E53B00D96E64 /* PlanListViewModel.swift in Sources */, 983002A822FA05D600F03DBB /* InsightsManagementViewController.swift in Sources */, 98CAD296221B4ED2003E8F45 /* StatSection.swift in Sources */, @@ -24448,6 +24453,7 @@ FABB22D42602FC2C00C8785C /* ManagedPerson+CoreDataProperties.swift in Sources */, FE32F003275F602E0040BE67 /* CommentContentRenderer.swift in Sources */, 3FAE0653287C8FC500F46508 /* JPScrollViewDelegate.swift in Sources */, + FEE48EFD2A4C8312008A48E0 /* Blog+JetpackSocial.swift in Sources */, FABB22D52602FC2C00C8785C /* TenorStrings.swift in Sources */, FAFC064F27D2360B002F0483 /* QuickStartCell.swift in Sources */, 086F2482284F52DD00032F39 /* TooltipAnchor.swift in Sources */, From 1b7e58a0c2181586ef66aa2e845a1cd366108778 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Wed, 28 Jun 2023 23:32:33 +0700 Subject: [PATCH 060/175] Update MIGRATIONS.md --- MIGRATIONS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 4270609fc962..59922a1b271b 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -5,6 +5,10 @@ data model as well as any custom migrations. ## WordPress 151 +@dvdchr 2023-06-28 + +- `Blog`: added `planActiveFeatures` (optional, no default, `Transformable` with type `[String]`) + @dvdchr 2023-06-23 - Created a new entity `PublicizeInfo` with: From 1aa3b45c3976483f16caa57ae85bc4d2c5e95a00 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Wed, 28 Jun 2023 23:58:52 +0700 Subject: [PATCH 061/175] Add tests --- WordPress/WordPress.xcodeproj/project.pbxproj | 4 ++ .../WordPressTest/Blog+PublicizeTests.swift | 68 +++++++++++++++++++ WordPress/WordPressTest/BlogBuilder.swift | 5 ++ 3 files changed, 77 insertions(+) create mode 100644 WordPress/WordPressTest/Blog+PublicizeTests.swift diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 2c20e253c697..9cf907e1f115 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -5588,6 +5588,7 @@ FEDDD47026A03DE900F8942B /* ListTableViewCell+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEDDD46E26A03DE900F8942B /* ListTableViewCell+Notifications.swift */; }; FEE48EFC2A4C8312008A48E0 /* Blog+JetpackSocial.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE48EFB2A4C8312008A48E0 /* Blog+JetpackSocial.swift */; }; FEE48EFD2A4C8312008A48E0 /* Blog+JetpackSocial.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE48EFB2A4C8312008A48E0 /* Blog+JetpackSocial.swift */; }; + FEE48EFF2A4C9855008A48E0 /* Blog+PublicizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE48EFE2A4C9855008A48E0 /* Blog+PublicizeTests.swift */; }; FEF4DC5528439357003806BE /* ReminderScheduleCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEF4DC5428439357003806BE /* ReminderScheduleCoordinator.swift */; }; FEF4DC5628439357003806BE /* ReminderScheduleCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEF4DC5428439357003806BE /* ReminderScheduleCoordinator.swift */; }; FEFA263E26C58427009CCB7E /* ShareAppTextActivityItemSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFA263D26C58427009CCB7E /* ShareAppTextActivityItemSourceTests.swift */; }; @@ -9365,6 +9366,7 @@ FEDA1AD7269D475D0038EC98 /* ListTableViewCell+Comments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListTableViewCell+Comments.swift"; sourceTree = ""; }; FEDDD46E26A03DE900F8942B /* ListTableViewCell+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListTableViewCell+Notifications.swift"; sourceTree = ""; }; FEE48EFB2A4C8312008A48E0 /* Blog+JetpackSocial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Blog+JetpackSocial.swift"; sourceTree = ""; }; + FEE48EFE2A4C9855008A48E0 /* Blog+PublicizeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Blog+PublicizeTests.swift"; sourceTree = ""; }; FEF4DC5428439357003806BE /* ReminderScheduleCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderScheduleCoordinator.swift; sourceTree = ""; }; FEFA263D26C58427009CCB7E /* ShareAppTextActivityItemSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppTextActivityItemSourceTests.swift; sourceTree = ""; }; FEFC0F872730510F001F7F1D /* WordPress 136.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 136.xcdatamodel"; sourceTree = ""; }; @@ -12335,6 +12337,7 @@ 2481B1E7260D4EAC00AE59DB /* WPAccount+LookupTests.swift */, 2481B20B260D8FED00AE59DB /* WPAccount+ObjCLookupTests.m */, C38C5D8027F61D2C002F517E /* MenuItemTests.swift */, + FEE48EFE2A4C9855008A48E0 /* Blog+PublicizeTests.swift */, ); name = Models; sourceTree = ""; @@ -23303,6 +23306,7 @@ 570BFD8D22823DE5007859A8 /* PostActionSheetTests.swift in Sources */, FA4ADADA1C509FE400F858D7 /* SiteManagementServiceTests.swift in Sources */, 3F1B66A323A2F54B0075F09E /* ReaderReblogActionTests.swift in Sources */, + FEE48EFF2A4C9855008A48E0 /* Blog+PublicizeTests.swift in Sources */, 5749984722FA0F2E00CE86ED /* PostNoticeViewModelTests.swift in Sources */, 08B6E51C1F037ADD00268F57 /* MediaFileManagerTests.swift in Sources */, 5981FE051AB8A89A0009E080 /* WPUserAgentTests.m in Sources */, diff --git a/WordPress/WordPressTest/Blog+PublicizeTests.swift b/WordPress/WordPressTest/Blog+PublicizeTests.swift new file mode 100644 index 000000000000..287f88b52d78 --- /dev/null +++ b/WordPress/WordPressTest/Blog+PublicizeTests.swift @@ -0,0 +1,68 @@ +import Foundation +import XCTest + +@testable import WordPress + +final class Blog_PublicizeTests: CoreDataTestCase { + + func testAutoSharingInfoForDotComBlog() { + let blog = makeBlog(hostedAtWPCom: true, activeSocialFeature: false) + + // all dotcom sites should have no sharing limitations. + XCTAssertFalse(blog.isSocialSharingLimited) + } + + func testAutoSharingInfoForDotComBlogWithStoredSharingLimit() { + // unlikely case, but let's test anyway. + let blog = makeBlog(hostedAtWPCom: true, activeSocialFeature: false, hasPreExistingData: true) + + XCTAssertFalse(blog.isSocialSharingLimited) + XCTAssertNil(blog.sharingLimit) + } + + func testAutoSharingInfoForSelfHostedBlog() { + let blog = makeBlog(hostedAtWPCom: false, activeSocialFeature: false) + + XCTAssertTrue(blog.isSocialSharingLimited) + } + + func testAutoSharingInfoForSelfHostedBlogWithStoredSharingLimit() { + let blog = makeBlog(hostedAtWPCom: false, activeSocialFeature: false, hasPreExistingData: true) + + XCTAssertNotNil(blog.sharingLimit) + } + + func testAutoSharingInfoForSelfHostedBlogWithSocialFeature() { + let blog = makeBlog(hostedAtWPCom: false, activeSocialFeature: true) + + XCTAssertFalse(blog.isSocialSharingLimited) + } + + func testAutoSharingInfoForSelfHostedBlogWithSocialFeatureAndStoredSharingLimit() { + // Example: a free site purchased an individual Social Basic plan. In this case, there should still be a + // `PublicizeInfo` data stored in Core Data, but the blog might still have the social feature active. + let blog = makeBlog(hostedAtWPCom: false, activeSocialFeature: true, hasPreExistingData: true) + + // the sharing limit should be nil regardless of the existence of stored data. + XCTAssertNil(blog.sharingLimit) + } + + // MARK: - Helpers + + private func makeBlog(hostedAtWPCom: Bool, activeSocialFeature: Bool, hasPreExistingData: Bool = false) -> Blog { + let blog = BlogBuilder(mainContext) + .with(isHostedAtWPCom: hostedAtWPCom) + .with(planActiveFeatures: activeSocialFeature ? ["social-shares-1000"] : []) + .build() + + if hasPreExistingData { + let publicizeInfo = PublicizeInfo(context: mainContext) + publicizeInfo.shareLimit = 30 + publicizeInfo.sharesRemaining = 25 + blog.publicizeInfo = publicizeInfo + } + + return blog + } + +} diff --git a/WordPress/WordPressTest/BlogBuilder.swift b/WordPress/WordPressTest/BlogBuilder.swift index 6242bbe5d624..e90b62319d7d 100644 --- a/WordPress/WordPressTest/BlogBuilder.swift +++ b/WordPress/WordPressTest/BlogBuilder.swift @@ -40,6 +40,11 @@ final class BlogBuilder { return self } + func with(planActiveFeatures: [String]) -> Self { + blog.planActiveFeatures = planActiveFeatures + return self + } + func withJetpack(version: String? = nil, username: String? = nil, email: String? = nil) -> Self { set(blogOption: "jetpack_client_id", value: 1) set(blogOption: "jetpack_version", value: version as Any) From 5eaf9969d628e1af916b4ac6ffe076da7cefbf9f Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Wed, 28 Jun 2023 14:14:38 -0400 Subject: [PATCH 062/175] Don't prompt for blogging reminders if they aren't allowed for the blog. --- .../Blog/Blogging Reminders/BloggingRemindersFlow.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Reminders/BloggingRemindersFlow.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Reminders/BloggingRemindersFlow.swift index 334c7e5dcbd3..40ae94d7981d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Reminders/BloggingRemindersFlow.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blogging Reminders/BloggingRemindersFlow.swift @@ -11,7 +11,7 @@ class BloggingRemindersFlow { delegate: BloggingRemindersFlowDelegate? = nil, onDismiss: DismissClosure? = nil) { - guard Feature.enabled(.bloggingReminders) && JetpackNotificationMigrationService.shared.shouldPresentNotifications() else { + guard blog.areBloggingRemindersAllowed() else { return } From 87c2c71e2b00f439ac21fd538db7f824167c8836 Mon Sep 17 00:00:00 2001 From: "Tanner W. Stokes" Date: Wed, 28 Jun 2023 15:02:27 -0400 Subject: [PATCH 063/175] Add release note. --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 811221163b1e..470196301494 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,6 @@ 22.8 ----- - +* [*] Blogging Reminders: Disabled prompt for self-hosted sites not connected to Jetpack. [#20970] 22.7 ----- From 9e536982512a2310af94b4a8d0814573460dac4b Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 29 Jun 2023 09:34:58 +1200 Subject: [PATCH 064/175] Check if the completion block is nil first Co-authored-by: Gio Lodi --- WordPress/Classes/Services/PostService.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Services/PostService.m b/WordPress/Classes/Services/PostService.m index b446b2cc0064..4310d8555168 100644 --- a/WordPress/Classes/Services/PostService.m +++ b/WordPress/Classes/Services/PostService.m @@ -704,11 +704,11 @@ - (void)mergePosts:(NSArray *)remotePosts [[ContextManager sharedInstance] saveContext:self.managedObjectContext withCompletionBlock:^{ // Call the completion block after context is saved. The callback is called on the context queue because `posts` // contains models that are bound to the `self.managedObjectContext` object. - [self.managedObjectContext performBlock:^{ - if (completion) { + if (completion) { + [self.managedObjectContext performBlock:^{ completion(posts); - } - }]; + }]; + } } onQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; } From 4574c27012b0a71667da7e592a459ccd21327b5e Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 29 Jun 2023 09:47:35 +1200 Subject: [PATCH 065/175] Log account query error --- WordPress/Classes/Services/BlogService.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/Services/BlogService.m b/WordPress/Classes/Services/BlogService.m index e704ca5cb6b5..848eb15783f3 100644 --- a/WordPress/Classes/Services/BlogService.m +++ b/WordPress/Classes/Services/BlogService.m @@ -423,9 +423,10 @@ - (void)associateSyncedBlogsToJetpackAccount:(WPAccount *)account - (void)mergeBlogs:(NSArray *)blogs withAccountID:(NSManagedObjectID *)accountID inContext:(NSManagedObjectContext *)context { // Nuke dead blogs - WPAccount *account = [context existingObjectWithID:accountID error:nil]; + NSError *error = nil; + WPAccount *account = [context existingObjectWithID:accountID error:&error]; if (account == nil) { - DDLogInfo(@"Can't find the account. User may have signed out."); + DDLogInfo(@"Can't find the account. User may have signed out. Error: %@", error); return; } From aaf596e03f50f0b4ab60a7d2ba46664f3739197e Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 29 Jun 2023 09:48:03 +1200 Subject: [PATCH 066/175] Add a issue link Co-authored-by: Gio Lodi --- WordPress/WordPressTest/BlogJetpackTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPress/WordPressTest/BlogJetpackTests.swift b/WordPress/WordPressTest/BlogJetpackTests.swift index 5490efb97fab..468d8fb933c5 100644 --- a/WordPress/WordPressTest/BlogJetpackTests.swift +++ b/WordPress/WordPressTest/BlogJetpackTests.swift @@ -159,6 +159,8 @@ class BlogJetpackTests: CoreDataTestCase { // Blog sync makes a series of HTTP requests: the first one fetchs all blogs, followed by a few // requests to get blog capabilities (one for each blog). + // + // See also https://github.com/wordpress-mobile/WordPress-iOS/issues/20964 HTTPStubs.stubRequest(forEndpoint: "me/sites", withFileAtPath: OHPathForFile("me-sites-with-jetpack.json", Self.self)!) HTTPStubs.stubRequests { request in From 9265aee8aceb80b132ab34379aa613e5db40c43b Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:25:23 -0400 Subject: [PATCH 067/175] Add `dotComID` to the hide no connections view user defaults key --- ...PostSettingsViewController+JetpackSocial.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift index 02ea5050796d..3260ec8a44aa 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift @@ -3,7 +3,7 @@ extension PostSettingsViewController { @objc func showNoConnection() -> Bool { let isJetpackSocialEnabled = FeatureFlag.jetpackSocial.enabled - let isNoConnectionViewHidden = UserPersistentStoreFactory.instance().bool(forKey: Constants.hideNoConnectionViewKey) + let isNoConnectionViewHidden = UserPersistentStoreFactory.instance().bool(forKey: hideNoConnectionViewKey()) let blogSupportsPublicize = apost.blog.supportsPublicize() let blogHasNoConnections = publicizeConnections.count == 0 let blogHasServices = availableServices().count > 0 @@ -27,6 +27,14 @@ extension PostSettingsViewController { return viewController.view } + private func hideNoConnectionViewKey() -> String { + guard let dotComID = apost.blog.dotComID?.stringValue else { + return Constants.hideNoConnectionViewKey + } + + return "\(dotComID)-\(Constants.hideNoConnectionViewKey)" + } + private func onConnectTap() -> () -> Void { return { [weak self] in guard let blog = self?.apost.blog, @@ -39,7 +47,10 @@ extension PostSettingsViewController { private func onNotNowTap() -> () -> Void { return { [weak self] in - UserPersistentStoreFactory.instance().set(true, forKey: Constants.hideNoConnectionViewKey) + guard let key = self?.hideNoConnectionViewKey() else { + return + } + UserPersistentStoreFactory.instance().set(true, forKey: key) self?.tableView.reloadData() } } From 21c1091325532c57b54a47df118361b290a5e636 Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:51:45 +0700 Subject: [PATCH 068/175] Fix BlazeCampaignViewModelTests --- .../WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift b/WordPress/WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift index ce1fd73c4b62..6c9651a91d22 100644 --- a/WordPress/WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift +++ b/WordPress/WordPressTest/Dashboard/BlazeCampaignViewModelTests.swift @@ -29,6 +29,7 @@ private let campaign: BlazeCampaign = { "start_date": "2023-06-13T00:00:00Z", "end_date": "2023-06-01T19:15:45Z", "status": "finished", + "ui_status": "finished", "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", "budget_cents": 500, "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", From 91472e789b08612d164e28074938fb7422892f22 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 29 Jun 2023 09:20:10 -0400 Subject: [PATCH 069/175] Move Blaze json mocks to a file --- WordPress/WordPress.xcodeproj/project.pbxproj | 12 ++++++ .../DashboardBlazeCardCellViewModelTest.swift | 39 ++++--------------- .../Test Data/blaze-search-response.json | 26 +++++++++++++ 3 files changed, 46 insertions(+), 31 deletions(-) create mode 100644 WordPress/WordPressTest/Test Data/blaze-search-response.json diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 98e166382cb6..664b4b2591ac 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -353,6 +353,7 @@ 0C63266F2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C63266E2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift */; }; 0C71959B2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; 0C71959C2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.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 */; }; 0C7E09242A4286F40052324C /* PostMetaButton+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */; }; @@ -6056,6 +6057,7 @@ 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignViewModelTests.swift; sourceTree = ""; }; 0C63266E2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergFilesAppMediaSourceTests.swift; sourceTree = ""; }; 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingsRelatedPostsView.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 = ""; }; 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostMetaButton+Swift.swift"; sourceTree = ""; }; @@ -10014,6 +10016,14 @@ path = Mocks; sourceTree = ""; }; + 0C7D48182A4DB91B0023CF84 /* Blaze */ = { + isa = PBXGroup; + children = ( + 0C7D48192A4DB9300023CF84 /* blaze-search-response.json */, + ); + name = Blaze; + sourceTree = ""; + }; 0C896DDC2A3A761600D7D4E7 /* Settings */ = { isa = PBXGroup; children = ( @@ -16541,6 +16551,7 @@ C8567490243F371D001A995E /* Tenor */, D8A468DE2181C5B50094B82F /* Site Creation */, 7E442FC820F6783600DEACA5 /* ActivityLog */, + 0C7D48182A4DB91B0023CF84 /* Blaze */, 8BEE845627B1DC5E0001A93C /* Dashboard */, B5DA8A5E20ADAA1C00D5BDE1 /* plugin-directory-jetpack.json */, 855408851A6F105700DDBD79 /* app-review-prompt-all-enabled.json */, @@ -19259,6 +19270,7 @@ E12BE5EE1C5235DB000FD5CA /* get-me-settings-v1.1.json in Resources */, 933D1F6C1EA7A3AB009FB462 /* TestingMode.storyboard in Resources */, 08F8CD371EBD2AA80049D0C0 /* test-image-device-photo-gps.jpg in Resources */, + 0C7D481A2A4DB9300023CF84 /* blaze-search-response.json in Resources */, D88A64A6208D92B1008AE9BC /* stock-photos-media.json in Resources */, C8567492243F3751001A995E /* tenor-search-response.json in Resources */, 7E4A772520F7C5E5001C706D /* activity-log-theme-content.json in Resources */, diff --git a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift index 82dbb63ce149..7407aa3f2c2a 100644 --- a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift +++ b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift @@ -102,7 +102,7 @@ private final class MockBlazeService: BlazeServiceProtocol { func getRecentCampaigns(for blog: Blog, completion: @escaping (Result) -> Void) { didPerformRequest = true DispatchQueue.main.async { - completion(.success(response)) + completion(Result(catching: getMockResponse)) } } } @@ -119,36 +119,13 @@ private final class MockDashboardBlazeStore: DashboardBlazeStoreProtocol { } } -private let response: BlazeCampaignsSearchResponse = { +private func getMockResponse() throws -> BlazeCampaignsSearchResponse { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .iso8601 - return try! decoder.decode(BlazeCampaignsSearchResponse.self, from: """ - { - "totalItems": 3, - "campaigns": [ - { - "campaign_id": 26916, - "name": "Test Post - don't approve", - "start_date": "2023-06-13T00:00:00Z", - "end_date": "2023-06-01T19:15:45Z", - "status": "finished", - "ui_status": "finished", - "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", - "budget_cents": 500, - "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "content_config": { - "title": "Test Post - don't approve", - "snippet": "Test Post Empty Empty", - "clickUrl": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "imageUrl": "https://i0.wp.com/public-api.wordpress.com/wpcom/v2/wordads/dsp/api/v1/dsp/creatives/56259/image?w=600&zoom=2" - }, - "campaign_stats": { - "impressions_total": 1000, - "clicks_total": 235 - } - } - ] - } - """.data(using: .utf8)!) -}() + + let url = try XCTUnwrap(Bundle(for: MockDashboardBlazeStore.self) + .url(forResource: "blaze-search-response", withExtension: "json")) + let data = try Data(contentsOf: url) + return try decoder.decode(BlazeCampaignsSearchResponse.self, from: data) +} diff --git a/WordPress/WordPressTest/Test Data/blaze-search-response.json b/WordPress/WordPressTest/Test Data/blaze-search-response.json new file mode 100644 index 000000000000..c30741bb3a1f --- /dev/null +++ b/WordPress/WordPressTest/Test Data/blaze-search-response.json @@ -0,0 +1,26 @@ +{ + "totalItems": 3, + "campaigns": [ + { + "campaign_id": 26916, + "name": "Test Post - don't approve", + "start_date": "2023-06-13T00:00:00Z", + "end_date": "2023-06-01T19:15:45Z", + "status": "finished", + "ui_status": "finished", + "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", + "budget_cents": 500, + "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", + "content_config": { + "title": "Test Post - don't approve", + "snippet": "Test Post Empty Empty", + "clickUrl": "https://alextest9123.wordpress.com/2023/06/01/test-post/", + "imageUrl": "https://i0.wp.com/public-api.wordpress.com/wpcom/v2/wordads/dsp/api/v1/dsp/creatives/56259/image?w=600&zoom=2" + }, + "campaign_stats": { + "impressions_total": 1000, + "clicks_total": 235 + } + } + ] +} From 11fb6211fad5894bffaac02fd0335b68597537d3 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 29 Jun 2023 09:33:12 -0400 Subject: [PATCH 070/175] Turn isBlazeCampaignsFlagEnabled into a closure so that it updates automatically --- .../Cards/Blaze/DashboardBlazeCardCellViewModel.swift | 8 ++++---- .../Dashboard/DashboardBlazeCardCellViewModelTest.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift index e8f02acf0973..ef7bdb2cb8e8 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift @@ -8,7 +8,7 @@ final class DashboardBlazeCardCellViewModel { private let service: BlazeServiceProtocol? private let store: DashboardBlazeStoreProtocol private var isRefreshing = false - private let isBlazeCampaignsFlagEnabled: Bool + private let isBlazeCampaignsFlagEnabled: () -> Bool enum State { /// Showing "Promote you content with Blaze" promo card. @@ -22,13 +22,13 @@ final class DashboardBlazeCardCellViewModel { init(blog: Blog, service: BlazeServiceProtocol? = BlazeService(), store: DashboardBlazeStoreProtocol = BlogDashboardPersistence(), - isBlazeCampaignsFlagEnabled: Bool = RemoteFeatureFlag.blazeManageCampaigns.enabled()) { + isBlazeCampaignsFlagEnabled: @escaping () -> Bool = { RemoteFeatureFlag.blazeManageCampaigns.enabled() }) { self.blog = blog self.service = service self.store = store self.isBlazeCampaignsFlagEnabled = isBlazeCampaignsFlagEnabled - if isBlazeCampaignsFlagEnabled, + if isBlazeCampaignsFlagEnabled(), let blogID = blog.dotComID?.intValue, let campaign = store.getBlazeCampaign(forBlogID: blogID) { self.state = .campaign(BlazeCampaignViewModel(campaign: campaign)) @@ -38,7 +38,7 @@ final class DashboardBlazeCardCellViewModel { } @objc func refresh() { - guard isBlazeCampaignsFlagEnabled else { + guard isBlazeCampaignsFlagEnabled() else { return // Continue showing the default `Promo` card } diff --git a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift index 7407aa3f2c2a..bb3487bbe3ec 100644 --- a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift +++ b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift @@ -23,7 +23,7 @@ final class DashboardBlazeCardCellViewModelTest: CoreDataTestCase { blog: blog, service: service, store: store, - isBlazeCampaignsFlagEnabled: isBlazeCampaignsFlagEnabled + isBlazeCampaignsFlagEnabled: { isBlazeCampaignsFlagEnabled } ) } From 796b09dd41309f2c0d428db9c5502ba349369913 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 28 Jun 2023 09:50:25 -0400 Subject: [PATCH 071/175] Add tracking for Blaze campaing flow --- .../Blaze Campaigns/BlazeCampaignsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index 0b6ba363d968..211b2e1d0b8e 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -97,7 +97,7 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { } @objc private func plusButtonTapped() { - // TODO: Track event + BlazeEventsTracker.trackBlazeFlowStarted(for: .campaignsList) BlazeFlowCoordinator.presentBlaze(in: self, source: .campaignsList, blog: blog) } } From 3bf5747126cbb7b189422e9eb817f7fd7da29727 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 28 Jun 2023 09:56:17 -0400 Subject: [PATCH 072/175] Integrate Blaze campaigns API (no paging for now) --- .../BlazeCampaignsViewController.swift | 94 +++---------------- 1 file changed, 13 insertions(+), 81 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index 211b2e1d0b8e..188e487517d3 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -86,14 +86,22 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { } private func fetchCampaigns() { - isLoading = true + guard let service = BlazeService() else { return } - // FIXME: Fetch campaigns via BlazeService + isLoading = true + service.getRecentCampaigns(for: blog) { [weak self] in + self?.didFetchCampaigns($0) + } + } - DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in - self?.isLoading = false - self?.campaigns = mockResponse.campaigns ?? [] + private func didFetchCampaigns(_ result: Result) { + switch result { + case .success(let response): + campaigns = response.campaigns ?? [] + case .failure: + showErrorView() } + isLoading = false } @objc private func plusButtonTapped() { @@ -187,79 +195,3 @@ private extension BlazeCampaignsViewController { } } } - -private let mockResponse: BlazeCampaignsSearchResponse = { - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - decoder.dateDecodingStrategy = .iso8601 - return try! decoder.decode(BlazeCampaignsSearchResponse.self, from: """ - { - "totalItems": 3, - "campaigns": [ - { - "campaign_id": 26916, - "name": "Test Post - don't approve Test Post - don't approve", - "start_date": "2023-06-13T00:00:00Z", - "end_date": "2023-06-01T19:15:45Z", - "status": "finished", - "ui_status": "finished", - "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", - "budget_cents": 500, - "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "content_config": { - "title": "Test Post - don't approve", - "snippet": "Test Post Empty Empty", - "clickUrl": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "imageUrl": "https://i0.wp.com/public-api.wordpress.com/wpcom/v2/wordads/dsp/api/v1/dsp/creatives/56259/image?w=600&zoom=2" - }, - "campaign_stats": { - "impressions_total": 1000, - "clicks_total": 235 - } - }, - { - "campaign_id": 1, - "name": "Test Post - don't approve", - "start_date": "2023-06-13T00:00:00Z", - "end_date": "2023-06-01T19:15:45Z", - "status": "rejected", - "ui_status": "rejected", - "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", - "budget_cents": 5000, - "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "content_config": { - "title": "Test Post - don't approve", - "snippet": "Test Post Empty Empty", - "clickUrl": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "imageUrl": "https://i0.wp.com/public-api.wordpress.com/wpcom/v2/wordads/dsp/api/v1/dsp/creatives/56259/image?w=600&zoom=2" - }, - "campaign_stats": { - "impressions_total": 1000, - "clicks_total": 235 - } - }, - { - "campaign_id": 2, - "name": "Test Post - don't approve", - "start_date": "2023-06-13T00:00:00Z", - "end_date": "2023-06-01T19:15:45Z", - "status": "active", - "ui_status": "active", - "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", - "budget_cents": 1000, - "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "content_config": { - "title": "Test Post - don't approve", - "snippet": "Test Post Empty Empty", - "clickUrl": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "imageUrl": "https://i0.wp.com/public-api.wordpress.com/wpcom/v2/wordads/dsp/api/v1/dsp/creatives/56259/image?w=600&zoom=2" - }, - "campaign_stats": { - "impressions_total": 5000, - "clicks_total": 1035 - } - } - ] - } - """.data(using: .utf8)!) -}() From 0ccee9805f500913ea65287f714df4f1bbeb5a01 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 28 Jun 2023 14:05:26 -0400 Subject: [PATCH 073/175] Add BlazeCampaignsStream --- WordPress/Classes/Services/BlazeService.swift | 9 +- .../BlazeCampaignsStream.swift | 60 ++++++++++ .../BlazeCampaignsViewController.swift | 103 ++++++++---------- WordPress/WordPress.xcodeproj/project.pbxproj | 6 + 4 files changed, 119 insertions(+), 59 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift diff --git a/WordPress/Classes/Services/BlazeService.swift b/WordPress/Classes/Services/BlazeService.swift index 987873304fe0..c0f90d58a991 100644 --- a/WordPress/Classes/Services/BlazeService.swift +++ b/WordPress/Classes/Services/BlazeService.swift @@ -28,6 +28,7 @@ protocol BlazeServiceProtocol { // MARK: - Methods func getRecentCampaigns(for blog: Blog, + page: Int = 1, completion: @escaping (Result) -> Void) { guard blog.canBlaze else { completion(.failure(BlazeServiceError.notEligibleForBlaze)) @@ -38,7 +39,13 @@ protocol BlazeServiceProtocol { completion(.failure(BlazeServiceError.missingBlogId)) return } - remote.searchCampaigns(forSiteId: siteId, callback: completion) + remote.searchCampaigns(forSiteId: siteId, page: page, callback: completion) + } + + func recentCampaigns(for blog: Blog, page: Int) async throws -> BlazeCampaignsSearchResponse { + try await withUnsafeThrowingContinuation { + getRecentCampaigns(for: blog, page: page, completion: $0.resume) + } } } diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift new file mode 100644 index 000000000000..a0be93bf497f --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift @@ -0,0 +1,60 @@ +import Foundation +import SwiftUI +import WordPressKit + +@MainActor +final class BlazeCampaignsStream { + private(set) var state = State() { + didSet { didChangeState?(state) } + } + var didChangeState: ((State) -> Void)? + + private var pages: [BlazeCampaignsSearchResponse] = [] + private var hasMore = true + private let blog: Blog + + init(blog: Blog) { + self.blog = blog + } + + func load() { + guard let siteID = blog.dotComID?.intValue else { + return assertionFailure("Missing site ID") + } + guard let service = BlazeService() else { + return assertionFailure("Failed to create BlazeService") + } + + guard !state.isLoading && hasMore else { + return + } + Task { + await load(service: service, siteID: siteID) + } + } + + #warning("fix this being called form background") + private func load(service: BlazeService, siteID: Int) async { + state.isLoading = true + do { + let response = try await service.recentCampaigns(for: siteID, page: pages.count + 1) + let campaigns = response.campaigns ?? [] + if #available(iOS 16, *) { + try? await Task.sleep(for: .seconds(5)) + } + pages.append(response) + state.campaigns += campaigns + hasMore = (response.totalPages ?? 0) > pages.count && !campaigns.isEmpty + } catch { + state.error = error + } + state.isLoading = false + } + + struct State { + var campaigns: [BlazeCampaign] = [] + var isLoading = false + var error: Error? + var isLoadingMore: Bool { isLoading && !campaigns.isEmpty } + } +} diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index 188e487517d3..17244e74db12 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -1,7 +1,7 @@ import UIKit +import WordPressKit final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { - // MARK: - Views private lazy var plusButton: UIBarButtonItem = { @@ -16,6 +16,7 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false tableView.rowHeight = UITableView.automaticDimension + tableView.sectionFooterHeight = UITableView.automaticDimension tableView.separatorStyle = .none tableView.register(BlazeCampaignTableViewCell.self, forCellReuseIdentifier: BlazeCampaignTableViewCell.defaultReuseID) tableView.dataSource = self @@ -25,27 +26,15 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { // MARK: - Properties + private var stream: BlazeCampaignsStream + private var state: BlazeCampaignsStream.State { stream.state } private let blog: Blog - private var campaigns: [BlazeCampaign] = [] { - didSet { - tableView.reloadData() - updateNoResultsView() - } - } - - private var isLoading: Bool = false { - didSet { - if isLoading != oldValue { - updateNoResultsView() - } - } - } - // MARK: - Initializers init(blog: Blog) { self.blog = blog + self.stream = BlazeCampaignsStream(blog: blog) super.init(nibName: nil, bundle: nil) } @@ -58,6 +47,7 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { override func viewDidLoad() { super.viewDidLoad() + setupView() setupNavBar() setupNoResults() @@ -65,7 +55,36 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - fetchCampaigns() + + reloadCampaigns() + } + + private func reloadCampaigns() { + stream.didChangeState = nil + + stream = BlazeCampaignsStream(blog: blog) + stream.didChangeState = { [weak self] _ in self?.refreshView() } + stream.load() + } + + private func refreshView() { + tableView.reloadData() + + hideNoResults() + noResultsViewController.hideImageView(true) + + if state.campaigns.isEmpty { + if state.isLoading { + noResultsViewController.hideImageView(false) + showLoadingView() + } else if state.error != nil { + showErrorView() + } else { + showNoResultsView() + } + } else { + // Loading next page + } } // MARK: - Private helpers @@ -85,25 +104,6 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { noResultsViewController.delegate = self } - private func fetchCampaigns() { - guard let service = BlazeService() else { return } - - isLoading = true - service.getRecentCampaigns(for: blog) { [weak self] in - self?.didFetchCampaigns($0) - } - } - - private func didFetchCampaigns(_ result: Result) { - switch result { - case .success(let response): - campaigns = response.campaigns ?? [] - case .failure: - showErrorView() - } - isLoading = false - } - @objc private func plusButtonTapped() { BlazeEventsTracker.trackBlazeFlowStarted(for: .campaignsList) BlazeFlowCoordinator.presentBlaze(in: self, source: .campaignsList, blog: blog) @@ -115,12 +115,12 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { extension BlazeCampaignsViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return campaigns.count + state.campaigns.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: BlazeCampaignTableViewCell.defaultReuseID) as? BlazeCampaignTableViewCell, - let campaign = campaigns[safe: indexPath.row] else { + let campaign = state.campaigns[safe: indexPath.row] else { return UITableViewCell() } @@ -128,29 +128,20 @@ extension BlazeCampaignsViewController: UITableViewDataSource, UITableViewDelega cell.configure(with: viewModel, blog: blog) return cell } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + guard state.isLoadingMore else { return nil } + let indicator = UIActivityIndicatorView(style: .medium) + indicator.startAnimating() + return indicator + } } // MARK: - No results extension BlazeCampaignsViewController: NoResultsViewControllerDelegate { - private func updateNoResultsView() { - guard !isLoading else { - showLoadingView() - return - } - - if campaigns.isEmpty { - showNoResultsView() - return - } - - hideNoResults() - } - private func showNoResultsView() { - hideNoResults() - noResultsViewController.hideImageView(true) configureAndDisplayNoResults(on: view, title: Strings.NoResults.emptyTitle, subtitle: Strings.NoResults.emptySubtitle, @@ -158,16 +149,12 @@ extension BlazeCampaignsViewController: NoResultsViewControllerDelegate { } private func showErrorView() { - hideNoResults() - noResultsViewController.hideImageView(true) configureAndDisplayNoResults(on: view, title: Strings.NoResults.errorTitle, subtitle: Strings.NoResults.errorSubtitle) } private func showLoadingView() { - hideNoResults() - noResultsViewController.hideImageView(false) configureAndDisplayNoResults(on: view, title: Strings.NoResults.loadingTitle, accessoryView: NoResultsViewController.loadingAccessoryView()) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index ec0808d341c1..10e6e3830c40 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -340,6 +340,8 @@ 0A9610F928B2E56300076EBA /* UserSuggestion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */; }; 0A9610FA28B2E56300076EBA /* UserSuggestion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */; }; 0A9687BC28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */; }; + 0C0D3B0D2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; + 0C0D3B0E2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; 0C35FFF129CB81F700D224EB /* BlogDashboardHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */; }; 0C35FFF229CB81F700D224EB /* BlogDashboardHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */; }; 0C35FFF429CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */; }; @@ -6059,6 +6061,7 @@ 0A69300A28B5AA5E00E98DE1 /* FullScreenCommentReplyViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCommentReplyViewModelTests.swift; sourceTree = ""; }; 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserSuggestion+Comparable.swift"; sourceTree = ""; }; 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCommentReplyViewModelMock.swift; sourceTree = ""; }; + 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsStream.swift; sourceTree = ""; }; 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardHelpers.swift; sourceTree = ""; }; 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationViewModelTests.swift; sourceTree = ""; }; 0C35FFF529CBB5DE00D224EB /* BlogDashboardEmptyStateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardEmptyStateCell.swift; sourceTree = ""; }; @@ -17666,6 +17669,7 @@ FA111E372A2F38FC00896FCE /* BlazeCampaignsViewController.swift */, FA3A28172A38D36900206D74 /* BlazeCampaignTableViewCell.swift */, FA3A281A2A39C8FF00206D74 /* BlazeCampaignSingleStatView.swift */, + 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */, ); path = "Blaze Campaigns"; sourceTree = ""; @@ -21233,6 +21237,7 @@ 3F73388226C1CE9B0075D1DD /* TimeSelectionButton.swift in Sources */, 5DED0E181B432E0400431FCD /* SourcePostAttribution.m in Sources */, 1715179420F4B5CD002C4A38 /* MySitesCoordinator.swift in Sources */, + 0C0D3B0D2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */, 08D978561CD2AF7D0054F19A /* MenuItem+ViewDesign.m in Sources */, 3254366C24ABA82100B2C5F5 /* ReaderInterestsStyleGuide.swift in Sources */, 988056032183CCE50083B643 /* SiteStatsInsightsTableViewController.swift in Sources */, @@ -24343,6 +24348,7 @@ FABB22752602FC2C00C8785C /* NetworkAware.swift in Sources */, FABB22762602FC2C00C8785C /* LanguageViewController.swift in Sources */, FABB22772602FC2C00C8785C /* InlineEditableNameValueCell.swift in Sources */, + 0C0D3B0E2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */, FABB22782602FC2C00C8785C /* TodayStatsRecordValue+CoreDataProperties.swift in Sources */, FABB22792602FC2C00C8785C /* WPCrashLoggingProvider.swift in Sources */, FA332AD529C1FC7A00182FBB /* MovedToJetpackViewModel.swift in Sources */, From 427ac15b310c259d7d35bcdc083df0f7defc631b Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 28 Jun 2023 15:40:33 -0400 Subject: [PATCH 074/175] Add BlazeCampaignFooterView --- .../BlazeCampaignFooterView.swift | 67 +++++++++++++++++++ .../BlazeCampaignTableViewCell.swift | 2 + .../BlazeCampaignsStream.swift | 19 ++++-- .../BlazeCampaignsViewController.swift | 50 +++++++++++--- WordPress/WordPress.xcodeproj/project.pbxproj | 6 ++ 5 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignFooterView.swift diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignFooterView.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignFooterView.swift new file mode 100644 index 000000000000..bc08f3b0999b --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignFooterView.swift @@ -0,0 +1,67 @@ +import UIKit + +final class BlazeCampaignFooterView: UIView { + var onRetry: (() -> Void)? + + private lazy var errorView: UIView = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .body) + label.textColor = .secondaryLabel + label.adjustsFontForContentSizeCategory = true + label.text = Strings.errorMessage + + let button = UIButton(type: .system) + var configuration = UIButton.Configuration.plain() + configuration.title = Strings.retry + configuration.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16) + button.configuration = configuration + button.addTarget(self, action: #selector(buttonRetryTapped), for: .touchUpInside) + + return UIStackView(arrangedSubviews: [label, UIView(), button]) + }() + + private let spinner = UIActivityIndicatorView(style: .medium) + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(errorView) + errorView.translatesAutoresizingMaskIntoConstraints = false + pinSubviewToAllEdges(errorView, insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0), priority: .init(999)) + + addSubview(spinner) + spinner.translatesAutoresizingMaskIntoConstraints = false + spinner.startAnimating() + pinSubviewAtCenter(spinner) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var state: State = .empty { + didSet { + guard oldValue != state else { return } + + subviews.forEach { $0.isHidden = true } + switch state { + case .empty: break + case .error: errorView.isHidden = false + case .loading: spinner.isHidden = false + } + } + } + + @objc private func buttonRetryTapped() { + onRetry?() + } + + private struct Strings { + static let errorMessage = NSLocalizedString("blaze.campaigns.pageLoadError", value: "An error occcured", comment: "A bootom footer error message in Campaign list.") + static let retry = NSLocalizedString("blaze.campaigns.pageLoadRetry", value: "Retry", comment: "A bottom footer retry button in Campaign list.") + } + + enum State { + case empty, loading, error + } +} diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignTableViewCell.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignTableViewCell.swift index 452f76fc5181..b80ec2e8635a 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignTableViewCell.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignTableViewCell.swift @@ -1,3 +1,5 @@ +import UIKit + final class BlazeCampaignTableViewCell: UITableViewCell, Reusable { // MARK: - Views diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift index a0be93bf497f..10071a39e7f8 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift @@ -24,7 +24,6 @@ final class BlazeCampaignsStream { guard let service = BlazeService() else { return assertionFailure("Failed to create BlazeService") } - guard !state.isLoading && hasMore else { return } @@ -34,17 +33,27 @@ final class BlazeCampaignsStream { } #warning("fix this being called form background") + + var didFail = false + private func load(service: BlazeService, siteID: Int) async { state.isLoading = true + state.error = nil do { let response = try await service.recentCampaigns(for: siteID, page: pages.count + 1) let campaigns = response.campaigns ?? [] if #available(iOS 16, *) { - try? await Task.sleep(for: .seconds(5)) + try? await Task.sleep(for: .seconds(4)) + } + #warning("TEMP") + if pages.count == 0 || didFail { + pages.append(response) + state.campaigns += campaigns + hasMore = (response.totalPages ?? 0) > pages.count && !campaigns.isEmpty + } else { + didFail = true + state.error = URLError(.unknown) } - pages.append(response) - state.campaigns += campaigns - hasMore = (response.totalPages ?? 0) > pages.count && !campaigns.isEmpty } catch { state.error = error } diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index 17244e74db12..ef179e05a3a4 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -13,9 +13,11 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { }() private lazy var tableView: UITableView = { - let tableView = UITableView() + // Using grouped style to disable sticky section footers + let tableView = UITableView(frame: .zero, style: .grouped) tableView.translatesAutoresizingMaskIntoConstraints = false tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 128 tableView.sectionFooterHeight = UITableView.automaticDimension tableView.separatorStyle = .none tableView.register(BlazeCampaignTableViewCell.self, forCellReuseIdentifier: BlazeCampaignTableViewCell.defaultReuseID) @@ -24,6 +26,8 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { return tableView }() + private let footerView = BlazeCampaignFooterView() + // MARK: - Properties private var stream: BlazeCampaignsStream @@ -43,6 +47,12 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { fatalError("init(coder:) has not been implemented") } + enum Cell { + case campaign(BlazeCampaign) + case spinner + case error + } + // MARK: - View lifecycle override func viewDidLoad() { @@ -51,6 +61,8 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { setupView() setupNavBar() setupNoResults() + + footerView.onRetry = { [weak self] in self?.stream.load() } } override func viewWillAppear(_ animated: Bool) { @@ -63,16 +75,19 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { stream.didChangeState = nil stream = BlazeCampaignsStream(blog: blog) - stream.didChangeState = { [weak self] _ in self?.refreshView() } + stream.didChangeState = { [weak self] _ in self?.reloadView() } stream.load() } - private func refreshView() { + private func reloadView() { + reloadStateView() + reloadFooterView() tableView.reloadData() + } + private func reloadStateView() { hideNoResults() noResultsViewController.hideImageView(true) - if state.campaigns.isEmpty { if state.isLoading { noResultsViewController.hideImageView(false) @@ -82,8 +97,20 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { } else { showNoResultsView() } + } + } + + private func reloadFooterView() { + if !state.campaigns.isEmpty { + if state.isLoading { + footerView.state = .loading + } else if state.error != nil { + footerView.state = .error + } else { + footerView.state = .empty + } } else { - // Loading next page + footerView.state = .empty } } @@ -130,10 +157,15 @@ extension BlazeCampaignsViewController: UITableViewDataSource, UITableViewDelega } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - guard state.isLoadingMore else { return nil } - let indicator = UIActivityIndicatorView(style: .medium) - indicator.startAnimating() - return indicator + footerView + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if scrollView.contentOffset.y + scrollView.frame.size.height > scrollView.contentSize.height - 500 { + if state.error == nil { + stream.load() + } + } } } diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 10e6e3830c40..28f204a49edb 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -342,6 +342,8 @@ 0A9687BC28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */; }; 0C0D3B0D2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; 0C0D3B0E2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; + 0C0D3B102A4CB4990050A00D /* BlazeCampaignFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0F2A4CB4990050A00D /* BlazeCampaignFooterView.swift */; }; + 0C0D3B112A4CB4990050A00D /* BlazeCampaignFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0F2A4CB4990050A00D /* BlazeCampaignFooterView.swift */; }; 0C35FFF129CB81F700D224EB /* BlogDashboardHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */; }; 0C35FFF229CB81F700D224EB /* BlogDashboardHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */; }; 0C35FFF429CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */; }; @@ -6062,6 +6064,7 @@ 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserSuggestion+Comparable.swift"; sourceTree = ""; }; 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCommentReplyViewModelMock.swift; sourceTree = ""; }; 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsStream.swift; sourceTree = ""; }; + 0C0D3B0F2A4CB4990050A00D /* BlazeCampaignFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignFooterView.swift; sourceTree = ""; }; 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardHelpers.swift; sourceTree = ""; }; 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationViewModelTests.swift; sourceTree = ""; }; 0C35FFF529CBB5DE00D224EB /* BlogDashboardEmptyStateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardEmptyStateCell.swift; sourceTree = ""; }; @@ -17667,6 +17670,7 @@ isa = PBXGroup; children = ( FA111E372A2F38FC00896FCE /* BlazeCampaignsViewController.swift */, + 0C0D3B0F2A4CB4990050A00D /* BlazeCampaignFooterView.swift */, FA3A28172A38D36900206D74 /* BlazeCampaignTableViewCell.swift */, FA3A281A2A39C8FF00206D74 /* BlazeCampaignSingleStatView.swift */, 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */, @@ -21271,6 +21275,7 @@ B5E167F419C08D18009535AA /* NSCalendar+Helpers.swift in Sources */, 80A2154629D15B88002FE8EB /* RemoteConfigOverrideStore.swift in Sources */, F4DDE2C229C92F0D00C02A76 /* CrashLogging+Singleton.swift in Sources */, + 0C0D3B102A4CB4990050A00D /* BlazeCampaignFooterView.swift in Sources */, 4629E4212440C5B20002E15C /* GutenbergCoverUploadProcessor.swift in Sources */, F45326D829F6B8A6005F9F31 /* EnhancedSiteCreationAnalyticsEvent.swift in Sources */, FF00889F204E01AE007CCE66 /* MediaQuotaCell.swift in Sources */, @@ -25058,6 +25063,7 @@ FABB24842602FC2C00C8785C /* SiteStatsTableHeaderView.swift in Sources */, FABB24852602FC2C00C8785C /* MediaSizeSliderCell.swift in Sources */, 80B016D22803AB9F00D15566 /* DashboardPostsListCardCell.swift in Sources */, + 0C0D3B112A4CB4990050A00D /* BlazeCampaignFooterView.swift in Sources */, FABB24862602FC2C00C8785C /* NotificationContentRouter.swift in Sources */, 178DDD31266D7576006C68C4 /* BloggingRemindersFlowCompletionViewController.swift in Sources */, FABB24872602FC2C00C8785C /* PageLayoutService.swift in Sources */, From 1248727be2599da42825ff40fa74549d6cb7e850 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 28 Jun 2023 15:59:54 -0400 Subject: [PATCH 075/175] Add refresh control to Blaze campaign list --- .../BlazeCampaignsStream.swift | 6 +-- .../BlazeCampaignsViewController.swift | 39 ++++++++++++++----- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift index 10071a39e7f8..2790dbd30604 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift @@ -17,7 +17,7 @@ final class BlazeCampaignsStream { self.blog = blog } - func load() { + func load() async { guard let siteID = blog.dotComID?.intValue else { return assertionFailure("Missing site ID") } @@ -27,9 +27,7 @@ final class BlazeCampaignsStream { guard !state.isLoading && hasMore else { return } - Task { - await load(service: service, siteID: siteID) - } + await load(service: service, siteID: siteID) } #warning("fix this being called form background") diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index ef179e05a3a4..71f14a9b1b5e 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -1,4 +1,5 @@ import UIKit +import SVProgressHUD import WordPressKit final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { @@ -20,12 +21,14 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { tableView.estimatedRowHeight = 128 tableView.sectionFooterHeight = UITableView.automaticDimension tableView.separatorStyle = .none + tableView.refreshControl = refreshControl tableView.register(BlazeCampaignTableViewCell.self, forCellReuseIdentifier: BlazeCampaignTableViewCell.defaultReuseID) tableView.dataSource = self tableView.delegate = self return tableView }() + private let refreshControl = UIRefreshControl() private let footerView = BlazeCampaignFooterView() // MARK: - Properties @@ -62,23 +65,41 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { setupNavBar() setupNoResults() - footerView.onRetry = { [weak self] in self?.stream.load() } + refreshControl.addTarget(self, action: #selector(pullToRefreshInvoked), for: .valueChanged) + footerView.onRetry = { [weak self] in self?.loadNextPage() } + + configure(with: stream) + loadNextPage() } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + private func loadNextPage() { + Task { + await stream.load() + } + } - reloadCampaigns() + @objc private func pullToRefreshInvoked() { + Task { + let stream = BlazeCampaignsStream(blog: blog) + await stream.load() + if let error = stream.state.error { + SVProgressHUD.showDismissibleError(withStatus: error.localizedDescription) + } else { + configure(with: stream) + } + refreshControl.endRefreshing() + } } - private func reloadCampaigns() { + private func configure(with newStream: BlazeCampaignsStream) { stream.didChangeState = nil - - stream = BlazeCampaignsStream(blog: blog) + stream = newStream stream.didChangeState = { [weak self] _ in self?.reloadView() } - stream.load() + reloadView() } + // MARK: View reload + private func reloadView() { reloadStateView() reloadFooterView() @@ -163,7 +184,7 @@ extension BlazeCampaignsViewController: UITableViewDataSource, UITableViewDelega func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView.contentOffset.y + scrollView.frame.size.height > scrollView.contentSize.height - 500 { if state.error == nil { - stream.load() + loadNextPage() } } } From 641c92e1da48091c2b6fae034cc161a9362a48ff Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 29 Jun 2023 15:47:50 -0400 Subject: [PATCH 076/175] Improve pagination support --- WordPress/Classes/Services/BlazeService.swift | 8 +- .../BlazeCampaignFooterView.swift | 67 -------- .../BlazeCampaignsStream.swift | 77 +++++----- .../BlazeCampaignsViewController.swift | 145 ++++++++++-------- .../Cards/Blaze/BlazeCampaignStatusView.swift | 17 +- .../Blaze/DashboardBlazeCampaignView.swift | 48 +++--- .../DashboardBlazeCardCellViewModel.swift | 2 +- .../Shared/UITableView+Header.swift | 27 ++++ .../ViewRelated/Views/PagingFooterView.swift | 56 +++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 12 +- .../DashboardBlazeCardCellViewModelTest.swift | 2 +- 11 files changed, 241 insertions(+), 220 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignFooterView.swift create mode 100644 WordPress/Classes/ViewRelated/Views/PagingFooterView.swift diff --git a/WordPress/Classes/Services/BlazeService.swift b/WordPress/Classes/Services/BlazeService.swift index c0f90d58a991..fa4764d7c89e 100644 --- a/WordPress/Classes/Services/BlazeService.swift +++ b/WordPress/Classes/Services/BlazeService.swift @@ -2,7 +2,7 @@ import Foundation import WordPressKit protocol BlazeServiceProtocol { - func getRecentCampaigns(for blog: Blog, completion: @escaping (Result) -> Void) + func getRecentCampaigns(for blog: Blog, page: Int, completion: @escaping (Result) -> Void) } @objc final class BlazeService: NSObject, BlazeServiceProtocol { @@ -41,12 +41,6 @@ protocol BlazeServiceProtocol { } remote.searchCampaigns(forSiteId: siteId, page: page, callback: completion) } - - func recentCampaigns(for blog: Blog, page: Int) async throws -> BlazeCampaignsSearchResponse { - try await withUnsafeThrowingContinuation { - getRecentCampaigns(for: blog, page: page, completion: $0.resume) - } - } } enum BlazeServiceError: Error { diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignFooterView.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignFooterView.swift deleted file mode 100644 index bc08f3b0999b..000000000000 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignFooterView.swift +++ /dev/null @@ -1,67 +0,0 @@ -import UIKit - -final class BlazeCampaignFooterView: UIView { - var onRetry: (() -> Void)? - - private lazy var errorView: UIView = { - let label = UILabel() - label.font = UIFont.preferredFont(forTextStyle: .body) - label.textColor = .secondaryLabel - label.adjustsFontForContentSizeCategory = true - label.text = Strings.errorMessage - - let button = UIButton(type: .system) - var configuration = UIButton.Configuration.plain() - configuration.title = Strings.retry - configuration.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16) - button.configuration = configuration - button.addTarget(self, action: #selector(buttonRetryTapped), for: .touchUpInside) - - return UIStackView(arrangedSubviews: [label, UIView(), button]) - }() - - private let spinner = UIActivityIndicatorView(style: .medium) - - override init(frame: CGRect) { - super.init(frame: frame) - - addSubview(errorView) - errorView.translatesAutoresizingMaskIntoConstraints = false - pinSubviewToAllEdges(errorView, insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0), priority: .init(999)) - - addSubview(spinner) - spinner.translatesAutoresizingMaskIntoConstraints = false - spinner.startAnimating() - pinSubviewAtCenter(spinner) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - var state: State = .empty { - didSet { - guard oldValue != state else { return } - - subviews.forEach { $0.isHidden = true } - switch state { - case .empty: break - case .error: errorView.isHidden = false - case .loading: spinner.isHidden = false - } - } - } - - @objc private func buttonRetryTapped() { - onRetry?() - } - - private struct Strings { - static let errorMessage = NSLocalizedString("blaze.campaigns.pageLoadError", value: "An error occcured", comment: "A bootom footer error message in Campaign list.") - static let retry = NSLocalizedString("blaze.campaigns.pageLoadRetry", value: "Retry", comment: "A bottom footer retry button in Campaign list.") - } - - enum State { - case empty, loading, error - } -} diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift index 2790dbd30604..8a42d28f8c87 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift @@ -2,12 +2,17 @@ import Foundation import SwiftUI import WordPressKit -@MainActor +protocol BlazeCampaignsStreamDelegate: AnyObject { + func stream(_ stream: BlazeCampaignsStream, didAppendItemsAt indexPaths: [IndexPath]) + func streamDidRefreshState(_ stream: BlazeCampaignsStream) +} + final class BlazeCampaignsStream { - private(set) var state = State() { - didSet { didChangeState?(state) } - } - var didChangeState: ((State) -> Void)? + weak var delegate: BlazeCampaignsStreamDelegate? + + private(set) var campaigns: [BlazeCampaign] = [] + private(set) var isLoading = false + private(set) var error: Error? private var pages: [BlazeCampaignsSearchResponse] = [] private var hasMore = true @@ -17,51 +22,39 @@ final class BlazeCampaignsStream { self.blog = blog } - func load() async { - guard let siteID = blog.dotComID?.intValue else { - return assertionFailure("Missing site ID") - } + /// Loads the next page. Does nothing if it's already loading or has no more items to load. + func load(_ completion: ((Result) -> Void)? = nil) { guard let service = BlazeService() else { return assertionFailure("Failed to create BlazeService") } - guard !state.isLoading && hasMore else { + guard !isLoading && hasMore else { return } - await load(service: service, siteID: siteID) - } - - #warning("fix this being called form background") + isLoading = true + error = nil + delegate?.streamDidRefreshState(self) - var didFail = false - - private func load(service: BlazeService, siteID: Int) async { - state.isLoading = true - state.error = nil - do { - let response = try await service.recentCampaigns(for: siteID, page: pages.count + 1) - let campaigns = response.campaigns ?? [] - if #available(iOS 16, *) { - try? await Task.sleep(for: .seconds(4)) - } - #warning("TEMP") - if pages.count == 0 || didFail { - pages.append(response) - state.campaigns += campaigns - hasMore = (response.totalPages ?? 0) > pages.count && !campaigns.isEmpty - } else { - didFail = true - state.error = URLError(.unknown) - } - } catch { - state.error = error + service.getRecentCampaigns(for: blog, page: pages.count + 1) { [weak self] in + self?.didLoad(with: $0) + completion?($0) } - state.isLoading = false } - struct State { - var campaigns: [BlazeCampaign] = [] - var isLoading = false - var error: Error? - var isLoadingMore: Bool { isLoading && !campaigns.isEmpty } + private func didLoad(with result: Result) { + switch result { + case .success(let response): + let newCampaigns = response.campaigns ?? [] + pages.append(response) + hasMore = (response.totalPages ?? 0) > pages.count && !newCampaigns.isEmpty + + campaigns += newCampaigns + let indexPaths = campaigns.indices.prefix(newCampaigns.count) + .map { IndexPath(row: $0, section: 0) } + delegate?.stream(self, didAppendItemsAt: indexPaths) + case .failure(let error): + self.error = error + } + isLoading = false + delegate?.streamDidRefreshState(self) } } diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index 71f14a9b1b5e..3e8a8a53bb41 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -2,26 +2,25 @@ import UIKit import SVProgressHUD import WordPressKit -final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { +final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, BlazeCampaignsStreamDelegate { // MARK: - Views - private lazy var plusButton: UIBarButtonItem = { - let button = UIBarButtonItem(image: UIImage(systemName: "plus"), - style: .plain, - target: self, - action: #selector(plusButtonTapped)) - return button - }() + private lazy var plusButton = UIBarButtonItem( + image: UIImage(systemName: "plus"), + style: .plain, + target: self, + action: #selector(plusButtonTapped) + ) private lazy var tableView: UITableView = { - // Using grouped style to disable sticky section footers - let tableView = UITableView(frame: .zero, style: .grouped) + let tableView = UITableView(frame: .zero, style: .plain) tableView.translatesAutoresizingMaskIntoConstraints = false tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 128 tableView.sectionFooterHeight = UITableView.automaticDimension tableView.separatorStyle = .none tableView.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(setNeedsToRefreshCampaigns), for: .valueChanged) tableView.register(BlazeCampaignTableViewCell.self, forCellReuseIdentifier: BlazeCampaignTableViewCell.defaultReuseID) tableView.dataSource = self tableView.delegate = self @@ -29,12 +28,11 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { }() private let refreshControl = UIRefreshControl() - private let footerView = BlazeCampaignFooterView() // MARK: - Properties private var stream: BlazeCampaignsStream - private var state: BlazeCampaignsStream.State { stream.state } + private var pendingStream: AnyObject? private let blog: Blog // MARK: - Initializers @@ -50,12 +48,6 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { fatalError("init(coder:) has not been implemented") } - enum Cell { - case campaign(BlazeCampaign) - case spinner - case error - } - // MARK: - View lifecycle override func viewDidLoad() { @@ -65,55 +57,51 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { setupNavBar() setupNoResults() - refreshControl.addTarget(self, action: #selector(pullToRefreshInvoked), for: .valueChanged) - footerView.onRetry = { [weak self] in self?.loadNextPage() } - configure(with: stream) - loadNextPage() - } + stream.load() - private func loadNextPage() { - Task { - await stream.load() - } + NotificationCenter.default.addObserver(self, selector: #selector(setNeedsToRefreshCampaigns), name: .blazeCampaignCreated, object: nil) } - @objc private func pullToRefreshInvoked() { - Task { - let stream = BlazeCampaignsStream(blog: blog) - await stream.load() - if let error = stream.state.error { - SVProgressHUD.showDismissibleError(withStatus: error.localizedDescription) - } else { - configure(with: stream) - } - refreshControl.endRefreshing() - } + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + tableView.sizeToFitFooterView() } private func configure(with newStream: BlazeCampaignsStream) { - stream.didChangeState = nil - stream = newStream - stream.didChangeState = { [weak self] _ in self?.reloadView() } + self.stream = newStream + newStream.delegate = self + tableView.reloadData() reloadView() } - // MARK: View reload + // MARK: - BlazeCampaignsStreamDelegate + + func stream(_ stream: BlazeCampaignsStream, didAppendItemsAt indexPaths: [IndexPath]) { + UIView.performWithoutAnimation { + tableView.insertRows(at: indexPaths, with: .none) + } + } + + func streamDidRefreshState(_ stream: BlazeCampaignsStream) { + reloadView() + } private func reloadView() { reloadStateView() reloadFooterView() - tableView.reloadData() + tableView.sizeToFitFooterView() } private func reloadStateView() { hideNoResults() noResultsViewController.hideImageView(true) - if state.campaigns.isEmpty { - if state.isLoading { + if stream.campaigns.isEmpty { + if stream.isLoading { noResultsViewController.hideImageView(false) showLoadingView() - } else if state.error != nil { + } else if stream.error != nil { showErrorView() } else { showNoResultsView() @@ -122,20 +110,48 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { } private func reloadFooterView() { - if !state.campaigns.isEmpty { - if state.isLoading { - footerView.state = .loading - } else if state.error != nil { - footerView.state = .error - } else { - footerView.state = .empty - } + guard !stream.campaigns.isEmpty else { + tableView.tableFooterView = nil + return + } + if stream.isLoading { + tableView.tableFooterView = PagingFooterView(state: .loading) + } else if stream.error != nil { + let footerView = PagingFooterView(state: .error) + footerView.buttonRetry.addTarget(self, action: #selector(buttonRetryTapped), for: .touchUpInside) + tableView.tableFooterView = footerView } else { - footerView.state = .empty + tableView.tableFooterView = nil + } + } + + // MARK: - Actions + + @objc private func buttonRetryTapped() { + stream.load() + } + + @objc private func setNeedsToRefreshCampaigns() { + guard pendingStream == nil else { return } + + let stream = BlazeCampaignsStream(blog: blog) + stream.load { [weak self] in + guard let self else { return } + switch $0 { + case .success: + self.configure(with: stream) + case .failure(let error): + SVProgressHUD.showDismissibleError(withStatus: error.localizedDescription) + } + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { + self.pendingStream = nil + self.refreshControl.endRefreshing() + } } + pendingStream = stream } - // MARK: - Private helpers + // MARK: - Private private func setupView() { view.backgroundColor = .DS.Background.primary @@ -163,28 +179,21 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost { extension BlazeCampaignsViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - state.campaigns.count + stream.campaigns.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: BlazeCampaignTableViewCell.defaultReuseID) as? BlazeCampaignTableViewCell, - let campaign = state.campaigns[safe: indexPath.row] else { - return UITableViewCell() - } - + let cell = tableView.dequeueReusableCell(withIdentifier: BlazeCampaignTableViewCell.defaultReuseID) as! BlazeCampaignTableViewCell + let campaign = stream.campaigns[indexPath.row] let viewModel = BlazeCampaignViewModel(campaign: campaign) cell.configure(with: viewModel, blog: blog) return cell } - func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - footerView - } - func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView.contentOffset.y + scrollView.frame.size.height > scrollView.contentSize.height - 500 { - if state.error == nil { - loadNextPage() + if stream.error == nil { + stream.load() } } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift index 54e79092b649..1ed7960cbaf3 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift @@ -37,17 +37,16 @@ private extension BlazeCampaignStatusView { } } -struct BlazeCampaignStatusViewModel { +struct BlazeCampaignStatusViewModel: Hashable { + private let status: BlazeCampaign.Status + let isHidden: Bool let title: String let textColor: UIColor let backgroundColor: UIColor - init(campaign: BlazeCampaign) { - self.init(status: campaign.uiStatus) - } - init(status: BlazeCampaign.Status) { + self.status = status self.isHidden = status == .unknown self.title = status.localizedTitle @@ -93,6 +92,14 @@ struct BlazeCampaignStatusViewModel { self.backgroundColor = .secondarySystemBackground } } + + func hash(into hasher: inout Hasher) { + status.hash(into: &hasher) + } + + static func ==(lhs: BlazeCampaignStatusViewModel, rhs: BlazeCampaignStatusViewModel) -> Bool { + lhs.status == rhs.status + } } extension BlazeCampaign.Status { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift index 84ba63790dac..db8827dd4eec 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift @@ -97,40 +97,42 @@ private extension DashboardBlazeCampaignView { } } -struct BlazeCampaignViewModel { +struct BlazeCampaignViewModel: Hashable { let title: String let imageURL: URL? let impressions: Int let clicks: Int let budget: String - var status: BlazeCampaignStatusViewModel { .init(campaign: campaign) } - - var isShowingStats: Bool { - switch campaign.uiStatus { - case .created, .processing, .canceled, .approved, .rejected, .scheduled, .unknown: - return false - case .active, .finished: - return true - } - } - - private let campaign: BlazeCampaign - - private let currencyFormatter: NumberFormatter = { - let formatter = NumberFormatter() - formatter.numberStyle = .currency - formatter.currencyCode = "USD" - formatter.currencySymbol = "$" - formatter.maximumFractionDigits = 0 - return formatter - }() + let isShowingStats: Bool + let status: BlazeCampaignStatusViewModel init(campaign: BlazeCampaign) { - self.campaign = campaign self.title = campaign.name ?? "–" self.imageURL = campaign.contentConfig?.imageURL.flatMap(URL.init) self.impressions = campaign.stats?.impressionsTotal ?? 0 self.clicks = campaign.stats?.clicksTotal ?? 0 self.budget = currencyFormatter.string(from: ((campaign.budgetCents ?? 0) / 100) as NSNumber) ?? "-" + self.isShowingStats = campaign.uiStatus.isShowingStats + self.status = BlazeCampaignStatusViewModel(status: campaign.uiStatus) + } +} + +private let currencyFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.currencyCode = "USD" + formatter.currencySymbol = "$" + formatter.maximumFractionDigits = 0 + return formatter +}() + +private extension BlazeCampaign.Status { + var isShowingStats: Bool { + switch self { + case .created, .processing, .canceled, .approved, .rejected, .scheduled, .unknown: + return false + case .active, .finished: + return true + } } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift index ef7bdb2cb8e8..7b019360e966 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift @@ -45,7 +45,7 @@ final class DashboardBlazeCardCellViewModel { guard !isRefreshing, let service else { return } isRefreshing = true - service.getRecentCampaigns(for: blog) { [weak self] in + service.getRecentCampaigns(for: blog, page: 1) { [weak self] in self?.didRefresh(with: $0) } } diff --git a/WordPress/Classes/ViewRelated/Site Creation/Shared/UITableView+Header.swift b/WordPress/Classes/ViewRelated/Site Creation/Shared/UITableView+Header.swift index 9e4c28b3b6d5..716db7dca6e5 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Shared/UITableView+Header.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Shared/UITableView+Header.swift @@ -48,4 +48,31 @@ extension UITableView { self.tableHeaderView = tableHeaderView } } + + /// Resizes the `tableFooterView` to fit its content. + /// + /// The `tableFooterView` doesn't adjust its size automatically like a `UITableViewCell`, so this method + /// should be called whenever the `tableView`'s bounds changes or when the `tableFooterView` content changes. + /// + /// This method should typically be called in `UIViewController.viewDidLayoutSubviews`. + /// + /// Source: https://gist.github.com/smileyborg/50de5da1c921b73bbccf7f76b3694f6a + /// + func sizeToFitFooterView() { + guard let tableFooterView else { + return + } + let fittingSize = CGSize(width: bounds.width - (safeAreaInsets.left + safeAreaInsets.right), height: 0) + let size = tableFooterView.systemLayoutSizeFitting( + fittingSize, + withHorizontalFittingPriority: .required, + verticalFittingPriority: .fittingSizeLevel + ) + let newFrame = CGRect(origin: .zero, size: size) + if tableFooterView.frame.height != newFrame.height { + tableFooterView.frame = newFrame + self.tableFooterView = tableFooterView + } + } + } diff --git a/WordPress/Classes/ViewRelated/Views/PagingFooterView.swift b/WordPress/Classes/ViewRelated/Views/PagingFooterView.swift new file mode 100644 index 000000000000..61e80b6cef40 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Views/PagingFooterView.swift @@ -0,0 +1,56 @@ +import UIKit + +final class PagingFooterView: UIView { + enum State { + case loading, error + } + + let buttonRetry: UIButton = { + let button = UIButton(type: .system) + var configuration = UIButton.Configuration.plain() + configuration.title = Strings.retry + configuration.contentInsets = NSDirectionalEdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16) + button.configuration = configuration + return button + }() + + private lazy var errorView: UIView = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .body) + label.textColor = .secondaryLabel + label.adjustsFontForContentSizeCategory = true + label.text = Strings.errorMessage + return UIStackView(arrangedSubviews: [label, UIView(), buttonRetry]) + }() + + private let spinner = UIActivityIndicatorView(style: .medium) + + init(state: State) { + super.init(frame: .zero) + + // Add errorView to ensure the footer has the same height in both states + addSubview(errorView) + errorView.translatesAutoresizingMaskIntoConstraints = false + errorView.isHidden = true + pinSubviewToAllEdges(errorView, insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0), priority: .init(999)) + + switch state { + case .error: + errorView.isHidden = false + case .loading: + addSubview(spinner) + spinner.startAnimating() + spinner.translatesAutoresizingMaskIntoConstraints = false + pinSubviewAtCenter(spinner) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private struct Strings { + static let errorMessage = NSLocalizedString("general.pagingFooterView.errorMessage", value: "An error occurred", comment: "A generic error message for a footer view in a list with pagination") + static let retry = NSLocalizedString("general.pagingFooterView.retry", value: "Retry", comment: "A footer retry button") + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 28f204a49edb..2b032a47f5fa 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -342,8 +342,6 @@ 0A9687BC28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */; }; 0C0D3B0D2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; 0C0D3B0E2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; - 0C0D3B102A4CB4990050A00D /* BlazeCampaignFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0F2A4CB4990050A00D /* BlazeCampaignFooterView.swift */; }; - 0C0D3B112A4CB4990050A00D /* BlazeCampaignFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0F2A4CB4990050A00D /* BlazeCampaignFooterView.swift */; }; 0C35FFF129CB81F700D224EB /* BlogDashboardHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */; }; 0C35FFF229CB81F700D224EB /* BlogDashboardHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */; }; 0C35FFF429CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */; }; @@ -362,6 +360,8 @@ 0C7E09212A4286A00052324C /* PostMetaButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E091F2A4286A00052324C /* PostMetaButton.m */; }; 0C7E09242A4286F40052324C /* PostMetaButton+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */; }; 0C7E09252A4286F40052324C /* PostMetaButton+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */; }; + 0C8078AB2A4E01A5002ABF29 /* PagingFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8078AA2A4E01A5002ABF29 /* PagingFooterView.swift */; }; + 0C8078AC2A4E01A5002ABF29 /* PagingFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8078AA2A4E01A5002ABF29 /* PagingFooterView.swift */; }; 0C896DDE2A3A762200D7D4E7 /* SettingsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DDD2A3A762200D7D4E7 /* SettingsPicker.swift */; }; 0C896DE02A3A763400D7D4E7 /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DDF2A3A763400D7D4E7 /* SettingsCell.swift */; }; 0C896DE22A3A767200D7D4E7 /* SiteVisibility+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */; }; @@ -6064,7 +6064,6 @@ 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserSuggestion+Comparable.swift"; sourceTree = ""; }; 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCommentReplyViewModelMock.swift; sourceTree = ""; }; 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsStream.swift; sourceTree = ""; }; - 0C0D3B0F2A4CB4990050A00D /* BlazeCampaignFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignFooterView.swift; sourceTree = ""; }; 0C35FFF029CB81F700D224EB /* BlogDashboardHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardHelpers.swift; sourceTree = ""; }; 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationViewModelTests.swift; sourceTree = ""; }; 0C35FFF529CBB5DE00D224EB /* BlogDashboardEmptyStateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardEmptyStateCell.swift; sourceTree = ""; }; @@ -6077,6 +6076,7 @@ 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 = ""; }; 0C7E09232A4286F40052324C /* PostMetaButton+Swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostMetaButton+Swift.swift"; sourceTree = ""; }; + 0C8078AA2A4E01A5002ABF29 /* PagingFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingFooterView.swift; sourceTree = ""; }; 0C896DDD2A3A762200D7D4E7 /* SettingsPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPicker.swift; sourceTree = ""; }; 0C896DDF2A3A763400D7D4E7 /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; 0C896DE12A3A767200D7D4E7 /* SiteVisibility+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteVisibility+Extensions.swift"; sourceTree = ""; }; @@ -9814,6 +9814,7 @@ F18CB8952642E58700B90794 /* FixedSizeImageView.swift */, FADFBD25265F580500039C41 /* MultilineButton.swift */, 808C578E27C7FB1A0099A92C /* ButtonScrollView.swift */, + 0C8078AA2A4E01A5002ABF29 /* PagingFooterView.swift */, ); path = Views; sourceTree = ""; @@ -17670,7 +17671,6 @@ isa = PBXGroup; children = ( FA111E372A2F38FC00896FCE /* BlazeCampaignsViewController.swift */, - 0C0D3B0F2A4CB4990050A00D /* BlazeCampaignFooterView.swift */, FA3A28172A38D36900206D74 /* BlazeCampaignTableViewCell.swift */, FA3A281A2A39C8FF00206D74 /* BlazeCampaignSingleStatView.swift */, 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */, @@ -21275,7 +21275,6 @@ B5E167F419C08D18009535AA /* NSCalendar+Helpers.swift in Sources */, 80A2154629D15B88002FE8EB /* RemoteConfigOverrideStore.swift in Sources */, F4DDE2C229C92F0D00C02A76 /* CrashLogging+Singleton.swift in Sources */, - 0C0D3B102A4CB4990050A00D /* BlazeCampaignFooterView.swift in Sources */, 4629E4212440C5B20002E15C /* GutenbergCoverUploadProcessor.swift in Sources */, F45326D829F6B8A6005F9F31 /* EnhancedSiteCreationAnalyticsEvent.swift in Sources */, FF00889F204E01AE007CCE66 /* MediaQuotaCell.swift in Sources */, @@ -21369,6 +21368,7 @@ 3FBB2D2B27FB6CB200C57BBF /* SiteNameViewController.swift in Sources */, 17BD4A192101D31B00975AC3 /* NavigationActionHelpers.swift in Sources */, 3F4D035028A56F9B00F0A4FD /* CircularImageButton.swift in Sources */, + 0C8078AB2A4E01A5002ABF29 /* PagingFooterView.swift in Sources */, B55FFCFA1F034F1A0070812C /* String+Ranges.swift in Sources */, 7E846FF320FD37BD00881F5A /* ActivityCommentRange.swift in Sources */, 1E672D95257663CE00421F13 /* GutenbergAudioUploadProcessor.swift in Sources */, @@ -24593,6 +24593,7 @@ FA3A281C2A39C8FF00206D74 /* BlazeCampaignSingleStatView.swift in Sources */, FABB231D2602FC2C00C8785C /* PlanGroup.swift in Sources */, 8320BDE6283D9359009DF2DE /* BlogService+BloggingPrompts.swift in Sources */, + 0C8078AC2A4E01A5002ABF29 /* PagingFooterView.swift in Sources */, FABB231E2602FC2C00C8785C /* MenuItemPostsViewController.m in Sources */, FABB231F2602FC2C00C8785C /* SignupUsernameTableViewController.swift in Sources */, FABB23202602FC2C00C8785C /* Blog+Editor.swift in Sources */, @@ -25063,7 +25064,6 @@ FABB24842602FC2C00C8785C /* SiteStatsTableHeaderView.swift in Sources */, FABB24852602FC2C00C8785C /* MediaSizeSliderCell.swift in Sources */, 80B016D22803AB9F00D15566 /* DashboardPostsListCardCell.swift in Sources */, - 0C0D3B112A4CB4990050A00D /* BlazeCampaignFooterView.swift in Sources */, FABB24862602FC2C00C8785C /* NotificationContentRouter.swift in Sources */, 178DDD31266D7576006C68C4 /* BloggingRemindersFlowCompletionViewController.swift in Sources */, FABB24872602FC2C00C8785C /* PageLayoutService.swift in Sources */, diff --git a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift index 5ab5e569f14d..a944e1c4f04c 100644 --- a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift +++ b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift @@ -99,7 +99,7 @@ final class DashboardBlazeCardCellViewModelTest: CoreDataTestCase { private final class MockBlazeService: BlazeServiceProtocol { var didPerformRequest = false - func getRecentCampaigns(for blog: Blog, completion: @escaping (Result) -> Void) { + func getRecentCampaigns(for blog: Blog, page: Int, completion: @escaping (Result) -> Void) { didPerformRequest = true DispatchQueue.main.async { completion(Result(catching: getMockResponse)) From faa047caaed9863bb6c2607b2ed3011df94b95c8 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 29 Jun 2023 15:51:55 -0400 Subject: [PATCH 077/175] Remove duplicated campaigns (just in case) --- .../ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift index 8a42d28f8c87..6f2b868290fb 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift @@ -15,6 +15,7 @@ final class BlazeCampaignsStream { private(set) var error: Error? private var pages: [BlazeCampaignsSearchResponse] = [] + private var campaignIDs: Set = [] private var hasMore = true private let blog: Blog @@ -43,11 +44,13 @@ final class BlazeCampaignsStream { private func didLoad(with result: Result) { switch result { case .success(let response): - let newCampaigns = response.campaigns ?? [] + let newCampaigns = (response.campaigns ?? []) + .filter { !campaignIDs.contains($0.campaignID) } pages.append(response) hasMore = (response.totalPages ?? 0) > pages.count && !newCampaigns.isEmpty campaigns += newCampaigns + campaignIDs.formUnion(newCampaigns.map(\.campaignID)) let indexPaths = campaigns.indices.prefix(newCampaigns.count) .map { IndexPath(row: $0, section: 0) } delegate?.stream(self, didAppendItemsAt: indexPaths) From 4f5c715755b1d7f9ae314b21d1ffbe3e45012edb Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 29 Jun 2023 16:01:22 -0400 Subject: [PATCH 078/175] Cleanup --- .../BlazeCampaignsViewController.swift | 37 +++++++------- .../Cards/Blaze/BlazeCampaignStatusView.swift | 17 ++----- .../Blaze/DashboardBlazeCampaignView.swift | 48 +++++++++---------- 3 files changed, 45 insertions(+), 57 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index 3e8a8a53bb41..e7a2661c27c7 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -9,7 +9,7 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, B image: UIImage(systemName: "plus"), style: .plain, target: self, - action: #selector(plusButtonTapped) + action: #selector(buttonCreateCampaignTapped) ) private lazy var tableView: UITableView = { @@ -17,7 +17,6 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, B tableView.translatesAutoresizingMaskIntoConstraints = false tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 128 - tableView.sectionFooterHeight = UITableView.automaticDimension tableView.separatorStyle = .none tableView.refreshControl = refreshControl refreshControl.addTarget(self, action: #selector(setNeedsToRefreshCampaigns), for: .valueChanged) @@ -57,9 +56,10 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, B setupNavBar() setupNoResults() - configure(with: stream) + stream.delegate = self stream.load() + // Refresh data automatically when new campaign is created NotificationCenter.default.addObserver(self, selector: #selector(setNeedsToRefreshCampaigns), name: .blazeCampaignCreated, object: nil) } @@ -69,16 +69,10 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, B tableView.sizeToFitFooterView() } - private func configure(with newStream: BlazeCampaignsStream) { - self.stream = newStream - newStream.delegate = self - tableView.reloadData() - reloadView() - } - - // MARK: - BlazeCampaignsStreamDelegate + // MARK: - Stream func stream(_ stream: BlazeCampaignsStream, didAppendItemsAt indexPaths: [IndexPath]) { + // Make sure the existing cells are not reloaded to avoid interfering with image loading UIView.performWithoutAnimation { tableView.insertRows(at: indexPaths, with: .none) } @@ -139,11 +133,14 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, B guard let self else { return } switch $0 { case .success: - self.configure(with: stream) + self.stream = stream + self.stream.delegate = self + self.tableView.reloadData() + self.reloadView() case .failure(let error): SVProgressHUD.showDismissibleError(withStatus: error.localizedDescription) } - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250)) { self.pendingStream = nil self.refreshControl.endRefreshing() } @@ -151,6 +148,11 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, B pendingStream = stream } + @objc private func buttonCreateCampaignTapped() { + BlazeEventsTracker.trackBlazeFlowStarted(for: .campaignsList) + BlazeFlowCoordinator.presentBlaze(in: self, source: .campaignsList, blog: blog) + } + // MARK: - Private private func setupView() { @@ -167,11 +169,6 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, B private func setupNoResults() { noResultsViewController.delegate = self } - - @objc private func plusButtonTapped() { - BlazeEventsTracker.trackBlazeFlowStarted(for: .campaignsList) - BlazeFlowCoordinator.presentBlaze(in: self, source: .campaignsList, blog: blog) - } } // MARK: - Table methods @@ -239,8 +236,8 @@ private extension BlazeCampaignsViewController { static let loadingTitle = NSLocalizedString("blaze.campaigns.loading.title", value: "Loading campaigns...", comment: "Displayed while Blaze campaigns are being loaded.") static let emptyTitle = NSLocalizedString("blaze.campaigns.empty.title", value: "You have no campaigns", comment: "Title displayed when there are no Blaze campaigns to display.") static let emptySubtitle = NSLocalizedString("blaze.campaigns.empty.subtitle", value: "You have not created any campaigns yet. Click promote to get started.", comment: "Text displayed when there are no Blaze campaigns to display.") - static let errorTitle = NSLocalizedString("Oops", comment: "Title for the view when there's an error loading Blaze campiagns.") - static let errorSubtitle = NSLocalizedString("There was an error loading campaigns.", comment: "Text displayed when there is a failure loading Blaze campaigns.") + static let errorTitle = NSLocalizedString("blaze.campaigns.errorTitle", value: "Oops", comment: "Title for the view when there's an error loading Blaze campiagns.") + static let errorSubtitle = NSLocalizedString("blaze.campaigns.errorMessage", value: "There was an error loading campaigns.", comment: "Text displayed when there is a failure loading Blaze campaigns.") } } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift index 1ed7960cbaf3..54e79092b649 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/BlazeCampaignStatusView.swift @@ -37,16 +37,17 @@ private extension BlazeCampaignStatusView { } } -struct BlazeCampaignStatusViewModel: Hashable { - private let status: BlazeCampaign.Status - +struct BlazeCampaignStatusViewModel { let isHidden: Bool let title: String let textColor: UIColor let backgroundColor: UIColor + init(campaign: BlazeCampaign) { + self.init(status: campaign.uiStatus) + } + init(status: BlazeCampaign.Status) { - self.status = status self.isHidden = status == .unknown self.title = status.localizedTitle @@ -92,14 +93,6 @@ struct BlazeCampaignStatusViewModel: Hashable { self.backgroundColor = .secondarySystemBackground } } - - func hash(into hasher: inout Hasher) { - status.hash(into: &hasher) - } - - static func ==(lhs: BlazeCampaignStatusViewModel, rhs: BlazeCampaignStatusViewModel) -> Bool { - lhs.status == rhs.status - } } extension BlazeCampaign.Status { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift index db8827dd4eec..84ba63790dac 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift @@ -97,42 +97,40 @@ private extension DashboardBlazeCampaignView { } } -struct BlazeCampaignViewModel: Hashable { +struct BlazeCampaignViewModel { let title: String let imageURL: URL? let impressions: Int let clicks: Int let budget: String - let isShowingStats: Bool - let status: BlazeCampaignStatusViewModel + var status: BlazeCampaignStatusViewModel { .init(campaign: campaign) } - init(campaign: BlazeCampaign) { - self.title = campaign.name ?? "–" - self.imageURL = campaign.contentConfig?.imageURL.flatMap(URL.init) - self.impressions = campaign.stats?.impressionsTotal ?? 0 - self.clicks = campaign.stats?.clicksTotal ?? 0 - self.budget = currencyFormatter.string(from: ((campaign.budgetCents ?? 0) / 100) as NSNumber) ?? "-" - self.isShowingStats = campaign.uiStatus.isShowingStats - self.status = BlazeCampaignStatusViewModel(status: campaign.uiStatus) - } -} - -private let currencyFormatter: NumberFormatter = { - let formatter = NumberFormatter() - formatter.numberStyle = .currency - formatter.currencyCode = "USD" - formatter.currencySymbol = "$" - formatter.maximumFractionDigits = 0 - return formatter -}() - -private extension BlazeCampaign.Status { var isShowingStats: Bool { - switch self { + switch campaign.uiStatus { case .created, .processing, .canceled, .approved, .rejected, .scheduled, .unknown: return false case .active, .finished: return true } } + + private let campaign: BlazeCampaign + + private let currencyFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.currencyCode = "USD" + formatter.currencySymbol = "$" + formatter.maximumFractionDigits = 0 + return formatter + }() + + init(campaign: BlazeCampaign) { + self.campaign = campaign + self.title = campaign.name ?? "–" + self.imageURL = campaign.contentConfig?.imageURL.flatMap(URL.init) + self.impressions = campaign.stats?.impressionsTotal ?? 0 + self.clicks = campaign.stats?.clicksTotal ?? 0 + self.budget = currencyFormatter.string(from: ((campaign.budgetCents ?? 0) / 100) as NSNumber) ?? "-" + } } From 572d202797f8121766a05dc064ec9191a5ab4b0a Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 29 Jun 2023 16:23:06 -0400 Subject: [PATCH 079/175] Use different design for error messages --- .../Blaze Campaigns/BlazeCampaignsViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index e7a2661c27c7..4a5e265acb4a 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -1,6 +1,6 @@ import UIKit -import SVProgressHUD import WordPressKit +import WordPressFlux final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, BlazeCampaignsStreamDelegate { // MARK: - Views @@ -138,7 +138,9 @@ final class BlazeCampaignsViewController: UIViewController, NoResultsViewHost, B self.tableView.reloadData() self.reloadView() case .failure(let error): - SVProgressHUD.showDismissibleError(withStatus: error.localizedDescription) + if self.refreshControl.isRefreshing { + ActionDispatcher.dispatch(NoticeAction.post(Notice(title: error.localizedDescription, feedbackType: .error))) + } } DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250)) { self.pendingStream = nil From 8b08d72e7bd130eb18e3fd3f59e1f0808617b338 Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Tue, 27 Jun 2023 19:26:26 -0400 Subject: [PATCH 080/175] Add the remaining social shares view to the post settings screen --- .../JetpackSocialNoConnectionView.swift | 1 + .../JetpackSocialRemainingSharesView.swift | 71 +++++++++++++++++++ ...SettingsViewController+JetpackSocial.swift | 55 +++++++++----- .../Post/PostSettingsViewController.h | 2 + .../Post/PostSettingsViewController.m | 60 ++++++++++------ .../PostSettingsViewController_Internal.h | 1 + .../icon-warning.imageset/Contents.json | 12 ++++ .../icon-warning.imageset/icon-warning.svg | 1 + WordPress/WordPress.xcodeproj/project.pbxproj | 6 ++ 9 files changed, 173 insertions(+), 36 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialRemainingSharesView.swift create mode 100644 WordPress/Jetpack/AppImages.xcassets/Social/icon-warning.imageset/Contents.json create mode 100644 WordPress/Jetpack/AppImages.xcassets/Social/icon-warning.imageset/icon-warning.svg diff --git a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift index 8e209285c50e..2c2f6fa6303a 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift @@ -53,6 +53,7 @@ extension JetpackSocialNoConnectionView { static func createHostController(with viewModel: JetpackSocialNoConnectionViewModel = JetpackSocialNoConnectionViewModel()) -> UIHostingController { let hostController = UIHostingController(rootView: JetpackSocialNoConnectionView(viewModel: viewModel)) hostController.view.translatesAutoresizingMaskIntoConstraints = false + hostController.view.backgroundColor = .listForeground return hostController } } diff --git a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialRemainingSharesView.swift b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialRemainingSharesView.swift new file mode 100644 index 000000000000..46e0560b7a62 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialRemainingSharesView.swift @@ -0,0 +1,71 @@ +import SwiftUI + +struct JetpackSocialRemainingSharesView: View { + + let viewModel: JetpackSocialRemainingSharesViewModel + + var body: some View { + VStack(alignment: .leading, spacing: 4.0) { + HStack(spacing: 8.0) { + if viewModel.displayWarning { + Image("icon-warning") + .resizable() + .frame(width: 16.0, height: 16.0) + } + remainingText + } + Text(Constants.subscribeText) + .font(.callout) + .foregroundColor(Color(UIColor.primary)) + .onTapGesture { + viewModel.onSubscribeTap() + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(EdgeInsets(top: 12.0, leading: 16.0, bottom: 12.0, trailing: 16.0)) + .background(Color(UIColor.listForeground)) + } + + private var remainingText: some View { + let sharesRemainingString = String(format: Constants.remainingTextFormat, viewModel.remaining, viewModel.limit) + let sharesRemaining = Text(sharesRemainingString).font(.callout) + if viewModel.displayWarning { + return sharesRemaining + } + let remainingDays = Text(Constants.remainingEndText).font(.callout).foregroundColor(.secondary) + return sharesRemaining + remainingDays + } + + private struct Constants { + static let remainingTextFormat = NSLocalizedString("social.remainingshares.text.format", + value: "%1$d/%2$d social shares remaining", + comment: "Beginning text of the remaining social shares a user has left." + + " %1$d is their current remaining shares. %2$d is their share limit." + + " This text is combined with ' in the next 30 days' if there is no warning displayed.") + static let remainingEndText = NSLocalizedString("social.remainingshares.text.part", + value: " in the next 30 days", + comment: "The second half of the remaining social shares a user has." + + " This is only displayed when there is no social limit warning.") + static let subscribeText = NSLocalizedString("social.remainingshares.subscribe", + value: "Subscribe now to share more", + comment: "Title for the button to subscribe to Jetpack Social on the remaining shares view") + } + +} + +struct JetpackSocialRemainingSharesViewModel { + let remaining: Int + let limit: Int + let displayWarning: Bool + let onSubscribeTap: () -> Void + + init(remaining: Int = 27, + limit: Int = 30, + displayWarning: Bool = false, + onSubscribeTap: @escaping () -> Void) { + self.remaining = remaining + self.limit = limit + self.displayWarning = displayWarning + self.onSubscribeTap = onSubscribeTap + } +} diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift index 3260ec8a44aa..a482e0e2a39a 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift @@ -1,6 +1,9 @@ +import SwiftUI extension PostSettingsViewController { + // MARK: - No connection view + @objc func showNoConnection() -> Bool { let isJetpackSocialEnabled = FeatureFlag.jetpackSocial.enabled let isNoConnectionViewHidden = UserPersistentStoreFactory.instance().bool(forKey: hideNoConnectionViewKey()) @@ -27,7 +30,37 @@ extension PostSettingsViewController { return viewController.view } - private func hideNoConnectionViewKey() -> String { + // MARK: - Remaining shares view + + @objc func showRemainingShares() -> Bool { + let isJetpackSocialEnabled = FeatureFlag.jetpackSocial.enabled + let blogSupportsPublicize = apost.blog.supportsPublicize() + let blogHasConnections = publicizeConnections.count > 0 + // TODO: Check if there's a share limit + + return isJetpackSocialEnabled + && blogSupportsPublicize + && blogHasConnections + } + + @objc func createRemainingSharesView() -> UIView { + let viewModel = JetpackSocialRemainingSharesViewModel { + // TODO + print("Subscribe tap") + } + let hostController = UIHostingController(rootView: JetpackSocialRemainingSharesView(viewModel: viewModel)) + hostController.view.translatesAutoresizingMaskIntoConstraints = false + hostController.view.backgroundColor = .listForeground + return hostController.view + } + +} + +// MARK: - Private methods + +private extension PostSettingsViewController { + + func hideNoConnectionViewKey() -> String { guard let dotComID = apost.blog.dotComID?.stringValue else { return Constants.hideNoConnectionViewKey } @@ -35,17 +68,17 @@ extension PostSettingsViewController { return "\(dotComID)-\(Constants.hideNoConnectionViewKey)" } - private func onConnectTap() -> () -> Void { + func onConnectTap() -> () -> Void { return { [weak self] in guard let blog = self?.apost.blog, - let controller = SharingViewController(blog: blog, delegate: self) else { + let controller = SharingViewController(blog: blog, delegate: nil) else { return } self?.navigationController?.pushViewController(controller, animated: true) } } - private func onNotNowTap() -> () -> Void { + func onNotNowTap() -> () -> Void { return { [weak self] in guard let key = self?.hideNoConnectionViewKey() else { return @@ -55,7 +88,7 @@ extension PostSettingsViewController { } } - private func availableServices() -> [PublicizeService] { + func availableServices() -> [PublicizeService] { let context = apost.managedObjectContext ?? ContextManager.shared.mainContext let services = try? PublicizeService.allPublicizeServices(in: context) return services ?? [] @@ -63,18 +96,8 @@ extension PostSettingsViewController { // MARK: - Constants - private struct Constants { + struct Constants { static let hideNoConnectionViewKey = "post-settings-social-no-connection-view-hidden" } } - -// MARK: - SharingViewControllerDelegate - -extension PostSettingsViewController: SharingViewControllerDelegate { - - public func didChangePublicizeServices() { - tableView.reloadData() - } - -} diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.h b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.h index a611ab1d8b16..a1409455e82f 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.h +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.h @@ -17,4 +17,6 @@ @property (nonatomic, weak, nullable) id featuredImageDelegate; +- (void)reloadData; + @end diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m index bd1af6d5988b..2455b7f05a4d 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m @@ -41,7 +41,9 @@ typedef NS_ENUM(NSInteger, PostSettingsRow) { PostSettingsRowShareConnection, PostSettingsRowShareMessage, PostSettingsRowSlug, - PostSettingsRowExcerpt + PostSettingsRowExcerpt, + PostSettingsRowSocialNoConnections, + PostSettingsRowSocialRemainingShares }; static CGFloat CellHeight = 44.0f; @@ -359,6 +361,7 @@ - (void)reloadData { self.passwordTextField.text = self.apost.password; + [self configureSections]; [self.tableView reloadData]; } @@ -393,14 +396,16 @@ - (void)configureSections { NSNumber *stickyPostSection = @(PostSettingsSectionStickyPost); NSNumber *disabledTwitterSection = @(PostSettingsSectionDisabledTwitter); + NSNumber *remainingSharesSection = @(PostSettingsSectionSharesRemaining); NSMutableArray *sections = [@[ @(PostSettingsSectionTaxonomy), - @(PostSettingsSectionMeta), - @(PostSettingsSectionFormat), - @(PostSettingsSectionFeaturedImage), - stickyPostSection, - @(PostSettingsSectionShare), - disabledTwitterSection, - @(PostSettingsSectionMoreOptions) ] mutableCopy]; + @(PostSettingsSectionMeta), + @(PostSettingsSectionFormat), + @(PostSettingsSectionFeaturedImage), + stickyPostSection, + @(PostSettingsSectionShare), + disabledTwitterSection, + remainingSharesSection, + @(PostSettingsSectionMoreOptions) ] mutableCopy]; // Remove sticky post section for self-hosted non Jetpack site // and non admin user // @@ -412,6 +417,10 @@ - (void)configureSections [sections removeObject:disabledTwitterSection]; } + if (![self showRemainingShares]) { + [sections removeObject:remainingSharesSection]; + } + self.sections = [sections copy]; } @@ -428,28 +437,22 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger NSInteger sec = [[self.sections objectAtIndex:section] integerValue]; if (sec == PostSettingsSectionTaxonomy) { return 2; - } else if (sec == PostSettingsSectionMeta) { return [self.postMetaSectionRows count]; - } else if (sec == PostSettingsSectionFormat) { return 1; - } else if (sec == PostSettingsSectionFeaturedImage) { return 1; - } else if (sec == PostSettingsSectionStickyPost) { return 1; - } else if (sec == PostSettingsSectionShare) { return [self numberOfRowsForShareSection]; - } else if (sec == PostSettingsSectionDisabledTwitter) { return self.unsupportedConnections.count; - + } else if (sec == PostSettingsSectionSharesRemaining) { + return 1; } else if (sec == PostSettingsSectionMoreOptions) { return 2; - } return 0; @@ -564,6 +567,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell = [self configureStickyPostCellForIndexPath:indexPath]; } else if (sec == PostSettingsSectionShare || sec == PostSettingsSectionDisabledTwitter) { cell = [self showNoConnection] ? [self configureNoConnectionCell] : [self configureShareCellForIndexPath:indexPath]; + } else if (sec == PostSettingsSectionSharesRemaining) { + cell = [self configureRemainingSharesCell]; } else if (sec == PostSettingsSectionMoreOptions) { cell = [self configureMoreOptionsCellForIndexPath:indexPath]; } @@ -1362,12 +1367,27 @@ - (void)updateSearchBarForPicker:(WPMediaPickerViewController *)picker #pragma mark - Jetpack Social +- (UITableViewCell *)configureGenericCellWith:(UIView *)view { + UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:TableViewGenericCellIdentifier]; + for (UIView *subview in cell.contentView.subviews) { + [subview removeFromSuperview]; + } + [cell.contentView addSubview:view]; + [cell.contentView pinSubviewToAllEdges:view]; + return cell; +} + - (UITableViewCell *)configureNoConnectionCell { - UIView *noConnectionView = [self createNoConnectionView]; - UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:TableViewGenericCellIdentifier]; - [cell.contentView addSubview:noConnectionView]; - [cell.contentView pinSubviewToAllEdges:noConnectionView]; + UITableViewCell *cell = [self configureGenericCellWith:[self createNoConnectionView]]; + cell.tag = PostSettingsRowSocialNoConnections; + return cell; +} + +- (UITableViewCell *)configureRemainingSharesCell +{ + UITableViewCell *cell = [self configureGenericCellWith:[self createRemainingSharesView]]; + cell.tag = PostSettingsRowSocialRemainingShares; return cell; } diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController_Internal.h b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController_Internal.h index 6406aff1b79a..bccf3b37aa83 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController_Internal.h +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController_Internal.h @@ -8,6 +8,7 @@ typedef enum { PostSettingsSectionStickyPost, PostSettingsSectionShare, PostSettingsSectionDisabledTwitter, // NOTE: Clean up when Twitter has been removed from Publicize services. + PostSettingsSectionSharesRemaining, PostSettingsSectionGeolocation, PostSettingsSectionMoreOptions } PostSettingsSection; diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-warning.imageset/Contents.json b/WordPress/Jetpack/AppImages.xcassets/Social/icon-warning.imageset/Contents.json new file mode 100644 index 000000000000..5e1bdf242188 --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/Social/icon-warning.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-warning.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-warning.imageset/icon-warning.svg b/WordPress/Jetpack/AppImages.xcassets/Social/icon-warning.imageset/icon-warning.svg new file mode 100644 index 000000000000..2fb81dfc2ef3 --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/Social/icon-warning.imageset/icon-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index ec0808d341c1..eb27c965973d 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2086,6 +2086,8 @@ 83B1D038282C62620061D911 /* BloggingPromptsAttribution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B1D036282C62620061D911 /* BloggingPromptsAttribution.swift */; }; 83C972E0281C45AB0049E1FE /* Post+BloggingPrompts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C972DF281C45AB0049E1FE /* Post+BloggingPrompts.swift */; }; 83C972E1281C45AB0049E1FE /* Post+BloggingPrompts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C972DF281C45AB0049E1FE /* Post+BloggingPrompts.swift */; }; + 83DC5C462A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DC5C452A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift */; }; + 83DC5C472A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DC5C452A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift */; }; 83EF3D7B2937D703000AF9BF /* SharedDataIssueSolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED65D78293511E4008071BF /* SharedDataIssueSolver.swift */; }; 83EF3D7F2937F08C000AF9BF /* SharedDataIssueSolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EF3D7C2937E969000AF9BF /* SharedDataIssueSolverTests.swift */; }; 83F3E26011275E07004CD686 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83F3E25F11275E07004CD686 /* MapKit.framework */; }; @@ -7440,6 +7442,7 @@ 839B150A2795DEE0009F5E77 /* UIView+Margins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Margins.swift"; sourceTree = ""; }; 83B1D036282C62620061D911 /* BloggingPromptsAttribution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloggingPromptsAttribution.swift; sourceTree = ""; }; 83C972DF281C45AB0049E1FE /* Post+BloggingPrompts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Post+BloggingPrompts.swift"; sourceTree = ""; }; + 83DC5C452A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackSocialRemainingSharesView.swift; sourceTree = ""; }; 83EF3D7C2937E969000AF9BF /* SharedDataIssueSolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedDataIssueSolverTests.swift; sourceTree = ""; }; 83F3E25F11275E07004CD686 /* MapKit.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 83F3E2D211276371004CD686 /* CoreLocation.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; @@ -13352,6 +13355,7 @@ isa = PBXGroup; children = ( 83914BD02A2E89F30017A588 /* JetpackSocialNoConnectionView.swift */, + 83DC5C452A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift */, ); path = Social; sourceTree = ""; @@ -20957,6 +20961,7 @@ 8BA77BCF2483415400E1EBBF /* ReaderDetailToolbar.swift in Sources */, E102B7901E714F24007928E8 /* RecentSitesService.swift in Sources */, E1D7FF381C74EB0E00E7E5E5 /* PlanService.swift in Sources */, + 83DC5C462A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift in Sources */, F1655B4822A6C2FA00227BFB /* Routes+Mbar.swift in Sources */, 32CA6EC02390C61F00B51347 /* PostListEditorPresenter.swift in Sources */, 80D9CFF429DCA53E00FE3400 /* DashboardPagesListCardCell.swift in Sources */, @@ -23993,6 +23998,7 @@ FABB21772602FC2C00C8785C /* JetpackConnectionViewController.swift in Sources */, 803BB97A2959543D00B3F6D6 /* RootViewCoordinator.swift in Sources */, FABB21782602FC2C00C8785C /* NotificationActionsService.swift in Sources */, + 83DC5C472A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift in Sources */, FABB21792602FC2C00C8785C /* JetpackScanService.swift in Sources */, FABB217A2602FC2C00C8785C /* FilterSheetView.swift in Sources */, FABB217B2602FC2C00C8785C /* WPAddPostCategoryViewController.m in Sources */, From d909925275b5bb5843f0e698fe637a397120bc8e Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:39:15 -0400 Subject: [PATCH 081/175] Update post settings remaining shares view name and string keys --- ...> JetpackSocialSettingsRemainingSharesView.swift} | 8 ++++---- .../PostSettingsViewController+JetpackSocial.swift | 2 +- WordPress/WordPress.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) rename WordPress/Classes/ViewRelated/Jetpack/Social/{JetpackSocialRemainingSharesView.swift => JetpackSocialSettingsRemainingSharesView.swift} (88%) diff --git a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialRemainingSharesView.swift b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialSettingsRemainingSharesView.swift similarity index 88% rename from WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialRemainingSharesView.swift rename to WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialSettingsRemainingSharesView.swift index 46e0560b7a62..5ae47080ef46 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialRemainingSharesView.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialSettingsRemainingSharesView.swift @@ -1,6 +1,6 @@ import SwiftUI -struct JetpackSocialRemainingSharesView: View { +struct JetpackSocialSettingsRemainingSharesView: View { let viewModel: JetpackSocialRemainingSharesViewModel @@ -37,16 +37,16 @@ struct JetpackSocialRemainingSharesView: View { } private struct Constants { - static let remainingTextFormat = NSLocalizedString("social.remainingshares.text.format", + static let remainingTextFormat = NSLocalizedString("postsettings.social.remainingshares.text.format", value: "%1$d/%2$d social shares remaining", comment: "Beginning text of the remaining social shares a user has left." + " %1$d is their current remaining shares. %2$d is their share limit." + " This text is combined with ' in the next 30 days' if there is no warning displayed.") - static let remainingEndText = NSLocalizedString("social.remainingshares.text.part", + static let remainingEndText = NSLocalizedString("postsettings.social.remainingshares.text.part", value: " in the next 30 days", comment: "The second half of the remaining social shares a user has." + " This is only displayed when there is no social limit warning.") - static let subscribeText = NSLocalizedString("social.remainingshares.subscribe", + static let subscribeText = NSLocalizedString("postsettings.social.remainingshares.subscribe", value: "Subscribe now to share more", comment: "Title for the button to subscribe to Jetpack Social on the remaining shares view") } diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift index a482e0e2a39a..f72fc1e1f7a1 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift @@ -48,7 +48,7 @@ extension PostSettingsViewController { // TODO print("Subscribe tap") } - let hostController = UIHostingController(rootView: JetpackSocialRemainingSharesView(viewModel: viewModel)) + let hostController = UIHostingController(rootView: JetpackSocialSettingsRemainingSharesView(viewModel: viewModel)) hostController.view.translatesAutoresizingMaskIntoConstraints = false hostController.view.backgroundColor = .listForeground return hostController.view diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index eb27c965973d..328876d7d222 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2086,8 +2086,8 @@ 83B1D038282C62620061D911 /* BloggingPromptsAttribution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B1D036282C62620061D911 /* BloggingPromptsAttribution.swift */; }; 83C972E0281C45AB0049E1FE /* Post+BloggingPrompts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C972DF281C45AB0049E1FE /* Post+BloggingPrompts.swift */; }; 83C972E1281C45AB0049E1FE /* Post+BloggingPrompts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C972DF281C45AB0049E1FE /* Post+BloggingPrompts.swift */; }; - 83DC5C462A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DC5C452A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift */; }; - 83DC5C472A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DC5C452A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift */; }; + 83DC5C462A4B769000DAA422 /* JetpackSocialSettingsRemainingSharesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DC5C452A4B769000DAA422 /* JetpackSocialSettingsRemainingSharesView.swift */; }; + 83DC5C472A4B769000DAA422 /* JetpackSocialSettingsRemainingSharesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DC5C452A4B769000DAA422 /* JetpackSocialSettingsRemainingSharesView.swift */; }; 83EF3D7B2937D703000AF9BF /* SharedDataIssueSolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED65D78293511E4008071BF /* SharedDataIssueSolver.swift */; }; 83EF3D7F2937F08C000AF9BF /* SharedDataIssueSolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83EF3D7C2937E969000AF9BF /* SharedDataIssueSolverTests.swift */; }; 83F3E26011275E07004CD686 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83F3E25F11275E07004CD686 /* MapKit.framework */; }; @@ -7442,7 +7442,7 @@ 839B150A2795DEE0009F5E77 /* UIView+Margins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Margins.swift"; sourceTree = ""; }; 83B1D036282C62620061D911 /* BloggingPromptsAttribution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloggingPromptsAttribution.swift; sourceTree = ""; }; 83C972DF281C45AB0049E1FE /* Post+BloggingPrompts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Post+BloggingPrompts.swift"; sourceTree = ""; }; - 83DC5C452A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackSocialRemainingSharesView.swift; sourceTree = ""; }; + 83DC5C452A4B769000DAA422 /* JetpackSocialSettingsRemainingSharesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackSocialSettingsRemainingSharesView.swift; sourceTree = ""; }; 83EF3D7C2937E969000AF9BF /* SharedDataIssueSolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedDataIssueSolverTests.swift; sourceTree = ""; }; 83F3E25F11275E07004CD686 /* MapKit.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 83F3E2D211276371004CD686 /* CoreLocation.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; @@ -13355,7 +13355,7 @@ isa = PBXGroup; children = ( 83914BD02A2E89F30017A588 /* JetpackSocialNoConnectionView.swift */, - 83DC5C452A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift */, + 83DC5C452A4B769000DAA422 /* JetpackSocialSettingsRemainingSharesView.swift */, ); path = Social; sourceTree = ""; @@ -20961,7 +20961,7 @@ 8BA77BCF2483415400E1EBBF /* ReaderDetailToolbar.swift in Sources */, E102B7901E714F24007928E8 /* RecentSitesService.swift in Sources */, E1D7FF381C74EB0E00E7E5E5 /* PlanService.swift in Sources */, - 83DC5C462A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift in Sources */, + 83DC5C462A4B769000DAA422 /* JetpackSocialSettingsRemainingSharesView.swift in Sources */, F1655B4822A6C2FA00227BFB /* Routes+Mbar.swift in Sources */, 32CA6EC02390C61F00B51347 /* PostListEditorPresenter.swift in Sources */, 80D9CFF429DCA53E00FE3400 /* DashboardPagesListCardCell.swift in Sources */, @@ -23998,7 +23998,7 @@ FABB21772602FC2C00C8785C /* JetpackConnectionViewController.swift in Sources */, 803BB97A2959543D00B3F6D6 /* RootViewCoordinator.swift in Sources */, FABB21782602FC2C00C8785C /* NotificationActionsService.swift in Sources */, - 83DC5C472A4B769000DAA422 /* JetpackSocialRemainingSharesView.swift in Sources */, + 83DC5C472A4B769000DAA422 /* JetpackSocialSettingsRemainingSharesView.swift in Sources */, FABB21792602FC2C00C8785C /* JetpackScanService.swift in Sources */, FABB217A2602FC2C00C8785C /* FilterSheetView.swift in Sources */, FABB217B2602FC2C00C8785C /* WPAddPostCategoryViewController.m in Sources */, From a01728ce2fd3fa8c429e2e014b51d8efe3c312b8 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 11:16:20 +1200 Subject: [PATCH 082/175] Break retain cycle between TopicsCollectionView and ReaderTopicCollectionViewCoordinator --- ...ReaderTopicCollectionViewCoordinator.swift | 20 +++++++++---------- .../Tags View/TopicsCollectionView.swift | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift b/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift index 5cc04a108593..7e2ace5ea4e6 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift @@ -40,22 +40,13 @@ class ReaderTopicCollectionViewCoordinator: NSObject { weak var delegate: ReaderTopicCollectionViewCoordinatorDelegate? - let collectionView: UICollectionView + unowned let collectionView: UICollectionView var topics: [String] { didSet { reloadData() } } - deinit { - guard let layout = collectionView.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { - return - } - - layout.isExpanded = false - layout.invalidateLayout() - } - init(collectionView: UICollectionView, topics: [String]) { self.collectionView = collectionView self.topics = topics @@ -65,6 +56,15 @@ class ReaderTopicCollectionViewCoordinator: NSObject { configureCollectionView() } + func invalidate() { + guard let layout = collectionView.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { + return + } + + layout.isExpanded = false + layout.invalidateLayout() + } + func reloadData() { collectionView.reloadData() collectionView.invalidateIntrinsicContentSize() diff --git a/WordPress/Classes/ViewRelated/Reader/Tags View/TopicsCollectionView.swift b/WordPress/Classes/ViewRelated/Reader/Tags View/TopicsCollectionView.swift index 90d0e1484556..a6984eba92e2 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tags View/TopicsCollectionView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tags View/TopicsCollectionView.swift @@ -27,6 +27,10 @@ class TopicsCollectionView: DynamicHeightCollectionView { commonInit() } + deinit { + coordinator?.invalidate() + } + func commonInit() { collectionViewLayout = ReaderInterestsCollectionViewFlowLayout() From 740d5e99732fd5ee6f4c47b59eb9604573642f95 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 11:20:24 +1200 Subject: [PATCH 083/175] Pass collection view instance as a function argument --- .../ReaderTopicCollectionViewCoordinator.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift b/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift index 7e2ace5ea4e6..6e801380bfb7 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift @@ -53,7 +53,7 @@ class ReaderTopicCollectionViewCoordinator: NSObject { super.init() - configureCollectionView() + configure(collectionView) } func invalidate() { @@ -78,7 +78,7 @@ class ReaderTopicCollectionViewCoordinator: NSObject { layout.isExpanded = state == .expanded } - private func configureCollectionView() { + private func configure(_ collectionView: UICollectionView) { collectionView.isAccessibilityElement = false collectionView.delegate = self collectionView.dataSource = self @@ -105,7 +105,7 @@ class ReaderTopicCollectionViewCoordinator: NSObject { layout.allowsCentering = false } - private func sizeForCell(title: String) -> CGSize { + private func sizeForCell(title: String, of collectionView: UICollectionView)-> CGSize { let attributes: [NSAttributedString.Key: Any] = [ .font: ReaderInterestsStyleGuide.compactCellLabelTitleFont ] @@ -206,7 +206,7 @@ extension ReaderTopicCollectionViewCoordinator: UICollectionViewDelegateFlowLayo } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return sizeForCell(title: topics[indexPath.row]) + return sizeForCell(title: topics[indexPath.row], of: collectionView) } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { @@ -225,6 +225,6 @@ extension ReaderTopicCollectionViewCoordinator: ReaderInterestsCollectionViewFlo func collectionView(_ collectionView: UICollectionView, layout: ReaderInterestsCollectionViewFlowLayout, sizeForOverflowItem at: IndexPath, remainingItems: Int?) -> CGSize { let title = string(for: remainingItems) - return sizeForCell(title: title) + return sizeForCell(title: title, of: collectionView) } } From 83b3dc0b0ae4c4ee7b6c419a0e7805d491415d3a Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 11:21:26 +1200 Subject: [PATCH 084/175] Changing unowned collection view reference to weak --- .../ReaderTopicCollectionViewCoordinator.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift b/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift index 6e801380bfb7..ff36c31c1667 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift @@ -40,7 +40,7 @@ class ReaderTopicCollectionViewCoordinator: NSObject { weak var delegate: ReaderTopicCollectionViewCoordinatorDelegate? - unowned let collectionView: UICollectionView + weak var collectionView: UICollectionView? var topics: [String] { didSet { reloadData() @@ -57,7 +57,7 @@ class ReaderTopicCollectionViewCoordinator: NSObject { } func invalidate() { - guard let layout = collectionView.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { + guard let layout = collectionView?.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { return } @@ -66,12 +66,12 @@ class ReaderTopicCollectionViewCoordinator: NSObject { } func reloadData() { - collectionView.reloadData() - collectionView.invalidateIntrinsicContentSize() + collectionView?.reloadData() + collectionView?.invalidateIntrinsicContentSize() } func changeState(_ state: ReaderTopicCollectionViewState) { - guard let layout = collectionView.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { + guard let layout = collectionView?.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { return } @@ -193,7 +193,7 @@ extension ReaderTopicCollectionViewCoordinator: UICollectionViewDelegateFlowLayo } @objc func toggleExpanded(_ sender: ReaderInterestsCollectionViewCell) { - guard let layout = collectionView.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { + guard let layout = collectionView?.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { return } From 58ae07183d5b089a3431e6fc73acb8ad9e9fc344 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 16:26:39 +1200 Subject: [PATCH 085/175] Fix discarded notification observers in StatsWidgetsStore --- .../Classes/Stores/StatsWidgetsStore.swift | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/WordPress/Classes/Stores/StatsWidgetsStore.swift b/WordPress/Classes/Stores/StatsWidgetsStore.swift index 2647d0de02a8..0d2bfae230ca 100644 --- a/WordPress/Classes/Stores/StatsWidgetsStore.swift +++ b/WordPress/Classes/Stores/StatsWidgetsStore.swift @@ -258,22 +258,21 @@ private extension StatsWidgetsStore { /// Observes WPAccountDefaultWordPressComAccountChanged notification and reloads widget data based on the state of account. /// The site data is not yet loaded after this notification and widget data cannot be cached for newly signed in account. func observeAccountChangesForWidgets() { - NotificationCenter.default.addObserver(forName: .WPAccountDefaultWordPressComAccountChanged, - object: nil, - queue: nil) { notification in + NotificationCenter.default.addObserver(self, selector: #selector(handleAccountChangedNotification), name: .WPAccountDefaultWordPressComAccountChanged, object: nil) + } - UserDefaults(suiteName: WPAppGroupName)?.setValue(AccountHelper.isLoggedIn, forKey: AppConfiguration.Widget.Stats.userDefaultsLoggedInKey) + @objc func handleAccountChangedNotification() { + UserDefaults(suiteName: WPAppGroupName)?.setValue(AccountHelper.isLoggedIn, forKey: AppConfiguration.Widget.Stats.userDefaultsLoggedInKey) - if !AccountHelper.isLoggedIn { - HomeWidgetTodayData.delete() - HomeWidgetThisWeekData.delete() - HomeWidgetAllTimeData.delete() + if !AccountHelper.isLoggedIn { + HomeWidgetTodayData.delete() + HomeWidgetThisWeekData.delete() + HomeWidgetAllTimeData.delete() - UserDefaults(suiteName: WPAppGroupName)?.setValue(nil, forKey: AppConfiguration.Widget.Stats.userDefaultsSiteIdKey) - WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.todayKind) - WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.thisWeekKind) - WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.allTimeKind) - } + UserDefaults(suiteName: WPAppGroupName)?.setValue(nil, forKey: AppConfiguration.Widget.Stats.userDefaultsSiteIdKey) + WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.todayKind) + WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.thisWeekKind) + WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.allTimeKind) } } @@ -286,11 +285,11 @@ private extension StatsWidgetsStore { /// Observes applicationLaunchCompleted notification and runs migration. func observeApplicationLaunched() { - NotificationCenter.default.addObserver(forName: NSNotification.Name.applicationLaunchCompleted, - object: nil, - queue: nil) { [weak self] _ in - self?.handleJetpackWidgetsMigration() - } + NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationLaunchCompleted), name: NSNotification.Name.applicationLaunchCompleted, object: nil) + } + + @objc private func handleApplicationLaunchCompleted() { + handleJetpackWidgetsMigration() } func observeJetpackFeaturesState() { From be52be7ec08dfa68e787e50f53e124921c715949 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 16:27:43 +1200 Subject: [PATCH 086/175] Fix discarded notification observer in ContentMigrationCoordinator --- .../Migration/ContentMigrationCoordinator.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/WordPress/Classes/Utility/Migration/ContentMigrationCoordinator.swift b/WordPress/Classes/Utility/Migration/ContentMigrationCoordinator.swift index 033162e5d7d5..4430acd0312d 100644 --- a/WordPress/Classes/Utility/Migration/ContentMigrationCoordinator.swift +++ b/WordPress/Classes/Utility/Migration/ContentMigrationCoordinator.swift @@ -141,15 +141,16 @@ private extension ContentMigrationCoordinator { return } - notificationCenter.addObserver(forName: .WPAccountDefaultWordPressComAccountChanged, object: nil, queue: nil) { [weak self] notification in - // nil notification object means it's a logout event. - guard let self, - notification.object == nil else { - return - } + notificationCenter.addObserver(self, selector: #selector(handleAccountChangedNotification(_:)), name: .WPAccountDefaultWordPressComAccountChanged, object: nil) + } - self.cleanupExportedDataIfNeeded() + @objc private func handleAccountChangedNotification(_ notification: Foundation.Notification) { + // nil notification object means it's a logout event. + guard notification.object == nil else { + return } + + self.cleanupExportedDataIfNeeded() } /// A "middleware" logic that attempts to record (or clear) any migration error to the App Group space From 254f61a543c7b614e0556b23b1f18393399d67a6 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 19:40:20 +1200 Subject: [PATCH 087/175] Fix discarded quick start notification observers --- .../BlogDashboardViewController.swift | 43 +++++++++---------- .../DashboardQuickActionsCardCell.swift | 9 +++- .../NewQuickStartChecklistView.swift | 9 +++- .../Quick Start/QuickStartChecklistView.swift | 9 +++- .../MySiteViewController+QuickStart.swift | 4 +- .../Blog/My Site/MySiteViewController.swift | 2 + .../SitePickerViewController+QuickStart.swift | 7 ++- .../SitePickerViewController.swift | 2 + .../ReaderTabViewController.swift | 12 +++--- 9 files changed, 60 insertions(+), 37 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift index 464d5abb8f2f..e6b2a62a5aea 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/BlogDashboardViewController.swift @@ -159,30 +159,27 @@ final class BlogDashboardViewController: UIViewController { } private func addQuickStartObserver() { - NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in - - guard let self = self else { - return - } - - if let info = notification.userInfo, - let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement { - - switch element { - case .setupQuickStart: - self.loadCardsFromCache() - self.displayQuickStart() - case .updateQuickStart: - self.loadCardsFromCache() - case .stats, .mediaScreen: - if self.embeddedInScrollView { - self.mySiteScrollView?.scrollToTop(animated: true) - } else { - self.collectionView.scrollToTop(animated: true) - } - default: - break + NotificationCenter.default.addObserver(self, selector: #selector(handleQuickStartTourElementChangedNotification(_:)), name: .QuickStartTourElementChangedNotification, object: nil) + } + + @objc private func handleQuickStartTourElementChangedNotification(_ notification: Foundation.Notification) { + if let info = notification.userInfo, + let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement { + + switch element { + case .setupQuickStart: + self.loadCardsFromCache() + self.displayQuickStart() + case .updateQuickStart: + self.loadCardsFromCache() + case .stats, .mediaScreen: + if self.embeddedInScrollView { + self.mySiteScrollView?.scrollToTop(animated: true) + } else { + self.collectionView.scrollToTop(animated: true) } + default: + break } } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift index 1657074621ec..c1e439313e05 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift @@ -48,6 +48,8 @@ final class DashboardQuickActionsCardCell: UICollectionViewCell, Reusable { return button }() + fileprivate var quickStartObserver: NSObjectProtocol? + override init(frame: CGRect) { super.init(frame: frame) setup() @@ -142,7 +144,7 @@ extension DashboardQuickActionsCardCell { } private func startObservingQuickStart() { - NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in + quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in if let info = notification.userInfo, let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement { @@ -170,7 +172,10 @@ extension DashboardQuickActionsCardCell { } private func stopObservingQuickStart() { - NotificationCenter.default.removeObserver(self) + if let quickStartObserver { + NotificationCenter.default.removeObserver(quickStartObserver) + } + quickStartObserver = nil } private func autoScrollToStatsButton() { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/NewQuickStartChecklistView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/NewQuickStartChecklistView.swift index 0a340b22366b..62aa1624feb8 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/NewQuickStartChecklistView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/NewQuickStartChecklistView.swift @@ -99,6 +99,8 @@ final class NewQuickStartChecklistView: UIView, QuickStartChecklistConfigurable return imageView }() + fileprivate var quickStartObserver: NSObjectProtocol? + // MARK: - Init override init(frame: CGRect) { @@ -197,7 +199,7 @@ extension NewQuickStartChecklistView { } private func startObservingQuickStart() { - NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in + quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in guard let userInfo = notification.userInfo, let element = userInfo[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement, @@ -210,7 +212,10 @@ extension NewQuickStartChecklistView { } private func stopObservingQuickStart() { - NotificationCenter.default.removeObserver(self) + if let quickStartObserver { + NotificationCenter.default.removeObserver(quickStartObserver) + } + quickStartObserver = nil } @objc private func didTap() { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/QuickStartChecklistView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/QuickStartChecklistView.swift index 300c7477e2ec..70771abc0f5d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/QuickStartChecklistView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/QuickStartChecklistView.swift @@ -70,6 +70,8 @@ final class QuickStartChecklistView: UIView, QuickStartChecklistConfigurable { return view }() + fileprivate var quickStartObserver: NSObjectProtocol? + override init(frame: CGRect) { super.init(frame: frame) setupViews() @@ -134,7 +136,7 @@ extension QuickStartChecklistView { } private func startObservingQuickStart() { - NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in + quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in guard let userInfo = notification.userInfo, let element = userInfo[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement, @@ -147,7 +149,10 @@ extension QuickStartChecklistView { } private func stopObservingQuickStart() { - NotificationCenter.default.removeObserver(self) + if let quickStartObserver { + NotificationCenter.default.removeObserver(quickStartObserver) + } + quickStartObserver = nil } @objc private func didTap() { diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+QuickStart.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+QuickStart.swift index be911fbddf9c..d29401d72d6e 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+QuickStart.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+QuickStart.swift @@ -1,9 +1,11 @@ import UIKit +private var observerKey = 0 + extension MySiteViewController { func startObservingQuickStart() { - NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] (notification) in + quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] (notification) in if let info = notification.userInfo, let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement { diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift index 4812d2cf0152..87ee52d57e8d 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift @@ -27,6 +27,8 @@ class MySiteViewController: UIViewController, NoResultsViewHost { } } + var quickStartObserver: NSObjectProtocol? + private var isShowingDashboard: Bool { return segmentedControl.selectedSegmentIndex == Section.dashboard.rawValue } diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+QuickStart.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+QuickStart.swift index 528b4d52a487..35c75486e827 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+QuickStart.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+QuickStart.swift @@ -10,7 +10,7 @@ extension SitePickerViewController { } func startObservingQuickStart() { - NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] (notification) in + quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] (notification) in guard self?.blog.managedObjectContext != nil else { return } @@ -22,7 +22,10 @@ extension SitePickerViewController { } func stopObservingQuickStart() { - NotificationCenter.default.removeObserver(self) + if let quickStartObserver { + NotificationCenter.default.removeObserver(quickStartObserver) + } + quickStartObserver = nil } func startAlertTimer() { diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift index bc41dc10ac05..a6a101a69829 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift @@ -21,6 +21,8 @@ final class SitePickerViewController: UIViewController { let blogService: BlogService let mediaService: MediaService + var quickStartObserver: NSObjectProtocol? + private(set) lazy var blogDetailHeaderView: BlogDetailHeaderView = { let headerView = BlogDetailHeaderView(items: []) headerView.translatesAutoresizingMaskIntoConstraints = false diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift index 5a2ef070bc2c..92b63fbc272b 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift @@ -140,11 +140,13 @@ extension ReaderTabViewController { // MARK: Observing Quick Start extension ReaderTabViewController { private func startObservingQuickStart() { - NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in - if let info = notification.userInfo, - let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement { - self?.settingsButton.shouldShowSpotlight = element == .readerDiscoverSettings - } + NotificationCenter.default.addObserver(self, selector: #selector(handleQuickStartTourElementChangedNotification(_:)), name: .QuickStartTourElementChangedNotification, object: nil) + } + + @objc private func handleQuickStartTourElementChangedNotification(_ notification: Foundation.Notification) { + if let info = notification.userInfo, + let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement { + settingsButton.shouldShowSpotlight = element == .readerDiscoverSettings } } } From 36b961ff588cfc4ba062c90b5f4ad6923fb3dad6 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 19:44:08 +1200 Subject: [PATCH 088/175] Capture weak references in some notification observers --- .../CollapsableHeaderViewController.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Collapsable Header/CollapsableHeaderViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/Collapsable Header/CollapsableHeaderViewController.swift index d63f085bf29a..3c9c36d6dab2 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Collapsable Header/CollapsableHeaderViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/Collapsable Header/CollapsableHeaderViewController.swift @@ -759,19 +759,22 @@ extension CollapsableHeaderViewController: UIScrollViewDelegate { // MARK: - Keyboard Adjustments extension CollapsableHeaderViewController { private func startObservingKeyboardChanges() { - let willShowObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { (notification) in + let willShowObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { [weak self] (notification) in + guard let self else { return } UIView.animate(withKeyboard: notification) { (_, endFrame) in self.scrollableContainerBottomConstraint.constant = endFrame.height - self.footerHeight } } - let willHideObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { (notification) in + let willHideObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { [weak self] (notification) in + guard let self else { return } UIView.animate(withKeyboard: notification) { (_, _) in self.scrollableContainerBottomConstraint.constant = 0 } } - let willChangeFrameObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { (notification) in + let willChangeFrameObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { [weak self] (notification) in + guard let self else { return } UIView.animate(withKeyboard: notification) { (_, endFrame) in self.scrollableContainerBottomConstraint.constant = endFrame.height - self.footerHeight } From 26d458e491003dd296796f4d27bc5545b00babea Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 19:46:24 +1200 Subject: [PATCH 089/175] Do not discard notification observers in NoticePresenter --- .../System/Notices/NoticePresenter.swift | 84 +++++++++++-------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Notices/NoticePresenter.swift b/WordPress/Classes/ViewRelated/System/Notices/NoticePresenter.swift index 7546c7916a28..a745319309cd 100644 --- a/WordPress/Classes/ViewRelated/System/Notices/NoticePresenter.swift +++ b/WordPress/Classes/ViewRelated/System/Notices/NoticePresenter.swift @@ -1,4 +1,5 @@ import Foundation +import Combine import UIKit import UserNotifications import WordPressFlux @@ -59,6 +60,8 @@ class NoticePresenter { private var currentNoticePresentation: NoticePresentation? private var currentKeyboardPresentation: KeyboardPresentation = .notPresent + private var notificationObservers = Set() + init(store: NoticeStore = StoreContainer.shared.notice, animator: NoticeAnimator = NoticeAnimator(duration: Animations.appearanceDuration, springDampening: Animations.appearanceSpringDamping, springVelocity: NoticePresenter.Animations.appearanceSpringVelocity)) { self.store = store @@ -97,54 +100,61 @@ class NoticePresenter { // MARK: - Events private func listenToKeyboardEvents() { - NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: nil) { [weak self] (notification) in - guard let self = self, - let userInfo = notification.userInfo, - let keyboardFrameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue, - let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber else { - return - } - - self.currentKeyboardPresentation = .present(height: keyboardFrameValue.cgRectValue.size.height) - - guard let currentContainer = self.currentNoticePresentation?.containerView else { - return - } + NotificationCenter.default + .publisher(for: UIResponder.keyboardWillShowNotification) + .sink { [weak self] (notification) in + guard let self = self, + let userInfo = notification.userInfo, + let keyboardFrameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue, + let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber else { + return + } - UIView.animate(withDuration: durationValue.doubleValue, animations: { - currentContainer.bottomConstraint?.constant = self.onScreenNoticeContainerBottomConstraintConstant - self.view.layoutIfNeeded() - }) - } - NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: nil) { [weak self] (notification) in - self?.currentKeyboardPresentation = .notPresent + self.currentKeyboardPresentation = .present(height: keyboardFrameValue.cgRectValue.size.height) - guard let self = self, - let currentContainer = self.currentNoticePresentation?.containerView, - let userInfo = notification.userInfo, - let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber else { + guard let currentContainer = self.currentNoticePresentation?.containerView else { return + } + + UIView.animate(withDuration: durationValue.doubleValue, animations: { + currentContainer.bottomConstraint?.constant = self.onScreenNoticeContainerBottomConstraintConstant + self.view.layoutIfNeeded() + }) } + .store(in: ¬ificationObservers) + NotificationCenter.default + .publisher(for: UIResponder.keyboardWillHideNotification) + .sink { [weak self] (notification) in + self?.currentKeyboardPresentation = .notPresent + + guard let self = self, + let currentContainer = self.currentNoticePresentation?.containerView, + let userInfo = notification.userInfo, + let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber else { + return + } - UIView.animate(withDuration: durationValue.doubleValue, animations: { - currentContainer.bottomConstraint?.constant = self.onScreenNoticeContainerBottomConstraintConstant - self.view.layoutIfNeeded() - }) - } + UIView.animate(withDuration: durationValue.doubleValue, animations: { + currentContainer.bottomConstraint?.constant = self.onScreenNoticeContainerBottomConstraintConstant + self.view.layoutIfNeeded() + }) + } + .store(in: ¬ificationObservers) } /// Adjust the current Notice so it will always be in the correct y-position after the /// device is rotated. private func listenToOrientationChangeEvents() { - let nc = NotificationCenter.default - nc.addObserver(forName: NSNotification.Name.WPTabBarHeightChanged, object: nil, queue: nil) { [weak self] _ in - guard let self = self, - let containerView = self.currentNoticePresentation?.containerView else { - return - } + NotificationCenter.default.publisher(for: .WPTabBarHeightChanged) + .sink { [weak self] _ in + guard let self = self, + let containerView = self.currentNoticePresentation?.containerView else { + return + } - containerView.bottomConstraint?.constant = -self.window.untouchableViewController.offsetOnscreen - } + containerView.bottomConstraint?.constant = -self.window.untouchableViewController.offsetOnscreen + } + .store(in: ¬ificationObservers) } /// Handle all changes in the `NoticeStore`. From d204b03633bdbe8dfea5645893b52a4d828545e5 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 30 Jun 2023 19:47:24 +1200 Subject: [PATCH 090/175] Retain a notification observer in StatsForegroundObservable --- .../Stats/StatsForegroundObservable.swift | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/StatsForegroundObservable.swift b/WordPress/Classes/ViewRelated/Stats/StatsForegroundObservable.swift index 447af1b75cf2..63d45d12e1ef 100644 --- a/WordPress/Classes/ViewRelated/Stats/StatsForegroundObservable.swift +++ b/WordPress/Classes/ViewRelated/Stats/StatsForegroundObservable.swift @@ -4,9 +4,11 @@ protocol StatsForegroundObservable: AnyObject { func reloadStatsData() } +private var observerKey = 0 + extension StatsForegroundObservable where Self: UIViewController { func addWillEnterForegroundObserver() { - NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, + enterForegroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { [weak self] _ in self?.reloadStatsData() @@ -14,8 +16,18 @@ extension StatsForegroundObservable where Self: UIViewController { } func removeWillEnterForegroundObserver() { - NotificationCenter.default.removeObserver(self, - name: UIApplication.willEnterForegroundNotification, - object: nil) + if let enterForegroundObserver { + NotificationCenter.default.removeObserver(enterForegroundObserver) + } + enterForegroundObserver = nil + } + + private var enterForegroundObserver: NSObjectProtocol? { + get { + objc_getAssociatedObject(self, &observerKey) as? NSObjectProtocol + } + set { + objc_setAssociatedObject(self, &observerKey, newValue, .OBJC_ASSOCIATION_RETAIN) + } } } From b8239d475dbaa7ae56f7665cc60c084ee2e7fe7c Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Fri, 30 Jun 2023 15:40:58 +0700 Subject: [PATCH 091/175] Remove VisualEffectView --- .../Comments/CommentTableHeaderView.swift | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift b/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift index 0cdfbbf15e91..d24d87912ff3 100644 --- a/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift @@ -96,18 +96,6 @@ private struct CommentHeaderView: View { @State var showsDisclosureIndicator = true var body: some View { - if #available(iOS 15.0, *) { - // Material ShapeStyles are only available from iOS 15.0. - content.background(.ultraThinMaterial) - } else { - ZStack { - VisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)) - content - } - } - } - - var content: some View { HStack { text Spacer() @@ -115,6 +103,7 @@ private struct CommentHeaderView: View { disclosureIndicator } } + .background(.ultraThinMaterial) .padding(EdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16)) } @@ -139,17 +128,3 @@ private struct CommentHeaderView: View { .imageScale(.large) } } - -// MARK: SwiftUI VisualEffect support for iOS 14 - -private struct VisualEffectView: UIViewRepresentable { - var effect: UIVisualEffect - - func makeUIView(context: Context) -> UIVisualEffectView { - return UIVisualEffectView() - } - - func updateUIView(_ uiView: UIVisualEffectView, context: Context) { - uiView.effect = effect - } -} From 18e6f2476f0298cb4603f4803aef93e19dcf3436 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 30 Jun 2023 09:20:22 -0400 Subject: [PATCH 092/175] Make BlazeService init non-failing --- WordPress/Classes/Services/BlazeService.swift | 8 ++------ .../Blaze Campaigns/BlazeCampaignsStream.swift | 8 ++++---- .../Cards/Blaze/DashboardBlazeCardCellViewModel.swift | 6 +++--- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/WordPress/Classes/Services/BlazeService.swift b/WordPress/Classes/Services/BlazeService.swift index fa4764d7c89e..6694aff6bc0e 100644 --- a/WordPress/Classes/Services/BlazeService.swift +++ b/WordPress/Classes/Services/BlazeService.swift @@ -11,14 +11,10 @@ protocol BlazeServiceProtocol { // MARK: - Init - required init?(contextManager: CoreDataStackSwift = ContextManager.shared, + required init(contextManager: CoreDataStackSwift = ContextManager.shared, remote: BlazeServiceRemote? = nil) { - guard let account = try? WPAccount.lookupDefaultWordPressComAccount(in: contextManager.mainContext) else { - return nil - } - self.contextManager = contextManager - self.remote = remote ?? .init(wordPressComRestApi: account.wordPressComRestV2Api) + self.remote = remote ?? BlazeServiceRemote(wordPressComRestApi: WordPressComRestApi.defaultApi(in: contextManager.mainContext, localeKey: WordPressComRestApi.LocaleKeyV2)) } @objc class func createService() -> BlazeService? { diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift index 6f2b868290fb..23f9e54997e2 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift @@ -17,17 +17,17 @@ final class BlazeCampaignsStream { private var pages: [BlazeCampaignsSearchResponse] = [] private var campaignIDs: Set = [] private var hasMore = true + private let service: BlazeServiceProtocol private let blog: Blog - init(blog: Blog) { + init(blog: Blog, + service: BlazeServiceProtocol = BlazeService()) { self.blog = blog + self.service = service } /// Loads the next page. Does nothing if it's already loading or has no more items to load. func load(_ completion: ((Result) -> Void)? = nil) { - guard let service = BlazeService() else { - return assertionFailure("Failed to create BlazeService") - } guard !isLoading && hasMore else { return } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift index 7b019360e966..7360fdbdc6d2 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift @@ -5,7 +5,7 @@ final class DashboardBlazeCardCellViewModel { private(set) var state: State = .promo private let blog: Blog - private let service: BlazeServiceProtocol? + private let service: BlazeServiceProtocol private let store: DashboardBlazeStoreProtocol private var isRefreshing = false private let isBlazeCampaignsFlagEnabled: () -> Bool @@ -20,7 +20,7 @@ final class DashboardBlazeCardCellViewModel { var onRefresh: ((DashboardBlazeCardCellViewModel) -> Void)? init(blog: Blog, - service: BlazeServiceProtocol? = BlazeService(), + service: BlazeServiceProtocol = BlazeService(), store: DashboardBlazeStoreProtocol = BlogDashboardPersistence(), isBlazeCampaignsFlagEnabled: @escaping () -> Bool = { RemoteFeatureFlag.blazeManageCampaigns.enabled() }) { self.blog = blog @@ -42,7 +42,7 @@ final class DashboardBlazeCardCellViewModel { return // Continue showing the default `Promo` card } - guard !isRefreshing, let service else { return } + guard !isRefreshing else { return } isRefreshing = true service.getRecentCampaigns(for: blog, page: 1) { [weak self] in From c5d9267e336f738dfe975be512d83e976522faef Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 30 Jun 2023 09:59:37 -0400 Subject: [PATCH 093/175] Add BlazeCampaignsStreamTests with a happy path test --- .../BlazeCampaignsStream.swift | 5 +- WordPress/WordPress.xcodeproj/project.pbxproj | 16 ++ .../WordPressTest/Bundle+TestExtensions.swift | 14 ++ .../Dashboard/BlazeCampaignsStreamTests.swift | 141 ++++++++++++++++++ .../DashboardBlazeCardCellViewModelTest.swift | 6 +- .../Test Data/blaze-search-page-1.json | 26 ++++ .../Test Data/blaze-search-page-2.json | 47 ++++++ .../Test Data/blaze-search-response.json | 8 +- 8 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 WordPress/WordPressTest/Bundle+TestExtensions.swift create mode 100644 WordPress/WordPressTest/Dashboard/BlazeCampaignsStreamTests.swift create mode 100644 WordPress/WordPressTest/Test Data/blaze-search-page-1.json create mode 100644 WordPress/WordPressTest/Test Data/blaze-search-page-2.json diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift index 23f9e54997e2..664f9109b4fc 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsStream.swift @@ -20,8 +20,7 @@ final class BlazeCampaignsStream { private let service: BlazeServiceProtocol private let blog: Blog - init(blog: Blog, - service: BlazeServiceProtocol = BlazeService()) { + init(blog: Blog, service: BlazeServiceProtocol = BlazeService()) { self.blog = blog self.service = service } @@ -51,7 +50,7 @@ final class BlazeCampaignsStream { campaigns += newCampaigns campaignIDs.formUnion(newCampaigns.map(\.campaignID)) - let indexPaths = campaigns.indices.prefix(newCampaigns.count) + let indexPaths = campaigns.indices.suffix(newCampaigns.count) .map { IndexPath(row: $0, section: 0) } delegate?.stream(self, didAppendItemsAt: indexPaths) case .failure(let error): diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 2b032a47f5fa..497912bcf3a9 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -353,6 +353,10 @@ 0C391E622A3002950040EA91 /* BlazeCampaignStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391E602A3002950040EA91 /* BlazeCampaignStatusView.swift */; }; 0C391E642A312DB20040EA91 /* BlazeCampaignViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */; }; 0C63266F2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C63266E2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift */; }; + 0C6C4CD02A4F0A000049E762 /* BlazeCampaignsStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C6C4CCF2A4F0A000049E762 /* BlazeCampaignsStreamTests.swift */; }; + 0C6C4CD42A4F0AD90049E762 /* blaze-search-page-1.json in Resources */ = {isa = PBXBuildFile; fileRef = 0C6C4CD32A4F0AD80049E762 /* blaze-search-page-1.json */; }; + 0C6C4CD62A4F0AEE0049E762 /* blaze-search-page-2.json in Resources */ = {isa = PBXBuildFile; fileRef = 0C6C4CD52A4F0AEE0049E762 /* blaze-search-page-2.json */; }; + 0C6C4CD82A4F0F2C0049E762 /* Bundle+TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C6C4CD72A4F0F2C0049E762 /* Bundle+TestExtensions.swift */; }; 0C71959B2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; 0C71959C2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */; }; 0C7D481A2A4DB9300023CF84 /* blaze-search-response.json in Resources */ = {isa = PBXBuildFile; fileRef = 0C7D48192A4DB9300023CF84 /* blaze-search-response.json */; }; @@ -6071,6 +6075,10 @@ 0C391E602A3002950040EA91 /* BlazeCampaignStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignStatusView.swift; sourceTree = ""; }; 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignViewModelTests.swift; sourceTree = ""; }; 0C63266E2A3D1305000B8C57 /* GutenbergFilesAppMediaSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergFilesAppMediaSourceTests.swift; sourceTree = ""; }; + 0C6C4CCF2A4F0A000049E762 /* BlazeCampaignsStreamTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsStreamTests.swift; sourceTree = ""; }; + 0C6C4CD32A4F0AD80049E762 /* blaze-search-page-1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "blaze-search-page-1.json"; sourceTree = ""; }; + 0C6C4CD52A4F0AEE0049E762 /* blaze-search-page-2.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blaze-search-page-2.json"; sourceTree = ""; }; + 0C6C4CD72A4F0F2C0049E762 /* Bundle+TestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+TestExtensions.swift"; sourceTree = ""; }; 0C71959A2A3CA582002EA18C /* SiteSettingsRelatedPostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSettingsRelatedPostsView.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 = ""; }; @@ -10044,6 +10052,8 @@ isa = PBXGroup; children = ( 0C7D48192A4DB9300023CF84 /* blaze-search-response.json */, + 0C6C4CD32A4F0AD80049E762 /* blaze-search-page-1.json */, + 0C6C4CD52A4F0AEE0049E762 /* blaze-search-page-2.json */, ); name = Blaze; sourceTree = ""; @@ -13736,6 +13746,7 @@ FA3FBF8D2A2777E00012FC90 /* DashboardActivityLogViewModelTests.swift */, 0CD382852A4B6FCE00612173 /* DashboardBlazeCardCellViewModelTest.swift */, 0C391E632A312DB20040EA91 /* BlazeCampaignViewModelTests.swift */, + 0C6C4CCF2A4F0A000049E762 /* BlazeCampaignsStreamTests.swift */, ); path = Dashboard; sourceTree = ""; @@ -16438,6 +16449,7 @@ 805CC0B6296680CF002941DC /* RemoteFeatureFlagStoreMock.swift */, 805CC0B8296680F7002941DC /* RemoteConfigStoreMock.swift */, 805CC0BE29668A97002941DC /* MockCurrentDateProvider.swift */, + 0C6C4CD72A4F0F2C0049E762 /* Bundle+TestExtensions.swift */, ); name = Helpers; sourceTree = ""; @@ -19295,6 +19307,7 @@ F4426FDB287F066400218003 /* site-suggestions.json in Resources */, DC772B0128200A3700664C02 /* stats-visits-day-4.json in Resources */, 93C882A21EEB18D700227A59 /* html_page_with_link_to_rsd.html in Resources */, + 0C6C4CD62A4F0AEE0049E762 /* blaze-search-page-2.json in Resources */, D848CC0520FF062100A9038F /* notifications-user-content-meta.json in Resources */, 8BB185CF24B62D7600A4CCE8 /* reader-cards-success.json in Resources */, 7E4A772320F7BE94001C706D /* activity-log-comment-content.json in Resources */, @@ -19310,6 +19323,7 @@ 3211055A250C027D0048446F /* invalid-jpeg-header.jpg in Resources */, E15027621E03E51500B847E3 /* notes-action-push.json in Resources */, 3211056B250C0F750048446F /* valid-png-header in Resources */, + 0C6C4CD42A4F0AD90049E762 /* blaze-search-page-1.json in Resources */, 748BD8851F19234300813F9A /* notifications-mark-as-read.json in Resources */, 7E442FCA20F678D100DEACA5 /* activity-log-pingback-content.json in Resources */, F127FFD824213B5600B9D41A /* atomic-get-authentication-cookie-success.json in Resources */, @@ -23349,6 +23363,7 @@ C396C80B280F2401006FE7AC /* SiteDesignTests.swift in Sources */, 806E53E427E01CFE0064315E /* DashboardStatsViewModelTests.swift in Sources */, D88A649E208D82D2008AE9BC /* XCTestCase+Wait.swift in Sources */, + 0C6C4CD82A4F0F2C0049E762 /* Bundle+TestExtensions.swift in Sources */, C38C5D8127F61D2C002F517E /* MenuItemTests.swift in Sources */, E18549DB230FBFEF003C620E /* BlogServiceDeduplicationTests.swift in Sources */, 400A2C8F2217AD7F000A8A59 /* ClicksStatsRecordValueTests.swift in Sources */, @@ -23506,6 +23521,7 @@ 40F50B82221310F000CBBB73 /* StatsTestCase.swift in Sources */, 02BE5CC02281B53F00E351BA /* RegisterDomainDetailsViewModelLoadingStateTests.swift in Sources */, FEFA263E26C58427009CCB7E /* ShareAppTextActivityItemSourceTests.swift in Sources */, + 0C6C4CD02A4F0A000049E762 /* BlazeCampaignsStreamTests.swift in Sources */, 40EE948222132F5800CD264F /* PublicizeConectionStatsRecordValueTests.swift in Sources */, 577C2AAB22936DCB00AD1F03 /* PostCardCellGhostableTests.swift in Sources */, 08B954F328535EE800B07185 /* FeatureHighlightStoreTests.swift in Sources */, diff --git a/WordPress/WordPressTest/Bundle+TestExtensions.swift b/WordPress/WordPressTest/Bundle+TestExtensions.swift new file mode 100644 index 000000000000..000bdeb77d12 --- /dev/null +++ b/WordPress/WordPressTest/Bundle+TestExtensions.swift @@ -0,0 +1,14 @@ +import Foundation +import XCTest + +extension Bundle { + static var test: Bundle { Bundle(for: TestBundleToken.self) } + + func json(named name: String) throws -> Data { + let url = try XCTUnwrap(Bundle(for: TestBundleToken.self) + .url(forResource: name, withExtension: "json")) + return try Data(contentsOf: url) + } +} + +private final class TestBundleToken {} diff --git a/WordPress/WordPressTest/Dashboard/BlazeCampaignsStreamTests.swift b/WordPress/WordPressTest/Dashboard/BlazeCampaignsStreamTests.swift new file mode 100644 index 000000000000..73f8c3bc3dd2 --- /dev/null +++ b/WordPress/WordPressTest/Dashboard/BlazeCampaignsStreamTests.swift @@ -0,0 +1,141 @@ +import XCTest +import WordPressKit + +@testable import WordPress + +final class BlazeCampaignsStreamTests: CoreDataTestCase { + private var sut: BlazeCampaignsStream! + private var blog: Blog! + private var delegate = MockCampaignsStreamDelegate() + private let service = MockBlazePaginatedService() + + override func setUp() { + super.setUp() + + blog = ModelTestHelper.insertDotComBlog(context: mainContext) + blog.dotComID = 1 + + sut = BlazeCampaignsStream(blog: blog, service: service) + sut.delegate = delegate + } + + func testThatPagesAreLoaded() throws { + XCTAssertTrue(sut.campaigns.isEmpty) + XCTAssertFalse(sut.isLoading) + XCTAssertNil(sut.error) + + // When loading the first page + try loadNextPage() + + // Then + XCTAssertEqual(service.numberOfRequests, 1) + XCTAssertEqual(delegate.appendedIndexPaths, [[IndexPath(row: 0, section: 0)]]) + guard delegate.states.count == 2 else { + return XCTFail("Unexpected state updates recorded: \(delegate.states)") + } + do { + let state = delegate.states[0] + XCTAssertEqual(state.campaignIDs, []) + XCTAssertTrue(state.isLoading) + XCTAssertNil(state.error) + } + do { + let state = delegate.states[1] + XCTAssertEqual(state.campaignIDs, [1]) + XCTAssertFalse(state.isLoading) + XCTAssertNil(state.error) + } + + // When loading the second page + delegate.reset() + try loadNextPage() + + // Then the second page is loaded + XCTAssertEqual(service.numberOfRequests, 2) + XCTAssertEqual(delegate.appendedIndexPaths, [[IndexPath(row: 1, section: 0)]]) + guard delegate.states.count == 2 else { + return XCTFail("Unexpected state updates recorded: \(delegate.states)") + } + do { + let state = delegate.states[0] + XCTAssertEqual(state.campaignIDs, [1]) + XCTAssertTrue(state.isLoading) + XCTAssertNil(state.error) + } + do { + let state = delegate.states[1] + // Then the duplicated campaign with ID "1" is skipped + XCTAssertEqual(state.campaignIDs, [1, 2]) + XCTAssertFalse(state.isLoading) + XCTAssertNil(state.error) + } + + // When loading with not more pages available + delegate.reset() + sut.load() + + // Then no requests are made + XCTAssertTrue(delegate.appendedIndexPaths.isEmpty) + XCTAssertTrue(delegate.states.isEmpty) + XCTAssertEqual(service.numberOfRequests, 2) + } + + @discardableResult + private func loadNextPage() throws -> BlazeCampaignsSearchResponse { + let expectation = self.expectation(description: "didLoadNextPage") + var result: Result? + sut.load { + result = $0 + expectation.fulfill() + } + wait(for: [expectation], timeout: 1) + return try XCTUnwrap(result).get() + } +} + +private final class MockCampaignsStreamDelegate: BlazeCampaignsStreamDelegate { + var appendedIndexPaths: [[IndexPath]] = [] + var states: [RecordedBlazeCampaignsStreamState] = [] + + func reset() { + appendedIndexPaths = [] + states = [] + } + + func stream(_ stream: BlazeCampaignsStream, didAppendItemsAt indexPaths: [IndexPath]) { + appendedIndexPaths.append(indexPaths) + } + + func streamDidRefreshState(_ stream: BlazeCampaignsStream) { + states.append(RecordedBlazeCampaignsStreamState(campaigns: stream.campaigns, isLoading: stream.isLoading, error: stream.error)) + } +} + +private struct RecordedBlazeCampaignsStreamState { + let campaigns: [BlazeCampaign] + var campaignIDs: [Int] { campaigns.map(\.campaignID) } + let isLoading: Bool + let error: Error? +} + +private final class MockBlazePaginatedService: BlazeServiceProtocol { + var numberOfRequests = 0 + + func getRecentCampaigns(for blog: Blog, page: Int, completion: @escaping (Result) -> Void) { + XCTAssertEqual(blog.dotComID, 1) + XCTAssertTrue([1, 2].contains(page)) + + numberOfRequests += 1 + + do { + let data = try Bundle.test.json(named: "blaze-search-page-\(page)") + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .iso8601 + let response = try decoder.decode(BlazeCampaignsSearchResponse.self, from: data) + completion(.success(response)) + } catch { + completion(.failure(error)) + } + } +} diff --git a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift index a944e1c4f04c..2e99d427cd08 100644 --- a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift +++ b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift @@ -120,12 +120,10 @@ private final class MockDashboardBlazeStore: DashboardBlazeStoreProtocol { } private func getMockResponse() throws -> BlazeCampaignsSearchResponse { + let data = try Bundle.test.json(named: "blaze-search-response") + let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .iso8601 - - let url = try XCTUnwrap(Bundle(for: MockDashboardBlazeStore.self) - .url(forResource: "blaze-search-response", withExtension: "json")) - let data = try Data(contentsOf: url) return try decoder.decode(BlazeCampaignsSearchResponse.self, from: data) } diff --git a/WordPress/WordPressTest/Test Data/blaze-search-page-1.json b/WordPress/WordPressTest/Test Data/blaze-search-page-1.json new file mode 100644 index 000000000000..60be82986557 --- /dev/null +++ b/WordPress/WordPressTest/Test Data/blaze-search-page-1.json @@ -0,0 +1,26 @@ +{ + "total_pages": 2, + "campaigns": [ + { + "campaign_id": 1, + "name": "Campaign 01", + "start_date": "2023-06-13T00:00:00Z", + "end_date": "2023-06-01T19:15:45Z", + "status": "finished", + "ui_status": "finished", + "avatar_url": "https://example.com/avatar.jpg", + "budget_cents": 500, + "target_url": "https://example.com/campaign-01/target-url", + "content_config": { + "title": "Test Post - don't approve", + "snippet": "Test Post Empty Empty", + "clickUrl": "https://example.com/campaign-01/click-url", + "imageUrl": "https://example.com/campaign-01/image.jpg" + }, + "campaign_stats": { + "impressions_total": 1000, + "clicks_total": 235 + } + } + ] +} diff --git a/WordPress/WordPressTest/Test Data/blaze-search-page-2.json b/WordPress/WordPressTest/Test Data/blaze-search-page-2.json new file mode 100644 index 000000000000..d39c1a3fdd1b --- /dev/null +++ b/WordPress/WordPressTest/Test Data/blaze-search-page-2.json @@ -0,0 +1,47 @@ +{ + "total_pages": 2, + "campaigns": [ + { + "campaign_id": 1, + "name": "Campaign 01", + "start_date": "2023-06-13T00:00:00Z", + "end_date": "2023-06-01T19:15:45Z", + "status": "finished", + "ui_status": "finished", + "avatar_url": "https://example.com/avatar.jpg", + "budget_cents": 500, + "target_url": "https://example.com/campaign-01/target-url", + "content_config": { + "title": "Test Post - don't approve", + "snippet": "Test Post Empty Empty", + "clickUrl": "https://example.com/campaign-01/click-url", + "imageUrl": "https://example.com/campaign-01/image.jpg" + }, + "campaign_stats": { + "impressions_total": 1000, + "clicks_total": 235 + } + }, + { + "campaign_id": 2, + "name": "Campaign 02", + "start_date": "2023-06-13T00:00:00Z", + "end_date": "2023-06-01T19:15:45Z", + "status": "finished", + "ui_status": "finished", + "avatar_url": "https://example.com/avatar.jpg", + "budget_cents": 500, + "target_url": "https://example.com/campaign-02/target-url", + "content_config": { + "title": "Test Post - don't approve", + "snippet": "Test Post Empty Empty", + "clickUrl": "https://example.com/campaign-02/click-url", + "imageUrl": "https://example.com/campaign-02/image.jpg" + }, + "campaign_stats": { + "impressions_total": 1000, + "clicks_total": 235 + } + } + ] +} diff --git a/WordPress/WordPressTest/Test Data/blaze-search-response.json b/WordPress/WordPressTest/Test Data/blaze-search-response.json index c30741bb3a1f..a4a848897540 100644 --- a/WordPress/WordPressTest/Test Data/blaze-search-response.json +++ b/WordPress/WordPressTest/Test Data/blaze-search-response.json @@ -8,14 +8,14 @@ "end_date": "2023-06-01T19:15:45Z", "status": "finished", "ui_status": "finished", - "avatar_url": "https://0.gravatar.com/avatar/614d27bcc21db12e7c49b516b4750387?s=96&d=identicon&r=G", + "avatar_url": "https://example.com/avatar.jpg", "budget_cents": 500, - "target_url": "https://alextest9123.wordpress.com/2023/06/01/test-post/", + "target_url": "https://example.com/campaign-01/target-url", "content_config": { "title": "Test Post - don't approve", "snippet": "Test Post Empty Empty", - "clickUrl": "https://alextest9123.wordpress.com/2023/06/01/test-post/", - "imageUrl": "https://i0.wp.com/public-api.wordpress.com/wpcom/v2/wordads/dsp/api/v1/dsp/creatives/56259/image?w=600&zoom=2" + "clickUrl": "https://example.com/campaign-01/click-url", + "imageUrl": "https://example.com/campaign-01/image.jpg" }, "campaign_stats": { "impressions_total": 1000, From 1f0af8c2eed9ac0af2ef4b63055187af3837dfda Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 30 Jun 2023 10:25:39 -0400 Subject: [PATCH 094/175] Add negative scenario tests to BlazeCampaignsStreamTests --- .../Dashboard/BlazeCampaignsStreamTests.swift | 130 +++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/WordPress/WordPressTest/Dashboard/BlazeCampaignsStreamTests.swift b/WordPress/WordPressTest/Dashboard/BlazeCampaignsStreamTests.swift index 73f8c3bc3dd2..c06a9bff27de 100644 --- a/WordPress/WordPressTest/Dashboard/BlazeCampaignsStreamTests.swift +++ b/WordPress/WordPressTest/Dashboard/BlazeCampaignsStreamTests.swift @@ -27,7 +27,7 @@ final class BlazeCampaignsStreamTests: CoreDataTestCase { // When loading the first page try loadNextPage() - // Then + // Then the first page is loaded XCTAssertEqual(service.numberOfRequests, 1) XCTAssertEqual(delegate.appendedIndexPaths, [[IndexPath(row: 0, section: 0)]]) guard delegate.states.count == 2 else { @@ -80,6 +80,129 @@ final class BlazeCampaignsStreamTests: CoreDataTestCase { XCTAssertEqual(service.numberOfRequests, 2) } + func testFirstPageFailedToLoad() throws { + // Given + service.isFailing = true + + // When loading the first page + do { + try loadNextPage() + XCTFail("Expected the request to fail") + } catch { + XCTAssertEqual((error as? URLError)?.code, .notConnectedToInternet) + } + + // Then + XCTAssertEqual(service.numberOfRequests, 1) + XCTAssertEqual(delegate.appendedIndexPaths, []) + guard delegate.states.count == 2 else { + return XCTFail("Unexpected state updates recorded: \(delegate.states)") + } + do { + let state = delegate.states[0] + XCTAssertEqual(state.campaignIDs, []) + XCTAssertTrue(state.isLoading) + XCTAssertNil(state.error) + } + do { + let state = delegate.states[1] + XCTAssertEqual(state.campaignIDs, []) + XCTAssertFalse(state.isLoading) + let error = try XCTUnwrap(state.error) + XCTAssertEqual((error as? URLError)?.code, .notConnectedToInternet) + } + + // When connection restored + service.isFailing = false + + // When + delegate.reset() + try loadNextPage() + + // Then the first page is loaded + XCTAssertEqual(service.numberOfRequests, 2) + XCTAssertEqual(delegate.appendedIndexPaths, [[IndexPath(row: 0, section: 0)]]) + guard delegate.states.count == 2 else { + return XCTFail("Unexpected state updates recorded: \(delegate.states)") + } + do { + let state = delegate.states[0] + XCTAssertEqual(state.campaignIDs, []) + XCTAssertTrue(state.isLoading) + XCTAssertNil(state.error) + } + do { + let state = delegate.states[1] + XCTAssertEqual(state.campaignIDs, [1]) + XCTAssertFalse(state.isLoading) + XCTAssertNil(state.error) + } + } + + func testSecondPageFailedToLoad() throws { + // Given first page already loaded + try loadNextPage() + + // When connection fails + service.isFailing = true + + // When + delegate.reset() + do { + try loadNextPage() + XCTFail("Expected the request to fail") + } catch { + XCTAssertEqual((error as? URLError)?.code, .notConnectedToInternet) + } + + // Then the second page fails to load + XCTAssertEqual(service.numberOfRequests, 2) + XCTAssertEqual(delegate.appendedIndexPaths, []) + guard delegate.states.count == 2 else { + return XCTFail("Unexpected state updates recorded: \(delegate.states)") + } + do { + let state = delegate.states[0] + XCTAssertEqual(state.campaignIDs, [1]) + XCTAssertTrue(state.isLoading) + XCTAssertNil(state.error) + } + do { + let state = delegate.states[1] + XCTAssertEqual(state.campaignIDs, [1]) + XCTAssertFalse(state.isLoading) + let error = try XCTUnwrap(state.error) + XCTAssertEqual((error as? URLError)?.code, .notConnectedToInternet) + } + + // When connection is restored + service.isFailing = false + + // When loading the second page + delegate.reset() + try loadNextPage() + + // Then the second page is loaded + XCTAssertEqual(service.numberOfRequests, 3) + XCTAssertEqual(delegate.appendedIndexPaths, [[IndexPath(row: 1, section: 0)]]) + guard delegate.states.count == 2 else { + return XCTFail("Unexpected state updates recorded: \(delegate.states)") + } + do { + let state = delegate.states[0] + XCTAssertEqual(state.campaignIDs, [1]) + XCTAssertTrue(state.isLoading) + XCTAssertNil(state.error) + } + do { + let state = delegate.states[1] + // Then the duplicated campaign with ID "1" is skipped + XCTAssertEqual(state.campaignIDs, [1, 2]) + XCTAssertFalse(state.isLoading) + XCTAssertNil(state.error) + } + } + @discardableResult private func loadNextPage() throws -> BlazeCampaignsSearchResponse { let expectation = self.expectation(description: "didLoadNextPage") @@ -120,6 +243,7 @@ private struct RecordedBlazeCampaignsStreamState { private final class MockBlazePaginatedService: BlazeServiceProtocol { var numberOfRequests = 0 + var isFailing = false func getRecentCampaigns(for blog: Blog, page: Int, completion: @escaping (Result) -> Void) { XCTAssertEqual(blog.dotComID, 1) @@ -127,6 +251,10 @@ private final class MockBlazePaginatedService: BlazeServiceProtocol { numberOfRequests += 1 + guard !isFailing else { + return completion(.failure(URLError(.notConnectedToInternet))) + } + do { let data = try Bundle.test.json(named: "blaze-search-page-\(page)") let decoder = JSONDecoder() From 8c1393f16f89ffa7631a0d0383fc784a9fbf479f Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 30 Jun 2023 14:41:45 -0400 Subject: [PATCH 095/175] Remove keyboard crash workaround for iOS 14 --- .../UITextField+WorkaroundContinueIssue.swift | 50 ------------------- .../Classes/System/WordPressAppDelegate.swift | 7 --- WordPress/WordPress.xcodeproj/project.pbxproj | 6 --- 3 files changed, 63 deletions(-) delete mode 100644 WordPress/Classes/Extensions/UITextField+WorkaroundContinueIssue.swift diff --git a/WordPress/Classes/Extensions/UITextField+WorkaroundContinueIssue.swift b/WordPress/Classes/Extensions/UITextField+WorkaroundContinueIssue.swift deleted file mode 100644 index c26ef1d79428..000000000000 --- a/WordPress/Classes/Extensions/UITextField+WorkaroundContinueIssue.swift +++ /dev/null @@ -1,50 +0,0 @@ -import Foundation - -@objc -extension UITextField { - - /// This method takes care of resolving whether the iOS version is vulnerable to the Bulgarian / Icelandic keyboard crash issue - /// by Apple. Once the issue is resolved by Apple we should consider setting an upper iOS version to limit this workaround. - /// - /// Once we drop support for iOS 14, we could remove this extension entirely. - /// - public class func shouldActivateWorkaroundForBulgarianKeyboardCrash() -> Bool { - return true - } - - /// We're swizzling `UITextField.becomeFirstResponder()` so that we can fix an issue with - /// Bulgarian and Icelandic keyboards when appropriate. - /// - /// Ref: https://github.com/wordpress-mobile/WordPress-iOS/issues/15187 - /// - @objc - class func activateWorkaroundForBulgarianKeyboardCrash() { - guard let original = class_getInstanceMethod( - UITextField.self, - #selector(UITextField.becomeFirstResponder)), - let new = class_getInstanceMethod( - UITextField.self, - #selector(UITextField.swizzledBecomeFirstResponder)) else { - - DDLogError("Could not activate workaround for Bulgarian keyboard crash.") - - return - } - - method_exchangeImplementations(original, new) - } - - /// This method simply replaces the `returnKeyType == .continue` with - /// `returnKeyType == .next`when the Bulgarian Keyboard crash workaround is needed. - /// - public func swizzledBecomeFirstResponder() { - if UITextField.shouldActivateWorkaroundForBulgarianKeyboardCrash(), - returnKeyType == .continue { - returnKeyType = .next - } - - // This can look confusing - it's basically calling the original method to - // make sure we don't disrupt anything. - swizzledBecomeFirstResponder() - } -} diff --git a/WordPress/Classes/System/WordPressAppDelegate.swift b/WordPress/Classes/System/WordPressAppDelegate.swift index a975ba64aab4..3726179dc8c2 100644 --- a/WordPress/Classes/System/WordPressAppDelegate.swift +++ b/WordPress/Classes/System/WordPressAppDelegate.swift @@ -128,13 +128,6 @@ class WordPressAppDelegate: UIResponder, UIApplicationDelegate { ABTest.start() - if UITextField.shouldActivateWorkaroundForBulgarianKeyboardCrash() { - // WORKAROUND: this is a workaround for an issue with UITextField in iOS 14. - // Please refer to the documentation of the called method to learn the details and know - // how to tell if this call can be removed. - UITextField.activateWorkaroundForBulgarianKeyboardCrash() - } - InteractiveNotificationsManager.shared.registerForUserNotifications() setupPingHub() setupBackgroundRefresh(application) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index ec0808d341c1..a954830bd601 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -3480,7 +3480,6 @@ F18B43781F849F580089B817 /* PostAttachmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18B43771F849F580089B817 /* PostAttachmentTests.swift */; }; F18CB8962642E58700B90794 /* FixedSizeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18CB8952642E58700B90794 /* FixedSizeImageView.swift */; }; F18CB8972642E58700B90794 /* FixedSizeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18CB8952642E58700B90794 /* FixedSizeImageView.swift */; }; - F19153BD2549ED0800629EC4 /* UITextField+WorkaroundContinueIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = F19153BC2549ED0800629EC4 /* UITextField+WorkaroundContinueIssue.swift */; }; F195C42B26DFBDC2000EC884 /* BackgroundTasksCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F181EDE426B2AC7200C61241 /* BackgroundTasksCoordinator.swift */; }; F195C42C26DFBE21000EC884 /* WeeklyRoundupBackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D8C6EF26C17A6C002E3323 /* WeeklyRoundupBackgroundTask.swift */; }; F195C42D26DFBE3A000EC884 /* WordPressBackgroundTaskEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D8C6E826BA94DF002E3323 /* WordPressBackgroundTaskEventHandler.swift */; }; @@ -5298,7 +5297,6 @@ FABB25B62602FC2C00C8785C /* Product.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148362E1C6DF7D8005ACF53 /* Product.swift */; }; FABB25B72602FC2C00C8785C /* PostTag.m in Sources */ = {isa = PBXBuildFile; fileRef = 082AB9DC1C4F035E000CA523 /* PostTag.m */; }; FABB25B82602FC2C00C8785C /* ActivityListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82FC61291FA8B6F000A1757E /* ActivityListViewModel.swift */; }; - FABB25B92602FC2C00C8785C /* UITextField+WorkaroundContinueIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = F19153BC2549ED0800629EC4 /* UITextField+WorkaroundContinueIssue.swift */; }; FABB25BA2602FC2C00C8785C /* StoreKit+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = E177807F1C97FA9500FA7E14 /* StoreKit+Debug.swift */; }; FABB25BB2602FC2C00C8785C /* PrivacySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ADE0EA20A9EF6200D6AADC /* PrivacySettingsViewController.swift */; }; FABB25BC2602FC2C00C8785C /* SharingDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E6431DE21C4E892900FD8D90 /* SharingDetailViewController.m */; }; @@ -8887,7 +8885,6 @@ F1863715253E49B8003D4BEF /* AddSiteAlertFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddSiteAlertFactory.swift; sourceTree = ""; }; F18B43771F849F580089B817 /* PostAttachmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PostAttachmentTests.swift; path = Posts/PostAttachmentTests.swift; sourceTree = ""; }; F18CB8952642E58700B90794 /* FixedSizeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedSizeImageView.swift; sourceTree = ""; }; - F19153BC2549ED0800629EC4 /* UITextField+WorkaroundContinueIssue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+WorkaroundContinueIssue.swift"; sourceTree = ""; }; F198FF6E256D4914001266EB /* WordPressIntents.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WordPressIntents.entitlements; sourceTree = ""; }; F198FF7F256D498A001266EB /* WordPressIntentsRelease-Internal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WordPressIntentsRelease-Internal.entitlements"; sourceTree = ""; }; F198FFB1256D4AB2001266EB /* WordPressIntentsRelease-Alpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WordPressIntentsRelease-Alpha.entitlements"; sourceTree = ""; }; @@ -15210,7 +15207,6 @@ 171CC15724FCEBF7008B7180 /* UINavigationBar+Appearance.swift */, 7326A4A7221C8F4100B4EB8C /* UIStackView+Subviews.swift */, 8BF0B606247D88EB009A7457 /* UITableViewCell+enableDisable.swift */, - F19153BC2549ED0800629EC4 /* UITextField+WorkaroundContinueIssue.swift */, D829C33A21B12EFE00B09F12 /* UIView+Borders.swift */, F17A2A1D23BFBD72001E96AC /* UIView+ExistingConstraints.swift */, 8BA125EA27D8F5E4008B779F /* UIView+PinSubviewPriority.swift */, @@ -22595,7 +22591,6 @@ 0107E16428FFED1800DE87DB /* WidgetConfiguration.swift in Sources */, 82FC612A1FA8B6F000A1757E /* ActivityListViewModel.swift in Sources */, 0CB4056B29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */, - F19153BD2549ED0800629EC4 /* UITextField+WorkaroundContinueIssue.swift in Sources */, E17780801C97FA9500FA7E14 /* StoreKit+Debug.swift in Sources */, E1ADE0EB20A9EF6200D6AADC /* PrivacySettingsViewController.swift in Sources */, E6431DE61C4E892900FD8D90 /* SharingDetailViewController.m in Sources */, @@ -25480,7 +25475,6 @@ FABB25B62602FC2C00C8785C /* Product.swift in Sources */, FABB25B72602FC2C00C8785C /* PostTag.m in Sources */, FABB25B82602FC2C00C8785C /* ActivityListViewModel.swift in Sources */, - FABB25B92602FC2C00C8785C /* UITextField+WorkaroundContinueIssue.swift in Sources */, FABB25BA2602FC2C00C8785C /* StoreKit+Debug.swift in Sources */, FABB25BB2602FC2C00C8785C /* PrivacySettingsViewController.swift in Sources */, 3FFDEF812917882800B625CE /* MigrationNavigationController.swift in Sources */, From 0bbbb53ccce80a33a59e8721f78a2c28ed382b55 Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Fri, 10 Mar 2023 18:16:52 -0500 Subject: [PATCH 096/175] Use `replacePersistentStore` over `migratePersistentStore` There have been performance issues on accounts which have a large number of blogs. On the account which had the issue, there was a memory warning. This is likely due to how `migratePersistentStore` works. The developer documentation on these two functions isn't great but according to a Core Data document, `migratePersistentStore` does the following: 1. Creates a temporary persistence stack. 2. Mounts the old and new stores. 3. Loads all objects from the old store. 4. Migrates the objects to the new store. 5. The objects are given temporary IDs, then assigned to the new store. The new store then saves the newly assigned objects (committing them to the external repository). 6. Informs other stacks that the object IDs have changed (from the old to the new stores), which keeps the stack running after a migration. 7. Unmounts the old store. 8. Returns the new store. Ref: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/PersistentStoreFeatures.html#//apple_ref/doc/uid/TP40001075-CH23-SW1 Even though the function name `replacePersistentStore` implies that it is replacing something, it can be used to backup a database too. If the destination file does not exist, it will create it. According to an Apple engineer, `replacePersistentStore` will attempt to do an APFS clone where possible. This greatly improves both the performance of the backup and the memory footprint. Quote: > Additionally you should almost never use NSPersistentStoreCoordinator's migratePersistentStore... method but instead use the newer replacePersistentStoreAtURL.. (you can replace emptiness to make a copy). The former loads the store into memory so you can do fairly radical things like write it out as a different store type. It pre-dates iOS. The latter will perform an APFS clone where possible. Ref: https://developer.apple.com/forums/thread/651325 --- .../Classes/Utility/CoreDataHelper.swift | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/WordPress/Classes/Utility/CoreDataHelper.swift b/WordPress/Classes/Utility/CoreDataHelper.swift index 9c05910ff15a..afea8bc21751 100644 --- a/WordPress/Classes/Utility/CoreDataHelper.swift +++ b/WordPress/Classes/Utility/CoreDataHelper.swift @@ -211,21 +211,29 @@ extension CoreDataStack { func createStoreCopy(to backupLocation: URL) throws { try? removeBackupData(from: backupLocation) guard let storeCoordinator = mainContext.persistentStoreCoordinator, - let store = storeCoordinator.persistentStores.first else { + let store = storeCoordinator.persistentStores.first, + let currentDatabaseLocation = store.url else { throw ContextManager.ContextManagerError.missingCoordinatorOrStore } - let model = storeCoordinator.managedObjectModel - let storeCoordinatorCopy = NSPersistentStoreCoordinator(managedObjectModel: model) - var storeOptions = store.options - storeOptions?[NSReadOnlyPersistentStoreOption] = true - let storeCopy = try storeCoordinatorCopy.addPersistentStore(ofType: store.type, - configurationName: store.configurationName, - at: store.url, - options: storeOptions) - try storeCoordinatorCopy.migratePersistentStore(storeCopy, - to: backupLocation, - withType: storeCopy.type) + do { + try storeCoordinator.replacePersistentStore(at: backupLocation, + withPersistentStoreFrom: currentDatabaseLocation, + ofType: store.type) + } catch { + // Fallback to the previous migration method + let model = storeCoordinator.managedObjectModel + let storeCoordinatorCopy = NSPersistentStoreCoordinator(managedObjectModel: model) + var storeOptions = store.options + storeOptions?[NSReadOnlyPersistentStoreOption] = true + let storeCopy = try storeCoordinatorCopy.addPersistentStore(ofType: store.type, + configurationName: store.configurationName, + at: store.url, + options: storeOptions) + try storeCoordinatorCopy.migratePersistentStore(storeCopy, + to: backupLocation, + withType: storeCopy.type) + } } /// Removes any copy of the store from the backup location. @@ -257,9 +265,7 @@ extension CoreDataStack { let (databaseLocation, shmLocation, walLocation) = databaseFiles(for: databaseLocation) guard let currentDatabaseLocation = store.url, - FileManager.default.fileExists(atPath: databaseLocation.path), - FileManager.default.fileExists(atPath: shmLocation.path), - FileManager.default.fileExists(atPath: walLocation.path) else { + FileManager.default.fileExists(atPath: databaseLocation.path) else { throw ContextManager.ContextManagerError.missingDatabase } From 48eea91b88050bd5a7bee90bd831b4fd073933ea Mon Sep 17 00:00:00 2001 From: David Christiandy <1299411+dvdchr@users.noreply.github.com> Date: Sat, 1 Jul 2023 13:26:39 +0700 Subject: [PATCH 097/175] Fix background --- .../Classes/ViewRelated/Comments/CommentTableHeaderView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift b/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift index d24d87912ff3..f1a38a3410b7 100644 --- a/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Comments/CommentTableHeaderView.swift @@ -103,8 +103,8 @@ private struct CommentHeaderView: View { disclosureIndicator } } - .background(.ultraThinMaterial) .padding(EdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16)) + .background(.ultraThinMaterial) } var text: some View { From 97bbe96fe115ec2ff91988470aed952c190b1b13 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 4 Jul 2023 11:10:43 +1200 Subject: [PATCH 098/175] Add a release note --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 55182b92a312..7546c0d03e57 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ * [*] Blogging Reminders: Disabled prompt for self-hosted sites not connected to Jetpack. [#20970] * [**] [internal] Do not save synced blogs if the app has signed out. [#20959] * [**] [internal] Make sure synced posts are saved before calling completion block. [#20960] +* [*] [internal] Probably register and unregister some Quick start notification observers. [#20997] 22.7 From 168cacab94a1270d5254471e89c0bdf0936731d2 Mon Sep 17 00:00:00 2001 From: Hassaan El-Garem Date: Tue, 4 Jul 2023 02:44:02 +0300 Subject: [PATCH 099/175] Tests: fix tests build issue --- .../Blaze/BlazeCampaignDetailsWebViewModelTests.swift | 2 +- .../Blaze/BlazeCreateCampaignWebViewModelTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/WordPressTest/Blaze/BlazeCampaignDetailsWebViewModelTests.swift b/WordPress/WordPressTest/Blaze/BlazeCampaignDetailsWebViewModelTests.swift index 7df37b968ded..078af985607a 100644 --- a/WordPress/WordPressTest/Blaze/BlazeCampaignDetailsWebViewModelTests.swift +++ b/WordPress/WordPressTest/Blaze/BlazeCampaignDetailsWebViewModelTests.swift @@ -77,7 +77,7 @@ final class BlazeCampaignDetailsWebViewModelTests: CoreDataTestCase { } } -private class BlazeWebViewMock: BlazeWebView { +private class BlazeWebViewMock: NSObject, BlazeWebView { var loadCalled = false var requestLoaded: URLRequest? diff --git a/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift b/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift index 780deda13ecc..7f01f32103fd 100644 --- a/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift +++ b/WordPress/WordPressTest/Blaze/BlazeCreateCampaignWebViewModelTests.swift @@ -374,7 +374,7 @@ final class BlazeCreateCampaignWebViewModelTests: CoreDataTestCase { } } -private class BlazeWebViewMock: BlazeWebView { +private class BlazeWebViewMock: NSObject, BlazeWebView { var loadCalled = false var requestLoaded: URLRequest? From 33fdc51b0532600997bdf97fc92ffc05244b102d Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 4 Jul 2023 14:09:56 +1200 Subject: [PATCH 100/175] Add a new line between instance properties Co-authored-by: Gio Lodi --- .../Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift b/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift index ff36c31c1667..f07b1623081d 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tags View/ReaderTopicCollectionViewCoordinator.swift @@ -41,6 +41,7 @@ class ReaderTopicCollectionViewCoordinator: NSObject { weak var delegate: ReaderTopicCollectionViewCoordinatorDelegate? weak var collectionView: UICollectionView? + var topics: [String] { didSet { reloadData() From c7b76d93ebc2fc3520266cb1d08cd3697efc2283 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 4 Jul 2023 15:40:21 +1200 Subject: [PATCH 101/175] Move notification handler to its own function --- .../ChangeUsernameViewController.swift | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift index 062ba6d33b29..a40cdea405dd 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift @@ -156,23 +156,30 @@ private extension ChangeUsernameViewController { textField.placeholder = Constants.Alert.confirm NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, - queue: .main) {_ in - if let text = textField.text, - !text.isEmpty, - let username = self?.viewModel.selectedUsername, - text == username { - self?.changeUsernameAction?.isEnabled = true - textField.textColor = .success - return - } - self?.changeUsernameAction?.isEnabled = false - textField.textColor = .text + queue: .main) { [weak self] notification in + self?.handleTextDidChangeNotification(notification) } } DDLogInfo("Prompting user for confirmation of change username") return alertController } + @objc func handleTextDidChangeNotification(_ notification: Foundation.Notification) { + guard let textField = notification.object as? UITextField else { + return + } + + if let text = textField.text, + !text.isEmpty, + text == self.viewModel.selectedUsername { + self.changeUsernameAction?.isEnabled = true + textField.textColor = .success + return + } + self.changeUsernameAction?.isEnabled = false + textField.textColor = .text + } + enum Constants { static let actionButtonTitle = NSLocalizedString("Save", comment: "Settings Text save button title") static let username = NSLocalizedString("Username", comment: "The header and main title") From 6d286e50096b480439977d9639193543444ee7e3 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 4 Jul 2023 15:42:47 +1200 Subject: [PATCH 102/175] Update notification handler function --- .../ChangeUsernameViewController.swift | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift index a40cdea405dd..bdf7803e888e 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift @@ -169,15 +169,9 @@ private extension ChangeUsernameViewController { return } - if let text = textField.text, - !text.isEmpty, - text == self.viewModel.selectedUsername { - self.changeUsernameAction?.isEnabled = true - textField.textColor = .success - return - } - self.changeUsernameAction?.isEnabled = false - textField.textColor = .text + let enabled = textField.text?.isEmpty == false && textField.text == self.viewModel.selectedUsername + changeUsernameAction?.isEnabled = enabled + textField.textColor = enabled ? .success : .text } enum Constants { From 6784a648118e53875cc77bbaf442ab87611b30d1 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 4 Jul 2023 15:51:47 +1200 Subject: [PATCH 103/175] Fix incorrectly unregistered notification observer --- .../ChangeUsernameViewController.swift | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift index bdf7803e888e..2e5affc71539 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift @@ -15,6 +15,8 @@ class ChangeUsernameViewController: SignupUsernameTableViewController { } return saveItem }() + + private weak var confirmationController: UIAlertController? private var changeUsernameAction: UIAlertAction? init(service: AccountSettingsService, settings: AccountSettings?, completionBlock: @escaping CompletionBlock) { @@ -31,6 +33,13 @@ class ChangeUsernameViewController: SignupUsernameTableViewController { super.viewDidLoad() setupViewModel() setupUI() + + NotificationCenter.default.addObserver( + self, + selector: #selector(handleTextDidChangeNotification(_:)), + name: UITextField.textDidChangeNotification, + object: nil + ) } override func viewWillAppear(_ animated: Bool) { @@ -105,7 +114,9 @@ private extension ChangeUsernameViewController { } func save() { - present(changeUsernameConfirmationPrompt(), animated: true) + let controller = changeUsernameConfirmationPrompt() + present(controller, animated: true) + confirmationController = controller } func changeUsername() { @@ -135,10 +146,7 @@ private extension ChangeUsernameViewController { preferredStyle: .alert) alertController.addAttributeMessage(String(format: Constants.Alert.message, viewModel.selectedUsername), highlighted: viewModel.selectedUsername) - alertController.addCancelActionWithTitle(Constants.Alert.cancel, handler: { [weak alertController] _ in - if let textField = alertController?.textFields?.first { - NotificationCenter.default.removeObserver(textField, name: UITextField.textDidChangeNotification, object: nil) - } + alertController.addCancelActionWithTitle(Constants.Alert.cancel, handler: { _ in DDLogInfo("User cancelled alert") }) changeUsernameAction = alertController.addDefaultActionWithTitle(Constants.Alert.change, handler: { [weak alertController] _ in @@ -148,27 +156,30 @@ private extension ChangeUsernameViewController { return } DDLogInfo("User changes username") - NotificationCenter.default.removeObserver(textField, name: UITextField.textDidChangeNotification, object: nil) self.changeUsername() }) changeUsernameAction?.isEnabled = false - alertController.addTextField { [weak self] textField in + alertController.addTextField { textField in textField.placeholder = Constants.Alert.confirm - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, - object: textField, - queue: .main) { [weak self] notification in - self?.handleTextDidChangeNotification(notification) - } } DDLogInfo("Prompting user for confirmation of change username") return alertController } @objc func handleTextDidChangeNotification(_ notification: Foundation.Notification) { - guard let textField = notification.object as? UITextField else { + guard notification.name == UITextField.textDidChangeNotification, + let confirmationController, + let textField = notification.object as? UITextField, + confirmationController.textFields?.contains(textField) == true + else { + DDLogInfo("The notification is not sent from the text field within the change username confirmation prompt") return } + // We need to add another condition to check if the text field is the username confirmation text field, if there + // are more than one text field in the prompt. + precondition(confirmationController.textFields?.count == 1, "There should be only one text field in the prompt") + let enabled = textField.text?.isEmpty == false && textField.text == self.viewModel.selectedUsername changeUsernameAction?.isEnabled = enabled textField.textColor = enabled ? .success : .text From 549f96ee9158e90fa4f182b9ecdcfe754662528a Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 4 Jul 2023 15:53:03 +1200 Subject: [PATCH 104/175] Fix a retain cycle between view controller and alert action --- .../Change Username/ChangeUsernameViewController.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift index 2e5affc71539..13ac46dfe62d 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift @@ -149,8 +149,9 @@ private extension ChangeUsernameViewController { alertController.addCancelActionWithTitle(Constants.Alert.cancel, handler: { _ in DDLogInfo("User cancelled alert") }) - changeUsernameAction = alertController.addDefaultActionWithTitle(Constants.Alert.change, handler: { [weak alertController] _ in - guard let textField = alertController?.textFields?.first, + changeUsernameAction = alertController.addDefaultActionWithTitle(Constants.Alert.change, handler: { [weak alertController, weak self] _ in + guard let self, let alertController else { return } + guard let textField = alertController.textFields?.first, textField.text == self.viewModel.selectedUsername else { DDLogInfo("Username confirmation failed") return From 17c3f69d05c8b1b3a863df2ce62cc0f82e1246d6 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 4 Jul 2023 15:59:05 +1200 Subject: [PATCH 105/175] Delete `changeUsernameAction` property --- .../Change Username/ChangeUsernameViewController.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift index 13ac46dfe62d..7c899e58b112 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift @@ -17,7 +17,6 @@ class ChangeUsernameViewController: SignupUsernameTableViewController { }() private weak var confirmationController: UIAlertController? - private var changeUsernameAction: UIAlertAction? init(service: AccountSettingsService, settings: AccountSettings?, completionBlock: @escaping CompletionBlock) { self.viewModel = ChangeUsernameViewModel(service: service, settings: settings) @@ -149,7 +148,7 @@ private extension ChangeUsernameViewController { alertController.addCancelActionWithTitle(Constants.Alert.cancel, handler: { _ in DDLogInfo("User cancelled alert") }) - changeUsernameAction = alertController.addDefaultActionWithTitle(Constants.Alert.change, handler: { [weak alertController, weak self] _ in + let action = alertController.addDefaultActionWithTitle(Constants.Alert.change, handler: { [weak alertController, weak self] _ in guard let self, let alertController else { return } guard let textField = alertController.textFields?.first, textField.text == self.viewModel.selectedUsername else { @@ -159,7 +158,7 @@ private extension ChangeUsernameViewController { DDLogInfo("User changes username") self.changeUsername() }) - changeUsernameAction?.isEnabled = false + action.isEnabled = false alertController.addTextField { textField in textField.placeholder = Constants.Alert.confirm } @@ -181,6 +180,10 @@ private extension ChangeUsernameViewController { // are more than one text field in the prompt. precondition(confirmationController.textFields?.count == 1, "There should be only one text field in the prompt") + let actions = confirmationController.actions.filter({ $0.title == Constants.Alert.change }) + precondition(actions.count == 1, "More than one 'Change username' action found") + let changeUsernameAction = actions.first + let enabled = textField.text?.isEmpty == false && textField.text == self.viewModel.selectedUsername changeUsernameAction?.isEnabled = enabled textField.textColor = enabled ? .success : .text From 46db53b69726b9ea414ec0c96dfbe7b1f766ce05 Mon Sep 17 00:00:00 2001 From: Povilas Staskus Date: Tue, 4 Jul 2023 11:58:51 +0300 Subject: [PATCH 106/175] Update ContentMigrationCoordinatorTests to use selector observers --- .../ContentMigrationCoordinatorTests.swift | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/WordPress/WordPressTest/ContentMigrationCoordinatorTests.swift b/WordPress/WordPressTest/ContentMigrationCoordinatorTests.swift index 6051d5f717dc..f80e4a678123 100644 --- a/WordPress/WordPressTest/ContentMigrationCoordinatorTests.swift +++ b/WordPress/WordPressTest/ContentMigrationCoordinatorTests.swift @@ -203,7 +203,7 @@ final class ContentMigrationCoordinatorTests: CoreDataTestCase { } func test_coordinatorShouldObserveLogoutNotifications() { - XCTAssertNotNil(mockNotificationCenter.observerBlock) + XCTAssertNotNil(mockNotificationCenter.observerSelector) XCTAssertNotNil(mockNotificationCenter.observedNotificationName) XCTAssertEqual(mockNotificationCenter.observedNotificationName, Foundation.Notification.Name.WPAccountDefaultWordPressComAccountChanged) } @@ -213,7 +213,7 @@ final class ContentMigrationCoordinatorTests: CoreDataTestCase { let loginNotification = mockNotificationCenter.makeLoginNotification() // When - mockNotificationCenter.observerBlock?(loginNotification) + mockNotificationCenter.post(loginNotification) // Then XCTAssertFalse(mockDataMigrator.exportCalled) @@ -226,7 +226,7 @@ final class ContentMigrationCoordinatorTests: CoreDataTestCase { let logoutNotification = mockNotificationCenter.makeLogoutNotification() // When - mockNotificationCenter.observerBlock?(logoutNotification) + mockNotificationCenter.post(logoutNotification) // Then XCTAssertFalse(mockDataMigrator.exportCalled) @@ -300,15 +300,12 @@ private extension ContentMigrationCoordinatorTests { private final class MockNotificationCenter: NotificationCenter { var observedNotificationName: NSNotification.Name? = nil - var observerBlock: ((Foundation.Notification) -> Void)? = nil - - override func addObserver(forName name: NSNotification.Name?, - object obj: Any?, - queue: OperationQueue?, - using block: @escaping @Sendable (Foundation.Notification) -> Void) -> NSObjectProtocol { - observedNotificationName = name - observerBlock = block - return NSNull() + var observerSelector: Selector? = nil + + override func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?) { + observedNotificationName = aName + observerSelector = aSelector + super.addObserver(observer, selector: aSelector, name: aName, object: anObject) } func makeLoginNotification() -> Foundation.Notification { From 3a72bd9b9ca0edcf039b5126862f38c5b15e91d9 Mon Sep 17 00:00:00 2001 From: Momo Ozawa Date: Tue, 4 Jul 2023 15:54:12 +0100 Subject: [PATCH 107/175] remove stats card title hint --- .../Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift index 10745d10adf9..372e9c1c2ee8 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift @@ -39,7 +39,7 @@ class DashboardStatsCardCell: UICollectionViewCell, Reusable { } private func addSubviews() { - frameView.setTitle(Strings.statsTitle, titleHint: Strings.statsTitleHint) + frameView.setTitle(Strings.statsTitle) let statsStackview = DashboardStatsStackView() frameView.add(subview: statsStackview) @@ -133,7 +133,6 @@ private extension DashboardStatsCardCell { enum Strings { static let statsTitle = NSLocalizedString("my-sites.stats.card.title", value: "Today's Stats", comment: "Title for the card displaying today's stats.") - static let statsTitleHint = NSLocalizedString("my-sites.stats.card.title.hint", value: "Stats", comment: "The part of the title that needs to be emphasized") static let nudgeButtonTitle = NSLocalizedString("Interested in building your audience? Check out our top tips", comment: "Title for a button that opens up the 'Getting More Views and Traffic' support page when tapped.") static let nudgeButtonHint = NSLocalizedString("top tips", comment: "The part of the nudge title that should be emphasized, this content needs to match a string in 'If you want to try get more...'") } From 045f8b0de2f6b0e3f3f9c150c8a71295bb9ad530 Mon Sep 17 00:00:00 2001 From: Momo Ozawa Date: Tue, 4 Jul 2023 15:54:43 +0100 Subject: [PATCH 108/175] remove drafts card title hint --- .../Cards/Posts/DashboardPostsListCardCell.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift index 3fbacf3395c3..181c0596a38a 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift @@ -116,7 +116,7 @@ extension DashboardPostsListCardCell { } private func configureDraftsList(blog: Blog) { - frameView.setTitle(Strings.draftsTitle, titleHint: Strings.draftsTitleHint) + frameView.setTitle(Strings.draftsTitle) frameView.onHeaderTap = { [weak self] in self?.presentPostList(with: .draft) } @@ -185,7 +185,6 @@ private extension DashboardPostsListCardCell { private enum Strings { static let draftsTitle = NSLocalizedString("my-sites.drafts.card.title", value: "Work on a draft post", comment: "Title for the card displaying draft posts.") - static let draftsTitleHint = NSLocalizedString("my-sites.drafts.card.title.hint", value: "draft post", comment: "The part in the title that should be highlighted.") static let scheduledTitle = NSLocalizedString("Upcoming scheduled posts", comment: "Title for the card displaying upcoming scheduled posts.") } From 9396404952a956641c8f928e7159038dfcb1af6c Mon Sep 17 00:00:00 2001 From: Momo Ozawa Date: Tue, 4 Jul 2023 15:55:16 +0100 Subject: [PATCH 109/175] =?UTF-8?q?add=20=E2=80=9Cview=20stats=E2=80=9D=20?= =?UTF-8?q?item=20to=20context=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cards/Stats/DashboardStatsCardCell.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift index 372e9c1c2ee8..31e10f8870c7 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Stats/DashboardStatsCardCell.swift @@ -76,7 +76,12 @@ extension DashboardStatsCardCell: BlogDashboardCardConfigurable { if FeatureFlag.personalizeHomeTab.enabled { frameView.addMoreMenu(items: [ - BlogDashboardHelpers.makeHideCardAction(for: .todaysStats, blog: blog) + UIMenu(options: .displayInline, children: [ + makeShowStatsMenuAction(for: blog, in: viewController) + ]), + UIMenu(options: .displayInline, children: [ + BlogDashboardHelpers.makeHideCardAction(for: .todaysStats, blog: blog) + ]) ], card: .todaysStats) } @@ -95,6 +100,12 @@ extension DashboardStatsCardCell: BlogDashboardCardConfigurable { blog: blog) } + private func makeShowStatsMenuAction(for blog: Blog, in viewController: UIViewController) -> UIAction { + UIAction(title: Strings.viewStats, image: UIImage(systemName: "chart.bar.xaxis")) { [weak self] _ in + self?.showStats(for: blog, from: viewController) + } + } + private func showStats(for blog: Blog, from sourceController: UIViewController) { WPAnalytics.track(.dashboardCardItemTapped, properties: ["type": DashboardCard.todaysStats.rawValue], @@ -135,6 +146,7 @@ private extension DashboardStatsCardCell { static let statsTitle = NSLocalizedString("my-sites.stats.card.title", value: "Today's Stats", comment: "Title for the card displaying today's stats.") static let nudgeButtonTitle = NSLocalizedString("Interested in building your audience? Check out our top tips", comment: "Title for a button that opens up the 'Getting More Views and Traffic' support page when tapped.") static let nudgeButtonHint = NSLocalizedString("top tips", comment: "The part of the nudge title that should be emphasized, this content needs to match a string in 'If you want to try get more...'") + static let viewStats = NSLocalizedString("dashboardCard.stats.viewStats", value: "View stats", comment: "Title for the View stats button in the More menu") } enum Constants { From 2ba433c8a466910cfbf9b5232ccbb3e812e2a800 Mon Sep 17 00:00:00 2001 From: Momo Ozawa Date: Tue, 4 Jul 2023 15:55:55 +0100 Subject: [PATCH 110/175] =?UTF-8?q?add=20=E2=80=9Cview=20all=E2=80=A6?= =?UTF-8?q?=E2=80=9D=20item=20to=20posts=20card?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Posts/DashboardPostsListCardCell.swift | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift index 181c0596a38a..b42cfa44e9e6 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift @@ -90,16 +90,17 @@ extension DashboardPostsListCardCell { switch cardType { case .draftPosts: + addDraftsContextMenu(card: cardType, blog: blog) configureDraftsList(blog: blog) status = .draft case .scheduledPosts: + addScheduledContextMenu(card: cardType, blog: blog) configureScheduledList(blog: blog) status = .scheduled default: assertionFailure("Cell used with wrong card type") return } - addContextMenu(card: cardType, blog: blog) viewModel = PostsCardViewModel(blog: blog, status: status, view: self) viewModel?.viewDidLoad() @@ -107,14 +108,44 @@ extension DashboardPostsListCardCell { viewModel?.refresh() } - private func addContextMenu(card: DashboardCard, blog: Blog) { + private func addDraftsContextMenu(card: DashboardCard, blog: Blog) { guard FeatureFlag.personalizeHomeTab.enabled else { return } frameView.addMoreMenu(items: [ - BlogDashboardHelpers.makeHideCardAction(for: card, blog: blog) + UIMenu(options: .displayInline, children: [ + makeDraftsListMenuAction() + ]), + UIMenu(options: .displayInline, children: [ + BlogDashboardHelpers.makeHideCardAction(for: card, blog: blog) + ]) ], card: card) } + private func addScheduledContextMenu(card: DashboardCard, blog: Blog) { + guard FeatureFlag.personalizeHomeTab.enabled else { return } + + frameView.addMoreMenu(items: [ + UIMenu(options: .displayInline, children: [ + makeScheduledListMenuAction() + ]), + UIMenu(options: .displayInline, children: [ + BlogDashboardHelpers.makeHideCardAction(for: card, blog: blog) + ]) + ], card: card) + } + + private func makeDraftsListMenuAction() -> UIAction { + UIAction(title: Strings.viewAllDrafts, image: UIImage(systemName: "square.and.pencil")) { [weak self] _ in + self?.presentPostList(with: .draft) + } + } + + private func makeScheduledListMenuAction() -> UIAction { + UIAction(title: Strings.viewAllScheduledPosts, image: UIImage(systemName: "calendar")) { [weak self] _ in + self?.presentPostList(with: .scheduled) + } + } + private func configureDraftsList(blog: Blog) { frameView.setTitle(Strings.draftsTitle) frameView.onHeaderTap = { [weak self] in @@ -186,6 +217,8 @@ private extension DashboardPostsListCardCell { private enum Strings { static let draftsTitle = NSLocalizedString("my-sites.drafts.card.title", value: "Work on a draft post", comment: "Title for the card displaying draft posts.") static let scheduledTitle = NSLocalizedString("Upcoming scheduled posts", comment: "Title for the card displaying upcoming scheduled posts.") + static let viewAllDrafts = NSLocalizedString("my-sites.drafts.card.viewAllDrafts", value: "View all drafts", comment: "Title for the View all drafts button in the More menu") + static let viewAllScheduledPosts = NSLocalizedString("my-sites.scheduled.card.viewAllScheduledPosts", value: "View all scheduled posts", comment: "Title for the View all scheduled drafts button in the More menu") } enum Constants { From 69b707af3e89f66a0e2da03ca5cac3eb9a5f5346 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 10:13:09 +1200 Subject: [PATCH 111/175] Only observe text field change notification from the alert --- .../ChangeUsernameViewController.swift | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift index 7c899e58b112..8dd57cb91311 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Change Username/ChangeUsernameViewController.swift @@ -1,3 +1,4 @@ +import Combine import WordPressAuthenticator class ChangeUsernameViewController: SignupUsernameTableViewController { @@ -16,7 +17,12 @@ class ChangeUsernameViewController: SignupUsernameTableViewController { return saveItem }() - private weak var confirmationController: UIAlertController? + private var confirmationTextObserver: AnyCancellable? + private weak var confirmationController: UIAlertController? { + didSet { + observeConfirmationTextField() + } + } init(service: AccountSettingsService, settings: AccountSettings?, completionBlock: @escaping CompletionBlock) { self.viewModel = ChangeUsernameViewModel(service: service, settings: settings) @@ -32,13 +38,6 @@ class ChangeUsernameViewController: SignupUsernameTableViewController { super.viewDidLoad() setupViewModel() setupUI() - - NotificationCenter.default.addObserver( - self, - selector: #selector(handleTextDidChangeNotification(_:)), - name: UITextField.textDidChangeNotification, - object: nil - ) } override func viewWillAppear(_ animated: Bool) { @@ -166,19 +165,35 @@ private extension ChangeUsernameViewController { return alertController } - @objc func handleTextDidChangeNotification(_ notification: Foundation.Notification) { - guard notification.name == UITextField.textDidChangeNotification, - let confirmationController, - let textField = notification.object as? UITextField, - confirmationController.textFields?.contains(textField) == true + func observeConfirmationTextField() { + confirmationTextObserver?.cancel() + confirmationTextObserver = nil + + guard let confirmationController, + let textField = confirmationController.textFields?.first else { - DDLogInfo("The notification is not sent from the text field within the change username confirmation prompt") return } // We need to add another condition to check if the text field is the username confirmation text field, if there // are more than one text field in the prompt. - precondition(confirmationController.textFields?.count == 1, "There should be only one text field in the prompt") + assert(confirmationController.textFields?.count == 1, "There should be only one text field in the prompt") + + confirmationTextObserver = NotificationCenter.default + .publisher(for: UITextField.textDidChangeNotification, object: textField) + .sink(receiveValue: { [weak self] in + self?.handleTextDidChangeNotification($0) + }) + } + + func handleTextDidChangeNotification(_ notification: Foundation.Notification) { + guard notification.name == UITextField.textDidChangeNotification, + let confirmationController, + let textField = notification.object as? UITextField + else { + DDLogInfo("The notification is not sent from the text field within the change username confirmation prompt") + return + } let actions = confirmationController.actions.filter({ $0.title == Constants.Alert.change }) precondition(actions.count == 1, "More than one 'Change username' action found") From 66ff6fb6eca37b11b83c97f4231844a7276a9cad Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 10:51:19 +1200 Subject: [PATCH 112/175] Tidy up reset stats widget code --- .../Classes/Stores/StatsWidgetsStore.swift | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/Stores/StatsWidgetsStore.swift b/WordPress/Classes/Stores/StatsWidgetsStore.swift index 0d2bfae230ca..d9532becf851 100644 --- a/WordPress/Classes/Stores/StatsWidgetsStore.swift +++ b/WordPress/Classes/Stores/StatsWidgetsStore.swift @@ -262,18 +262,20 @@ private extension StatsWidgetsStore { } @objc func handleAccountChangedNotification() { - UserDefaults(suiteName: WPAppGroupName)?.setValue(AccountHelper.isLoggedIn, forKey: AppConfiguration.Widget.Stats.userDefaultsLoggedInKey) + let isLoggedIn = AccountHelper.isLoggedIn + let userDefaults = UserDefaults(suiteName: WPAppGroupName) + userDefaults?.setValue(isLoggedIn, forKey: AppConfiguration.Widget.Stats.userDefaultsLoggedInKey) - if !AccountHelper.isLoggedIn { - HomeWidgetTodayData.delete() - HomeWidgetThisWeekData.delete() - HomeWidgetAllTimeData.delete() + guard !isLoggedIn else { return } - UserDefaults(suiteName: WPAppGroupName)?.setValue(nil, forKey: AppConfiguration.Widget.Stats.userDefaultsSiteIdKey) - WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.todayKind) - WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.thisWeekKind) - WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.allTimeKind) - } + HomeWidgetTodayData.delete() + HomeWidgetThisWeekData.delete() + HomeWidgetAllTimeData.delete() + + userDefaults?.setValue(nil, forKey: AppConfiguration.Widget.Stats.userDefaultsSiteIdKey) + WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.todayKind) + WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.thisWeekKind) + WidgetCenter.shared.reloadTimelines(ofKind: AppConfiguration.Widget.Stats.allTimeKind) } /// Observes WPSigninDidFinishNotification and wordpressLoginFinishedJetpackLogin notifications and initializes the widget. From 2ea3f3562523a59cd166715088cbeaa5e92c076c Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 12:58:12 +1200 Subject: [PATCH 113/175] Change DashboardQuickActionsCardCell to use self as the quick start observer --- .../DashboardQuickActionsCardCell.swift | 60 ++++++++----------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift index c1e439313e05..6fef2411bba3 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift @@ -48,8 +48,6 @@ final class DashboardQuickActionsCardCell: UICollectionViewCell, Reusable { return button }() - fileprivate var quickStartObserver: NSObjectProtocol? - override init(frame: CGRect) { super.init(frame: frame) setup() @@ -59,10 +57,6 @@ final class DashboardQuickActionsCardCell: UICollectionViewCell, Reusable { required init?(coder: NSCoder) { fatalError("Not implemented") } - - deinit { - stopObservingQuickStart() - } } // MARK: - Button Actions @@ -144,38 +138,34 @@ extension DashboardQuickActionsCardCell { } private func startObservingQuickStart() { - quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in - - if let info = notification.userInfo, - let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement { - - switch element { - case .stats: - guard QuickStartTourGuide.shared.entryPointForCurrentTour == .blogDashboard else { - return - } - - self?.autoScrollToStatsButton() - case .mediaScreen: - guard QuickStartTourGuide.shared.entryPointForCurrentTour == .blogDashboard else { - return - } - - self?.autoScrollToMediaButton() - default: - break - } - self?.statsButton.shouldShowSpotlight = element == .stats - self?.mediaButton.shouldShowSpotlight = element == .mediaScreen - } - } + NotificationCenter.default.addObserver(self, selector: #selector(handleQuickStartTourElementChangedNotification(_:)), name: .QuickStartTourElementChangedNotification, object: nil) } - private func stopObservingQuickStart() { - if let quickStartObserver { - NotificationCenter.default.removeObserver(quickStartObserver) + @objc private func handleQuickStartTourElementChangedNotification(_ notification: Foundation.Notification) { + guard let info = notification.userInfo, + let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement + else { + return + } + + switch element { + case .stats: + guard QuickStartTourGuide.shared.entryPointForCurrentTour == .blogDashboard else { + return + } + + autoScrollToStatsButton() + case .mediaScreen: + guard QuickStartTourGuide.shared.entryPointForCurrentTour == .blogDashboard else { + return + } + + autoScrollToMediaButton() + default: + break } - quickStartObserver = nil + statsButton.shouldShowSpotlight = element == .stats + mediaButton.shouldShowSpotlight = element == .mediaScreen } private func autoScrollToStatsButton() { From 61724d8dc3da53a796e0d3ac9763e74c2806a36e Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 13:00:51 +1200 Subject: [PATCH 114/175] Change NewQuickStartChecklistView to use self as the quick start observer --- .../NewQuickStartChecklistView.swift | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/NewQuickStartChecklistView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/NewQuickStartChecklistView.swift index 62aa1624feb8..d39586676348 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/NewQuickStartChecklistView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/NewQuickStartChecklistView.swift @@ -99,8 +99,6 @@ final class NewQuickStartChecklistView: UIView, QuickStartChecklistConfigurable return imageView }() - fileprivate var quickStartObserver: NSObjectProtocol? - // MARK: - Init override init(frame: CGRect) { @@ -114,10 +112,6 @@ final class NewQuickStartChecklistView: UIView, QuickStartChecklistConfigurable fatalError("Not implemented") } - deinit { - stopObservingQuickStart() - } - // MARK: - Trait Collection override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -199,23 +193,18 @@ extension NewQuickStartChecklistView { } private func startObservingQuickStart() { - quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in - - guard let userInfo = notification.userInfo, - let element = userInfo[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement, - element == .tourCompleted else { - return - } - - self?.updateViews() - } + NotificationCenter.default.addObserver(self, selector: #selector(handleQuickStartTourElementChangedNotification(_:)), name: .QuickStartTourElementChangedNotification, object: nil) } - private func stopObservingQuickStart() { - if let quickStartObserver { - NotificationCenter.default.removeObserver(quickStartObserver) + @objc private func handleQuickStartTourElementChangedNotification(_ notification: Foundation.Notification) { + guard let userInfo = notification.userInfo, + let element = userInfo[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement, + element == .tourCompleted + else { + return } - quickStartObserver = nil + + updateViews() } @objc private func didTap() { From 52fdf44e5a3fa8882547e1b7c56e73845fc088b0 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 13:04:47 +1200 Subject: [PATCH 115/175] Change QuickStartChecklistView to use self as the quick start observer --- .../Quick Start/QuickStartChecklistView.swift | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/QuickStartChecklistView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/QuickStartChecklistView.swift index 70771abc0f5d..4f8002793e44 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/QuickStartChecklistView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Start/QuickStartChecklistView.swift @@ -70,8 +70,6 @@ final class QuickStartChecklistView: UIView, QuickStartChecklistConfigurable { return view }() - fileprivate var quickStartObserver: NSObjectProtocol? - override init(frame: CGRect) { super.init(frame: frame) setupViews() @@ -82,10 +80,6 @@ final class QuickStartChecklistView: UIView, QuickStartChecklistConfigurable { fatalError("Not implemented") } - deinit { - stopObservingQuickStart() - } - func configure(collection: QuickStartToursCollection, blog: Blog) { self.tours = collection.tours self.blog = blog @@ -136,23 +130,18 @@ extension QuickStartChecklistView { } private func startObservingQuickStart() { - quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] notification in - - guard let userInfo = notification.userInfo, - let element = userInfo[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement, - element == .tourCompleted else { - return - } - - self?.updateViews() - } + NotificationCenter.default.addObserver(self, selector: #selector(handleQuickStartTourElementChangedNotification(_:)), name: .QuickStartTourElementChangedNotification, object: nil) } - private func stopObservingQuickStart() { - if let quickStartObserver { - NotificationCenter.default.removeObserver(quickStartObserver) + @objc private func handleQuickStartTourElementChangedNotification(_ notification: Foundation.Notification) { + guard let userInfo = notification.userInfo, + let element = userInfo[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement, + element == .tourCompleted + else { + return } - quickStartObserver = nil + + updateViews() } @objc private func didTap() { From 4d92db82ab203770fbfd4c8af9fa0f2e3c1c1f32 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 13:08:30 +1200 Subject: [PATCH 116/175] Change MySiteViewController to use self as the quick start observer --- .../MySiteViewController+QuickStart.swift | 37 ++++++++++--------- .../Blog/My Site/MySiteViewController.swift | 2 - 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+QuickStart.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+QuickStart.swift index d29401d72d6e..d07ae63326a2 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+QuickStart.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController+QuickStart.swift @@ -1,32 +1,33 @@ import UIKit -private var observerKey = 0 - extension MySiteViewController { func startObservingQuickStart() { - quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] (notification) in + NotificationCenter.default.addObserver(self, selector: #selector(handleQuickStartTourElementChangedNotification(_:)), name: .QuickStartTourElementChangedNotification, object: nil) + } - if let info = notification.userInfo, - let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement { + @objc private func handleQuickStartTourElementChangedNotification(_ notification: Foundation.Notification) { + guard let info = notification.userInfo, + let element = info[QuickStartTourGuide.notificationElementKey] as? QuickStartTourElement + else { + return + } - self?.siteMenuSpotlightIsShown = element == .siteMenu + siteMenuSpotlightIsShown = element == .siteMenu - switch element { - case .noSuchElement, .newpost: - self?.additionalSafeAreaInsets = .zero + switch element { + case .noSuchElement, .newpost: + additionalSafeAreaInsets = .zero - case .siteIcon, .siteTitle, .viewSite: - self?.scrollView.scrollToTop(animated: true) - fallthrough + case .siteIcon, .siteTitle, .viewSite: + scrollView.scrollToTop(animated: true) + fallthrough - case .siteMenu, .pages, .sharing, .stats, .readerTab, .notifications, .mediaScreen: - self?.additionalSafeAreaInsets = Constants.quickStartNoticeInsets + case .siteMenu, .pages, .sharing, .stats, .readerTab, .notifications, .mediaScreen: + additionalSafeAreaInsets = Constants.quickStartNoticeInsets - default: - break - } - } + default: + break } } diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift index 87ee52d57e8d..4812d2cf0152 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift @@ -27,8 +27,6 @@ class MySiteViewController: UIViewController, NoResultsViewHost { } } - var quickStartObserver: NSObjectProtocol? - private var isShowingDashboard: Bool { return segmentedControl.selectedSegmentIndex == Section.dashboard.rawValue } From 8fd6993060181d8e0ada1187a76b607b67005004 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 13:10:46 +1200 Subject: [PATCH 117/175] Change SitePickerViewController to use self as the quick start observer --- .../SitePickerViewController+QuickStart.swift | 21 +++++++------------ .../SitePickerViewController.swift | 6 ------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+QuickStart.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+QuickStart.swift index 35c75486e827..b828d41e7085 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+QuickStart.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController+QuickStart.swift @@ -10,22 +10,17 @@ extension SitePickerViewController { } func startObservingQuickStart() { - quickStartObserver = NotificationCenter.default.addObserver(forName: .QuickStartTourElementChangedNotification, object: nil, queue: nil) { [weak self] (notification) in - guard self?.blog.managedObjectContext != nil else { - return - } - - self?.blogDetailHeaderView.toggleSpotlightOnSiteTitle() - self?.blogDetailHeaderView.toggleSpotlightOnSiteUrl() - self?.blogDetailHeaderView.refreshIconImage() - } + NotificationCenter.default.addObserver(self, selector: #selector(handleQuickStartTourElementChangedNotification(_:)), name: .QuickStartTourElementChangedNotification, object: nil) } - func stopObservingQuickStart() { - if let quickStartObserver { - NotificationCenter.default.removeObserver(quickStartObserver) + @objc private func handleQuickStartTourElementChangedNotification(_ notification: Foundation.Notification) { + guard blog.managedObjectContext != nil else { + return } - quickStartObserver = nil + + blogDetailHeaderView.toggleSpotlightOnSiteTitle() + blogDetailHeaderView.toggleSpotlightOnSiteUrl() + blogDetailHeaderView.refreshIconImage() } func startAlertTimer() { diff --git a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift index a6a101a69829..381d5cbc695c 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Picker/SitePickerViewController.swift @@ -21,8 +21,6 @@ final class SitePickerViewController: UIViewController { let blogService: BlogService let mediaService: MediaService - var quickStartObserver: NSObjectProtocol? - private(set) lazy var blogDetailHeaderView: BlogDetailHeaderView = { let headerView = BlogDetailHeaderView(items: []) headerView.translatesAutoresizingMaskIntoConstraints = false @@ -51,10 +49,6 @@ final class SitePickerViewController: UIViewController { startObservingTitleChanges() } - deinit { - stopObservingQuickStart() - } - private func setupHeaderView() { blogDetailHeaderView.blog = blog blogDetailHeaderView.delegate = self From 3ee06ad0b4b12b96150c885fa7eb0844e6e1428a Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 13:14:48 +1200 Subject: [PATCH 118/175] Update release note --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 7546c0d03e57..773e1af0c4c1 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,7 +3,7 @@ * [*] Blogging Reminders: Disabled prompt for self-hosted sites not connected to Jetpack. [#20970] * [**] [internal] Do not save synced blogs if the app has signed out. [#20959] * [**] [internal] Make sure synced posts are saved before calling completion block. [#20960] -* [*] [internal] Probably register and unregister some Quick start notification observers. [#20997] +* [**] [internal] Fix observing Quick Start notifications. [#20997] 22.7 From 48b399c102110ba06c383bad5f2c55eca70c4691 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 13:18:06 +1200 Subject: [PATCH 119/175] Address some code style issues --- .../ViewRelated/System/Notices/NoticePresenter.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Notices/NoticePresenter.swift b/WordPress/Classes/ViewRelated/System/Notices/NoticePresenter.swift index a745319309cd..33d80c8c0616 100644 --- a/WordPress/Classes/ViewRelated/System/Notices/NoticePresenter.swift +++ b/WordPress/Classes/ViewRelated/System/Notices/NoticePresenter.swift @@ -102,8 +102,8 @@ class NoticePresenter { private func listenToKeyboardEvents() { NotificationCenter.default .publisher(for: UIResponder.keyboardWillShowNotification) - .sink { [weak self] (notification) in - guard let self = self, + .sink { [weak self] notification in + guard let self, let userInfo = notification.userInfo, let keyboardFrameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue, let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber else { @@ -122,12 +122,13 @@ class NoticePresenter { }) } .store(in: ¬ificationObservers) + NotificationCenter.default .publisher(for: UIResponder.keyboardWillHideNotification) - .sink { [weak self] (notification) in + .sink { [weak self] notification in self?.currentKeyboardPresentation = .notPresent - guard let self = self, + guard let self, let currentContainer = self.currentNoticePresentation?.containerView, let userInfo = notification.userInfo, let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber else { From 66fe483570ed8b570ea1c26a3fa6c601cba26868 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 13:45:23 +1200 Subject: [PATCH 120/175] Use Combine API to observe notifications in WPLoggingStack --- .../Utility/Logging/WPCrashLoggingProvider.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift b/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift index 43770b7f486a..17582673230c 100644 --- a/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift +++ b/WordPress/Classes/Utility/Logging/WPCrashLoggingProvider.swift @@ -1,4 +1,5 @@ import UIKit +import Combine import AutomatticTracks /// A wrapper around the logging stack – provides shared initialization and configuration for Tracks Crash and Event Logging @@ -12,6 +13,8 @@ struct WPLoggingStack { private let eventLoggingDataProvider = EventLoggingDataProvider.fromDDFileLogger(WPLogger.shared().fileLogger) private let eventLoggingDelegate = EventLoggingDelegate() + private let enterForegroundObserver: AnyCancellable + init() { let eventLogging = EventLogging(dataSource: eventLoggingDataProvider, delegate: eventLoggingDelegate) @@ -20,18 +23,16 @@ struct WPLoggingStack { self.crashLogging = CrashLogging(dataProvider: WPCrashLoggingDataProvider(), eventLogging: eventLogging) /// Upload any remaining files any time the app becomes active - let willEnterForeground = UIApplication.willEnterForegroundNotification - NotificationCenter.default.addObserver(forName: willEnterForeground, object: nil, queue: nil, using: self.willEnterForeground) + enterForegroundObserver = NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification) + .sink(receiveValue: { [eventLogging] _ in + eventLogging.uploadNextLogFileIfNeeded() + DDLogDebug("📜 Resumed encrypted log upload queue due to app entering foreground") + }) } func start() throws { _ = try crashLogging.start() } - - private func willEnterForeground(note: Foundation.Notification) { - self.eventLogging.uploadNextLogFileIfNeeded() - DDLogDebug("📜 Resumed encrypted log upload queue due to app entering foreground") - } } struct WPCrashLoggingDataProvider: CrashLoggingDataProvider { From c0443ae5fbab937d4b64202bd437d6582a3953e9 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 16:12:02 +1200 Subject: [PATCH 121/175] Use iOS 15 API to simplify `performQuery` implementation --- WordPress/Classes/Utility/CoreDataHelper.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Utility/CoreDataHelper.swift b/WordPress/Classes/Utility/CoreDataHelper.swift index afea8bc21751..bb193b216bee 100644 --- a/WordPress/Classes/Utility/CoreDataHelper.swift +++ b/WordPress/Classes/Utility/CoreDataHelper.swift @@ -197,11 +197,9 @@ extension ContextManager.ContextManagerError: LocalizedError, CustomDebugStringC extension CoreDataStack { /// Perform a query using the `mainContext` and return the result. func performQuery(_ block: @escaping (NSManagedObjectContext) -> T) -> T { - var value: T! = nil - self.mainContext.performAndWait { - value = block(self.mainContext) + mainContext.performAndWait { [mainContext] in + block(mainContext) } - return value } // MARK: - Database Migration From 8ee703f47940d23070c649dc0409a2a9ad357d65 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 5 Jul 2023 16:49:55 +1200 Subject: [PATCH 122/175] Add warnings about incorrect `performQuery` usages --- .../Classes/Utility/CoreDataHelper.swift | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/WordPress/Classes/Utility/CoreDataHelper.swift b/WordPress/Classes/Utility/CoreDataHelper.swift index afea8bc21751..c241568d2eec 100644 --- a/WordPress/Classes/Utility/CoreDataHelper.swift +++ b/WordPress/Classes/Utility/CoreDataHelper.swift @@ -196,6 +196,8 @@ extension ContextManager.ContextManagerError: LocalizedError, CustomDebugStringC extension CoreDataStack { /// Perform a query using the `mainContext` and return the result. + /// + /// - Warning: Do not return `NSManagedObject` instances from the closure. func performQuery(_ block: @escaping (NSManagedObjectContext) -> T) -> T { var value: T! = nil self.mainContext.performAndWait { @@ -360,3 +362,69 @@ extension CoreDataStack { try ContextManager.migrateDataModelsIfNecessary(storeURL: databaseLocation, objectModel: objectModel) } } + +/// This extension declares many `performQuery` usages that may introduce Core Data concurrency issues. +/// +/// The context object used by the `performQuery` function is opaque to the caller. The caller should not assume what +/// the context object is, nor the context queue type (the main queue or a background queue). That means the caller +/// does not have enough information to guarantee safe access to the returned `NSManagedObject` instances. +/// +/// The closure passed to the `performQuery` function should use the context to query objects and return none Core Data +/// types. Here is an example of how it should be used. +/// +/// ``` +/// // Wrong: +/// let account = coreDataStack.performQuery { context in +/// return Account.lookUp(in: context) +/// } +/// let name = account.username +/// +/// // Right: +/// let name = coreDataStack.performQuery { context in +/// let account = Account.lookUp(in: context) +/// return account.username +/// } +/// ``` +extension CoreDataStack { + @available(*, deprecated, message: "Returning `NSManagedObject` instances may introduce Core Data concurrency issues.") + func performQuery(_ block: @escaping (NSManagedObjectContext) -> T) -> T where T: NSManagedObject { + mainContext.performAndWait { [mainContext] in + block(mainContext) + } + } + + @available(*, deprecated, message: "Returning `NSManagedObject` instances may introduce Core Data concurrency issues.") + func performQuery(_ block: @escaping (NSManagedObjectContext) -> T?) -> T? where T: NSManagedObject { + mainContext.performAndWait { [mainContext] in + block(mainContext) + } + } + + @available(*, deprecated, message: "Returning `NSManagedObject` instances may introduce Core Data concurrency issues.") + func performQuery(_ block: @escaping (NSManagedObjectContext) -> T) -> T where T: Sequence, T.Element: NSManagedObject { + mainContext.performAndWait { [mainContext] in + block(mainContext) + } + } + + @available(*, deprecated, message: "Returning `NSManagedObject` instances may introduce Core Data concurrency issues.") + func performQuery(_ block: @escaping (NSManagedObjectContext) -> T?) -> T? where T: Sequence, T.Element: NSManagedObject { + mainContext.performAndWait { [mainContext] in + block(mainContext) + } + } + + @available(*, deprecated, message: "Returning `NSManagedObject` instances may introduce Core Data concurrency issues.") + func performQuery(_ block: @escaping (NSManagedObjectContext) -> Result) -> Result where T: NSManagedObject, E: Error { + mainContext.performAndWait { [mainContext] in + block(mainContext) + } + } + + @available(*, deprecated, message: "Returning `NSManagedObject` instances may introduce Core Data concurrency issues.") + func performQuery(_ block: @escaping (NSManagedObjectContext) -> Result?) -> Result? where T: NSManagedObject, E: Error { + mainContext.performAndWait { [mainContext] in + block(mainContext) + } + } +} From 78bcc3b0284f3bbcd4f26d31ce0943c5d1028b23 Mon Sep 17 00:00:00 2001 From: Povilas Staskus Date: Wed, 5 Jul 2023 10:15:01 +0300 Subject: [PATCH 123/175] Fix memory leak in domain flow (#20813) * Make DomainsSuggestionsTableViewControllerDelegate as weak to avoid reference cycle * Return self through a callback to avoid capturing self in a closure - Return self reference through CheckoutViewController, PlanSelectionViewController, and RegisterDomainSuggestionsViewController callbacks - It helps avoid capturing viewController in its own callback - The other solution would be using [weak viewController] notation but that is error prone and could still leave memory leak as a possibility * Update RELEASE-NOTES.txt --- RELEASE-NOTES.txt | 1 + .../FreeToPaidPlansCoordinator.swift | 25 +++++++-------- ...ogDetailsViewController+DomainCredit.swift | 2 +- .../CheckoutViewController.swift | 8 +++-- .../PlanSelectionViewController.swift | 6 ++-- ...DomainSuggestionsTableViewController.swift | 4 +-- ...isterDomainSuggestionsViewController.swift | 31 ++++++++++++++----- .../Views/DomainsDashboardFactory.swift | 2 +- .../ViewRelated/Plugins/PluginViewModel.swift | 2 +- 9 files changed, 51 insertions(+), 30 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 55182b92a312..a9519699302d 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ * [*] Blogging Reminders: Disabled prompt for self-hosted sites not connected to Jetpack. [#20970] * [**] [internal] Do not save synced blogs if the app has signed out. [#20959] * [**] [internal] Make sure synced posts are saved before calling completion block. [#20960] +* [**] [internal] Fixed an issue that was causing a memory leak in the domain selection flow. [#20813] 22.7 diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Free to Paid Plans/FreeToPaidPlansCoordinator.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Free to Paid Plans/FreeToPaidPlansCoordinator.swift index 5275cda78594..d7662557ea9c 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Free to Paid Plans/FreeToPaidPlansCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Free to Paid Plans/FreeToPaidPlansCoordinator.swift @@ -13,9 +13,7 @@ import SwiftUI includeSupportButton: false ) - let navigationController = UINavigationController(rootViewController: domainSuggestionsViewController) - - let purchaseCallback = { (domainName: String) in + let purchaseCallback = { (checkoutViewController: CheckoutViewController, domainName: String) in let blogService = BlogService(coreDataStack: ContextManager.shared) blogService.syncBlogAndAllMetadata(blog) { } @@ -25,32 +23,33 @@ import SwiftUI dashboardViewController.reloadCardsLocally() } let viewController = UIHostingController(rootView: resultView) - navigationController.setNavigationBarHidden(true, animated: false) - navigationController.pushViewController(viewController, animated: true) + checkoutViewController.navigationController?.setNavigationBarHidden(true, animated: false) + checkoutViewController.navigationController?.pushViewController(viewController, animated: true) PlansTracker.trackPurchaseResult(source: "plan_selection") } - let planSelected = { (domainName: String, checkoutURL: URL) in + let planSelected = { (planSelectionViewController: PlanSelectionViewController, domainName: String, checkoutURL: URL) in let viewModel = CheckoutViewModel(url: checkoutURL) - let checkoutViewController = CheckoutViewController(viewModel: viewModel, purchaseCallback: { - purchaseCallback(domainName) + let checkoutViewController = CheckoutViewController(viewModel: viewModel, purchaseCallback: { checkoutViewController in + purchaseCallback(checkoutViewController, domainName) }) checkoutViewController.configureSandboxStore { - navigationController.pushViewController(checkoutViewController, animated: true) + planSelectionViewController.navigationController?.pushViewController(checkoutViewController, animated: true) } } - let domainAddedToCart = { (domainName: String) in + let domainAddedToCart = { (domainViewController: RegisterDomainSuggestionsViewController, domainName: String) in guard let viewModel = PlanSelectionViewModel(blog: blog) else { return } let planSelectionViewController = PlanSelectionViewController(viewModel: viewModel) - planSelectionViewController.planSelectedCallback = { checkoutURL in - planSelected(domainName, checkoutURL) + planSelectionViewController.planSelectedCallback = { planSelectionViewController, checkoutURL in + planSelected(planSelectionViewController, domainName, checkoutURL) } - navigationController.pushViewController(planSelectionViewController, animated: true) + domainViewController.navigationController?.pushViewController(planSelectionViewController, animated: true) } domainSuggestionsViewController.domainAddedToCartCallback = domainAddedToCart + let navigationController = UINavigationController(rootViewController: domainSuggestionsViewController) dashboardViewController.present(navigationController, animated: true) } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+DomainCredit.swift b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+DomainCredit.swift index d16a1f98bdc0..023947bda2c6 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+DomainCredit.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+DomainCredit.swift @@ -22,7 +22,7 @@ extension BlogDetailsViewController { @objc func showDomainCreditRedemption() { let controller = RegisterDomainSuggestionsViewController - .instance(site: blog, domainSelectionType: .registerWithPaidPlan, domainPurchasedCallback: { [weak self] domain in + .instance(site: blog, domainSelectionType: .registerWithPaidPlan, domainPurchasedCallback: { [weak self] _, domain in WPAnalytics.track(.domainCreditRedemptionSuccess) self?.presentDomainCreditRedemptionSuccess(domain: domain) }) diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/CheckoutViewController.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/CheckoutViewController.swift index 9d03f782ec68..c7202961949d 100644 --- a/WordPress/Classes/ViewRelated/Domains/Domain registration/CheckoutViewController.swift +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/CheckoutViewController.swift @@ -5,12 +5,14 @@ struct CheckoutViewModel { } final class CheckoutViewController: WebKitViewController { + typealias PurchaseCallback = ((CheckoutViewController) -> Void) + let viewModel: CheckoutViewModel - let purchaseCallback: (() -> Void)? + let purchaseCallback: PurchaseCallback? private var webViewURLChangeObservation: NSKeyValueObservation? - init(viewModel: CheckoutViewModel, purchaseCallback: (() -> Void)?) { + init(viewModel: CheckoutViewModel, purchaseCallback: PurchaseCallback?) { self.viewModel = viewModel self.purchaseCallback = purchaseCallback @@ -40,7 +42,7 @@ final class CheckoutViewController: WebKitViewController { } if newURL.absoluteString.hasPrefix("https://wordpress.com/checkout/thank-you") { - self.purchaseCallback?() + self.purchaseCallback?(self) /// Stay on Checkout page self.webView.goBack() diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/PlanSelectionViewController.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/PlanSelectionViewController.swift index 3ada117f8c2b..663ba63f3c0e 100644 --- a/WordPress/Classes/ViewRelated/Domains/Domain registration/PlanSelectionViewController.swift +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/PlanSelectionViewController.swift @@ -37,8 +37,10 @@ struct PlanSelectionViewModel { } final class PlanSelectionViewController: WebKitViewController { + typealias PlanSelectionCallback = (PlanSelectionViewController, URL) -> Void + let viewModel: PlanSelectionViewModel - var planSelectedCallback: ((URL) -> Void)? + var planSelectedCallback: PlanSelectionCallback? private var webViewURLChangeObservation: NSKeyValueObservation? @@ -65,7 +67,7 @@ final class PlanSelectionViewController: WebKitViewController { } if self.viewModel.isPlanSelected(newURL) { - self.planSelectedCallback?(newURL) + self.planSelectedCallback?(self, newURL) /// Stay on Plan Selection page self.webView.goBack() diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/DomainSuggestionsTableViewController.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/DomainSuggestionsTableViewController.swift index 76e027713cc7..868ce858ef5f 100644 --- a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/DomainSuggestionsTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/DomainSuggestionsTableViewController.swift @@ -3,7 +3,7 @@ import SVProgressHUD import WordPressAuthenticator -protocol DomainSuggestionsTableViewControllerDelegate { +protocol DomainSuggestionsTableViewControllerDelegate: AnyObject { func domainSelected(_ domain: FullyQuotedDomainSuggestion) func newSearchStarted() } @@ -30,7 +30,7 @@ class DomainSuggestionsTableViewController: UITableViewController { var blog: Blog? var siteName: String? - var delegate: DomainSuggestionsTableViewControllerDelegate? + weak var delegate: DomainSuggestionsTableViewControllerDelegate? var domainSuggestionType: DomainsServiceRemote.DomainSuggestionType = .noWordpressDotCom var domainSelectionType: DomainSelectionType? var freeSiteAddress: String = "" diff --git a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainSuggestionsViewController.swift b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainSuggestionsViewController.swift index cb9a81b49317..526b589fa840 100644 --- a/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainSuggestionsViewController.swift +++ b/WordPress/Classes/ViewRelated/Domains/Domain registration/RegisterDomainSuggestions/RegisterDomainSuggestionsViewController.swift @@ -11,14 +11,17 @@ enum DomainSelectionType { } class RegisterDomainSuggestionsViewController: UIViewController { + typealias DomainPurchasedCallback = ((RegisterDomainSuggestionsViewController, String) -> Void) + typealias DomainAddedToCartCallback = ((RegisterDomainSuggestionsViewController, String) -> Void) + @IBOutlet weak var buttonContainerBottomConstraint: NSLayoutConstraint! @IBOutlet weak var buttonContainerViewHeightConstraint: NSLayoutConstraint! private var constraintsInitialized = false private var site: Blog! - var domainPurchasedCallback: ((String) -> Void)! - var domainAddedToCartCallback: ((String) -> Void)? + var domainPurchasedCallback: DomainPurchasedCallback! + var domainAddedToCartCallback: DomainAddedToCartCallback? private var domain: FullyQuotedDomainSuggestion? private var siteName: String? @@ -53,7 +56,7 @@ class RegisterDomainSuggestionsViewController: UIViewController { static func instance(site: Blog, domainSelectionType: DomainSelectionType = .registerWithPaidPlan, includeSupportButton: Bool = true, - domainPurchasedCallback: ((String) -> Void)? = nil) -> RegisterDomainSuggestionsViewController { + domainPurchasedCallback: DomainPurchasedCallback? = nil) -> RegisterDomainSuggestionsViewController { let storyboard = UIStoryboard(name: Constants.storyboardIdentifier, bundle: Bundle.main) let controller = storyboard.instantiateViewController(withIdentifier: Constants.viewControllerIdentifier) as! RegisterDomainSuggestionsViewController controller.site = site @@ -243,8 +246,12 @@ extension RegisterDomainSuggestionsViewController: NUXButtonViewControllerDelega createCart( domain, onSuccess: { [weak self] in - self?.domainAddedToCartCallback?(domain.domainName) - self?.setPrimaryButtonLoading(false, afterDelay: 0.25) + guard let self = self else { + return + } + + self.domainAddedToCartCallback?(self, domain.domainName) + self.setPrimaryButtonLoading(false, afterDelay: 0.25) }, onFailure: onFailure ) @@ -267,7 +274,13 @@ extension RegisterDomainSuggestionsViewController: NUXButtonViewControllerDelega } let controller = RegisterDomainDetailsViewController() - controller.viewModel = RegisterDomainDetailsViewModel(siteID: siteID, domain: domain, domainPurchasedCallback: domainPurchasedCallback) + controller.viewModel = RegisterDomainDetailsViewModel(siteID: siteID, domain: domain) { [weak self] name in + guard let self = self else { + return + } + + self.domainPurchasedCallback?(self, name) + } self.navigationController?.pushViewController(controller, animated: true) } @@ -354,7 +367,11 @@ extension RegisterDomainSuggestionsViewController: NUXButtonViewControllerDelega navController.dismiss(animated: true) }) { domain in self.dismiss(animated: true, completion: { [weak self] in - self?.domainPurchasedCallback(domain) + guard let self = self else { + return + } + + self.domainPurchasedCallback(self, domain) }) } } diff --git a/WordPress/Classes/ViewRelated/Domains/Views/DomainsDashboardFactory.swift b/WordPress/Classes/ViewRelated/Domains/Views/DomainsDashboardFactory.swift index ed7d8b2f862b..b720d367e9c6 100644 --- a/WordPress/Classes/ViewRelated/Domains/Views/DomainsDashboardFactory.swift +++ b/WordPress/Classes/ViewRelated/Domains/Views/DomainsDashboardFactory.swift @@ -14,7 +14,7 @@ struct DomainsDashboardFactory { domainSelectionType: domainSelectionType, includeSupportButton: false) - viewController.domainPurchasedCallback = { domain in + viewController.domainPurchasedCallback = { viewController, domain in let blogService = BlogService(coreDataStack: ContextManager.shared) blogService.syncBlogAndAllMetadata(blog) { } WPAnalytics.track(.domainCreditRedemptionSuccess) diff --git a/WordPress/Classes/ViewRelated/Plugins/PluginViewModel.swift b/WordPress/Classes/ViewRelated/Plugins/PluginViewModel.swift index b01a48adea0f..db15ba3332b9 100644 --- a/WordPress/Classes/ViewRelated/Plugins/PluginViewModel.swift +++ b/WordPress/Classes/ViewRelated/Plugins/PluginViewModel.swift @@ -540,7 +540,7 @@ class PluginViewModel: Observable { return } - let controller = RegisterDomainSuggestionsViewController.instance(site: blog, domainPurchasedCallback: { [weak self] domain in + let controller = RegisterDomainSuggestionsViewController.instance(site: blog, domainPurchasedCallback: { [weak self] _, domain in guard let strongSelf = self, let atHelper = AutomatedTransferHelper(site: strongSelf.site, plugin: directoryEntry) else { From 2352e9426b95da4c005fcba7b329e53222bc22f8 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 5 Jul 2023 18:15:30 -0400 Subject: [PATCH 124/175] Fix integration issue with details page --- .../Blaze Campaigns/BlazeCampaignsViewController.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift index 08391a418d79..f442473c0d46 100644 --- a/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift +++ b/WordPress/Classes/ViewRelated/Blaze Campaigns/BlazeCampaignsViewController.swift @@ -199,10 +199,9 @@ extension BlazeCampaignsViewController: UITableViewDataSource, UITableViewDelega func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - guard let campaign = campaigns[safe: indexPath.row] else { + guard let campaign = stream.campaigns[safe: indexPath.row] else { return } - BlazeFlowCoordinator.presentBlazeCampaignDetails(in: self, source: .campaignsList, blog: blog, campaignID: campaign.campaignID) } } From e44432eb019fc263ab39a51fdc6a75f86ab31dd9 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 6 Jul 2023 10:20:08 +1200 Subject: [PATCH 125/175] Fix memory leaks on viewing a post from a notification --- .../Reader/Comments/ReaderCommentsFollowPresenter.swift | 2 +- .../Detail/Views/ReaderDetailCommentsTableViewDelegate.swift | 2 +- .../Reader/Detail/Views/ReaderDetailHeaderView.swift | 4 ++-- .../Reader/Detail/Views/ReaderDetailLikesView.swift | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsFollowPresenter.swift b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsFollowPresenter.swift index d3d24c21c008..6736584f10a2 100644 --- a/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsFollowPresenter.swift +++ b/WordPress/Classes/ViewRelated/Reader/Comments/ReaderCommentsFollowPresenter.swift @@ -15,7 +15,7 @@ class ReaderCommentsFollowPresenter: NSObject { private let post: ReaderPost private weak var delegate: ReaderCommentsFollowPresenterDelegate? - private let presentingViewController: UIViewController + private unowned let presentingViewController: UIViewController private let followCommentsService: FollowCommentsService? // MARK: - Initialization diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailCommentsTableViewDelegate.swift b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailCommentsTableViewDelegate.swift index 4cf701ed5f16..362e679b03b8 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailCommentsTableViewDelegate.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailCommentsTableViewDelegate.swift @@ -7,7 +7,7 @@ class ReaderDetailCommentsTableViewDelegate: NSObject, UITableViewDataSource, UI private(set) var totalComments = 0 private var post: ReaderPost? - private var presentingViewController: UIViewController? + private weak var presentingViewController: UIViewController? private weak var buttonDelegate: BorderedButtonTableViewCellDelegate? private(set) var headerView: ReaderDetailCommentsHeader? var followButtonTappedClosure: (() ->Void)? diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift index e568c03ecef8..7109c297eb0e 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift @@ -1,7 +1,7 @@ import UIKit import AutomatticTracks -protocol ReaderDetailHeaderViewDelegate { +protocol ReaderDetailHeaderViewDelegate: AnyObject { func didTapBlogName() func didTapMenuButton(_ sender: UIView) func didTapHeaderAvatar() @@ -43,7 +43,7 @@ class ReaderDetailHeaderView: UIStackView, NibLoadable { /// Any interaction with the header is sent to the delegate /// - var delegate: ReaderDetailHeaderViewDelegate? + weak var delegate: ReaderDetailHeaderViewDelegate? func configure(for post: ReaderPost) { self.post = post diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailLikesView.swift b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailLikesView.swift index 6e220b113af7..be506522818d 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailLikesView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailLikesView.swift @@ -1,6 +1,6 @@ import UIKit -protocol ReaderDetailLikesViewDelegate { +protocol ReaderDetailLikesViewDelegate: AnyObject { func didTapLikesView() } @@ -13,7 +13,7 @@ class ReaderDetailLikesView: UIView, NibLoadable { @IBOutlet private weak var selfAvatarImageView: CircularImageView! static let maxAvatarsDisplayed = 5 - var delegate: ReaderDetailLikesViewDelegate? + weak var delegate: ReaderDetailLikesViewDelegate? /// Stores the number of total likes _without_ adding the like from self. private var totalLikes: Int = 0 From 6472ccd095667a40e47eab9abb3882f1942f5977 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 5 Jul 2023 18:39:58 -0400 Subject: [PATCH 126/175] Show campaign details page from dashboard --- .../Blaze/DashboardBlazeCampaignsCardView.swift | 16 ++++++++++++++-- .../Blaze/DashboardBlazeCardCellViewModel.swift | 6 +++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift index a80debbd3238..3db39894eb7c 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift @@ -26,6 +26,7 @@ final class DashboardBlazeCampaignsCardView: UIView { private var blog: Blog? private weak var presentingViewController: BlogDashboardViewController? + private var campaign: BlazeCampaign? // MARK: - Initializers @@ -69,6 +70,10 @@ final class DashboardBlazeCampaignsCardView: UIView { frameView.onHeaderTap = { [weak self] in self?.showCampaignList() } + + campaignView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(campainViewTapped))) + campaignView.isAccessibilityElement = true + campaignView.accessibilityTraits = .allowsDirectInteraction } private func showCampaignList() { @@ -82,15 +87,21 @@ final class DashboardBlazeCampaignsCardView: UIView { } } + @objc private func campainViewTapped() { + guard let presentingViewController, let blog, let campaign else { return } + BlazeFlowCoordinator.presentBlazeCampaignDetails(in: presentingViewController, source: .dashboardCard, blog: blog, campaignID: campaign.campaignID) + } + @objc private func buttonCreateCampaignTapped() { guard let presentingViewController, let blog else { return } BlazeEventsTracker.trackEntryPointTapped(for: .dashboardCard) BlazeFlowCoordinator.presentBlaze(in: presentingViewController, source: .dashboardCard, blog: blog) } - func configure(blog: Blog, viewController: BlogDashboardViewController?, campaign: BlazeCampaignViewModel) { + func configure(blog: Blog, viewController: BlogDashboardViewController?, campaign: BlazeCampaign) { self.blog = blog self.presentingViewController = viewController + self.campaign = campaign frameView.addMoreMenu(items: [ UIMenu(options: .displayInline, children: [ @@ -101,7 +112,8 @@ final class DashboardBlazeCampaignsCardView: UIView { ]) ], card: .blaze) - campaignView.configure(with: campaign, blog: blog) + let viewModel = BlazeCampaignViewModel(campaign: campaign) + campaignView.configure(with: viewModel, blog: blog) } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift index 7360fdbdc6d2..fecbb50eaac3 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCardCellViewModel.swift @@ -14,7 +14,7 @@ final class DashboardBlazeCardCellViewModel { /// Showing "Promote you content with Blaze" promo card. case promo /// Showing the latest Blaze campaign. - case campaign(BlazeCampaignViewModel) + case campaign(BlazeCampaign) } var onRefresh: ((DashboardBlazeCardCellViewModel) -> Void)? @@ -31,7 +31,7 @@ final class DashboardBlazeCardCellViewModel { if isBlazeCampaignsFlagEnabled(), let blogID = blog.dotComID?.intValue, let campaign = store.getBlazeCampaign(forBlogID: blogID) { - self.state = .campaign(BlazeCampaignViewModel(campaign: campaign)) + self.state = .campaign(campaign) } NotificationCenter.default.addObserver(self, selector: #selector(refresh), name: .blazeCampaignCreated, object: nil) @@ -57,7 +57,7 @@ final class DashboardBlazeCardCellViewModel { store.setBlazeCampaign(campaign, forBlogID: blogID) } if let campaign { - state = .campaign(BlazeCampaignViewModel(campaign: campaign)) + state = .campaign(campaign) } else { state = .promo } From 12554eaf066b819e0180c144fb314e6187d1eb1d Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 6 Jul 2023 10:50:16 +1200 Subject: [PATCH 127/175] Update a code comment Co-authored-by: Paul Von Schrottky <1898325+guarani@users.noreply.github.com> --- WordPress/Classes/Utility/CoreDataHelper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/Utility/CoreDataHelper.swift b/WordPress/Classes/Utility/CoreDataHelper.swift index c241568d2eec..095ebcb8aa94 100644 --- a/WordPress/Classes/Utility/CoreDataHelper.swift +++ b/WordPress/Classes/Utility/CoreDataHelper.swift @@ -369,7 +369,7 @@ extension CoreDataStack { /// the context object is, nor the context queue type (the main queue or a background queue). That means the caller /// does not have enough information to guarantee safe access to the returned `NSManagedObject` instances. /// -/// The closure passed to the `performQuery` function should use the context to query objects and return none Core Data +/// The closure passed to the `performQuery` function should use the context to query objects and return non- Core Data /// types. Here is an example of how it should be used. /// /// ``` From 6284867beaeb0c310cbd020748175dc943eae6fc Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 5 Jul 2023 18:59:24 -0400 Subject: [PATCH 128/175] Make dashboard campaign card accessible --- .../Cards/Blaze/DashboardBlazeCampaignView.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift index 84ba63790dac..db9ba0ac416c 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignView.swift @@ -73,6 +73,8 @@ final class DashboardBlazeCampaignView: UIView { if viewModel.isShowingStats { makeStatsViews(for: viewModel).forEach(statsView.addArrangedSubview) } + + enableVoiceOver(with: viewModel) } private func makeStatsViews(for viewModel: BlazeCampaignViewModel) -> [UIView] { @@ -84,6 +86,16 @@ final class DashboardBlazeCampaignView: UIView { return [impressionsView, clicksView] } + + private func enableVoiceOver(with viewModel: BlazeCampaignViewModel) { + var accessibilityLabel = "\(viewModel.title), \(viewModel.status.title)" + if viewModel.isShowingStats { + accessibilityLabel += ", \(Strings.impressions) \(viewModel.impressions), \(Strings.clicks) \(viewModel.clicks)" + } + self.isAccessibilityElement = true + self.accessibilityLabel = accessibilityLabel + self.accessibilityTraits = .allowsDirectInteraction + } } private extension DashboardBlazeCampaignView { From 0ae7ff802588af45108fd8a638913210532a0ff4 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 5 Jul 2023 19:20:02 -0400 Subject: [PATCH 129/175] Fix deprecation warning --- .../DashboardBlazeCampaignsCardView.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift index 3db39894eb7c..b9a235af3567 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift @@ -14,13 +14,19 @@ final class DashboardBlazeCampaignsCardView: UIView { private lazy var createCampaignButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false + button.configuration = { + var configuration = UIButton.Configuration.plain() + configuration.attributedTitle = { + var string = AttributedString(Strings.createCampaignButton) + string.font = WPStyleGuide.fontForTextStyle(.callout, fontWeight: .bold) + string.foregroundColor = UIColor.primary + return string + }() + configuration.contentInsets = Constants.createCampaignInsets + return configuration + }() button.contentHorizontalAlignment = .leading - button.setTitle(Strings.createCampaignButton, for: .normal) button.addTarget(self, action: #selector(buttonCreateCampaignTapped), for: .touchUpInside) - button.titleLabel?.font = WPStyleGuide.fontForTextStyle(.callout, fontWeight: .bold) - button.titleLabel?.adjustsFontForContentSizeCategory = true - button.setTitleColor(UIColor.primary, for: .normal) - button.contentEdgeInsets = Constants.createCampaignInsets return button }() @@ -126,6 +132,6 @@ private extension DashboardBlazeCampaignsCardView { enum Constants { static let campaignViewInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) - static let createCampaignInsets = UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16) + static let createCampaignInsets = NSDirectionalEdgeInsets(top: 16, leading: 16, bottom: 8, trailing: 16) } } From 19c768213ff2e8d70269451f9c93d7118c14da01 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 6 Jul 2023 11:26:22 +1200 Subject: [PATCH 130/175] Fix memory leaks in ReaderStreamViewController --- .../Classes/ViewRelated/Reader/ReaderListStreamHeader.swift | 2 +- .../ViewRelated/Reader/ReaderRecommendedSiteCardCell.swift | 4 ++-- .../Reader/ReaderSearchSuggestionsViewController.swift | 4 ++-- .../Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift | 2 +- .../Classes/ViewRelated/Reader/ReaderStreamHeader.swift | 6 +++--- .../Classes/ViewRelated/Reader/ReaderTagStreamHeader.swift | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderListStreamHeader.swift b/WordPress/Classes/ViewRelated/Reader/ReaderListStreamHeader.swift index 1f2659a9bc76..9529a374a07d 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderListStreamHeader.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderListStreamHeader.swift @@ -7,7 +7,7 @@ import WordPressShared.WPStyleGuide @IBOutlet fileprivate weak var detailLabel: UILabel! // Required by ReaderStreamHeader protocol. - open var delegate: ReaderStreamHeaderDelegate? + open weak var delegate: ReaderStreamHeaderDelegate? // MARK: - Lifecycle Methods diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderRecommendedSiteCardCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderRecommendedSiteCardCell.swift index 5f59ecec911c..3c30c5a570fe 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderRecommendedSiteCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderRecommendedSiteCardCell.swift @@ -9,7 +9,7 @@ class ReaderRecommendedSiteCardCell: UITableViewCell { @IBOutlet weak var descriptionLabel: UILabel! @IBOutlet weak var infoTrailingConstraint: NSLayoutConstraint! - var delegate: ReaderRecommendedSitesCardCellDelegate? + weak var delegate: ReaderRecommendedSitesCardCellDelegate? override func awakeFromNib() { super.awakeFromNib() @@ -110,6 +110,6 @@ class ReaderRecommendedSiteCardCell: UITableViewCell { } } -protocol ReaderRecommendedSitesCardCellDelegate { +protocol ReaderRecommendedSitesCardCellDelegate: AnyObject { func handleFollowActionForCell(_ cell: ReaderRecommendedSiteCardCell) } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSearchSuggestionsViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSearchSuggestionsViewController.swift index c8483a61d375..3169d407021b 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSearchSuggestionsViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSearchSuggestionsViewController.swift @@ -5,7 +5,7 @@ import WordPressShared /// Defines methods that a delegate should implement for clearing suggestions /// and for responding to a selected suggestion. /// -protocol ReaderSearchSuggestionsDelegate { +protocol ReaderSearchSuggestionsDelegate: AnyObject { func searchSuggestionsController(_ controller: ReaderSearchSuggestionsViewController, selectedItem: String) } @@ -28,7 +28,7 @@ class ReaderSearchSuggestionsViewController: UIViewController { } @objc var tableViewHandler: WPTableViewHandler! - var delegate: ReaderSearchSuggestionsDelegate? + weak var delegate: ReaderSearchSuggestionsDelegate? @objc let cellIdentifier = "CellIdentifier" @objc let rowAndButtonHeight = CGFloat(44.0) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift index 71cad564149b..b4437e4e2a05 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSiteStreamHeader.swift @@ -36,7 +36,7 @@ fileprivate func > (lhs: T?, rhs: T?) -> Bool { @IBOutlet fileprivate weak var descriptionLabel: UILabel! @IBOutlet fileprivate weak var descriptionLabelTopConstraint: NSLayoutConstraint! - open var delegate: ReaderStreamHeaderDelegate? + open weak var delegate: ReaderStreamHeaderDelegate? fileprivate var defaultBlavatar = "blavatar-default" // MARK: - Lifecycle Methods diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamHeader.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamHeader.swift index d970a7c02cbf..137cbc363037 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamHeader.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamHeader.swift @@ -1,11 +1,11 @@ import Foundation -public protocol ReaderStreamHeaderDelegate: NSObjectProtocol { +@objc public protocol ReaderStreamHeaderDelegate { func handleFollowActionForHeader(_ header: ReaderStreamHeader, completion: @escaping () -> Void) } -public protocol ReaderStreamHeader: NSObjectProtocol { - var delegate: ReaderStreamHeaderDelegate? {get set} +@objc public protocol ReaderStreamHeader { + weak var delegate: ReaderStreamHeaderDelegate? {get set} func enableLoggedInFeatures(_ enable: Bool) func configureHeader(_ topic: ReaderAbstractTopic) } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagStreamHeader.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagStreamHeader.swift index 0ce723d11d09..55278fcbe80f 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagStreamHeader.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagStreamHeader.swift @@ -5,7 +5,7 @@ import WordPressShared @IBOutlet fileprivate weak var titleLabel: UILabel! @IBOutlet fileprivate weak var followButton: UIButton! - open var delegate: ReaderStreamHeaderDelegate? + open weak var delegate: ReaderStreamHeaderDelegate? // MARK: - Lifecycle Methods open override func awakeFromNib() { From 9d606cfe68b59aa42b60e222f01a761ef0ae1216 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 6 Jul 2023 12:49:02 +0200 Subject: [PATCH 131/175] Release script: Update gutenberg-mobile ref --- Gutenberg/version.rb | 4 +- Podfile.lock | 204 +++++++++++++++++++++---------------------- 2 files changed, 104 insertions(+), 104 deletions(-) diff --git a/Gutenberg/version.rb b/Gutenberg/version.rb index 4722006d7961..4b3181cf5096 100644 --- a/Gutenberg/version.rb +++ b/Gutenberg/version.rb @@ -11,8 +11,8 @@ # # LOCAL_GUTENBERG=../my-gutenberg-fork bundle exec pod install GUTENBERG_CONFIG = { - # commit: '' - tag: 'v1.98.1' + commit: '6fe745be5342afe1c82f45e57d12a02c1b72e1de' + # tag: 'v1.98.1' } GITHUB_ORG = 'wordpress-mobile' diff --git a/Podfile.lock b/Podfile.lock index 9ba1587d52a0..d3d02b4a9ec2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -54,7 +54,7 @@ PODS: - AppAuth/Core (~> 1.6) - GTMSessionFetcher/Core (< 3.0, >= 1.5) - GTMSessionFetcher/Core (1.7.2) - - Gutenberg (1.98.1): + - Gutenberg (1.99.0): - React (= 0.69.4) - React-CoreModules (= 0.69.4) - React-RCTImage (= 0.69.4) @@ -481,7 +481,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.98.1): + - RNTAztecView (1.99.0): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - SDWebImage (5.11.1): @@ -545,18 +545,18 @@ DEPENDENCIES: - AppCenter (~> 4.1) - AppCenter/Distribute (~> 4.1) - Automattic-Tracks-iOS (~> 2.2) - - boost (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/boost.podspec.json`) - - BVLinearGradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/BVLinearGradient.podspec.json`) + - boost (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/boost.podspec.json`) + - BVLinearGradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/BVLinearGradient.podspec.json`) - CocoaLumberjack/Swift (~> 3.0) - CropViewController (= 2.5.3) - Down (~> 0.6.6) - - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/FBLazyVector.podspec.json`) - - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json`) + - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/FBLazyVector.podspec.json`) + - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json`) - FSInteractiveMap (from `https://github.com/wordpress-mobile/FSInteractiveMap.git`, tag `0.2.0`) - Gifu (= 3.2.0) - - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/glog.podspec.json`) + - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/glog.podspec.json`) - Gridicons (~> 1.1.0) - - Gutenberg (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, tag `v1.98.1`) + - Gutenberg (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, commit `6fe745be5342afe1c82f45e57d12a02c1b72e1de`) - JTAppleCalendar (~> 8.0.2) - Kanvas (~> 1.4.4) - MediaEditor (>= 1.2.2, ~> 1.2) @@ -565,48 +565,48 @@ DEPENDENCIES: - "NSURL+IDN (~> 0.4)" - OCMock (~> 3.4.3) - OHHTTPStubs/Swift (~> 9.1.0) - - RCT-Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RCT-Folly.podspec.json`) - - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RCTRequired.podspec.json`) - - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RCTTypeSafety.podspec.json`) + - RCT-Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCT-Folly.podspec.json`) + - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCTRequired.podspec.json`) + - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCTTypeSafety.podspec.json`) - Reachability (= 3.2) - - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React.podspec.json`) - - React-bridging (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-bridging.podspec.json`) - - React-callinvoker (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-callinvoker.podspec.json`) - - React-Codegen (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-Codegen.podspec.json`) - - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-Core.podspec.json`) - - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-CoreModules.podspec.json`) - - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-cxxreact.podspec.json`) - - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-jsi.podspec.json`) - - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-jsiexecutor.podspec.json`) - - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-jsinspector.podspec.json`) - - React-logger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-logger.podspec.json`) - - react-native-blur (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-blur.podspec.json`) - - react-native-get-random-values (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-get-random-values.podspec.json`) - - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-safe-area.podspec.json`) - - react-native-safe-area-context (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-safe-area-context.podspec.json`) - - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-slider.podspec.json`) - - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-video.podspec.json`) - - react-native-webview (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-webview.podspec.json`) - - React-perflogger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-perflogger.podspec.json`) - - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTActionSheet.podspec.json`) - - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTAnimation.podspec.json`) - - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTBlob.podspec.json`) - - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTImage.podspec.json`) - - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTLinking.podspec.json`) - - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTNetwork.podspec.json`) - - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTSettings.podspec.json`) - - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTText.podspec.json`) - - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTVibration.podspec.json`) - - React-runtimeexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-runtimeexecutor.podspec.json`) - - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/ReactCommon.podspec.json`) - - RNCClipboard (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNCClipboard.podspec.json`) - - RNCMaskedView (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNCMaskedView.podspec.json`) - - RNFastImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNFastImage.podspec.json`) - - RNGestureHandler (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNGestureHandler.podspec.json`) - - RNReanimated (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNReanimated.podspec.json`) - - RNScreens (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNScreens.podspec.json`) - - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNSVG.podspec.json`) - - RNTAztecView (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, tag `v1.98.1`) + - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React.podspec.json`) + - React-bridging (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-bridging.podspec.json`) + - React-callinvoker (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-callinvoker.podspec.json`) + - React-Codegen (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-Codegen.podspec.json`) + - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-Core.podspec.json`) + - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-CoreModules.podspec.json`) + - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-cxxreact.podspec.json`) + - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsi.podspec.json`) + - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsiexecutor.podspec.json`) + - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsinspector.podspec.json`) + - React-logger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-logger.podspec.json`) + - react-native-blur (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-blur.podspec.json`) + - react-native-get-random-values (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-get-random-values.podspec.json`) + - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-safe-area.podspec.json`) + - react-native-safe-area-context (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-safe-area-context.podspec.json`) + - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-slider.podspec.json`) + - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-video.podspec.json`) + - react-native-webview (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-webview.podspec.json`) + - React-perflogger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-perflogger.podspec.json`) + - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTActionSheet.podspec.json`) + - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTAnimation.podspec.json`) + - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTBlob.podspec.json`) + - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTImage.podspec.json`) + - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTLinking.podspec.json`) + - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTNetwork.podspec.json`) + - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTSettings.podspec.json`) + - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTText.podspec.json`) + - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTVibration.podspec.json`) + - React-runtimeexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-runtimeexecutor.podspec.json`) + - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/ReactCommon.podspec.json`) + - RNCClipboard (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNCClipboard.podspec.json`) + - RNCMaskedView (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNCMaskedView.podspec.json`) + - RNFastImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNFastImage.podspec.json`) + - RNGestureHandler (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNGestureHandler.podspec.json`) + - RNReanimated (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNReanimated.podspec.json`) + - RNScreens (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNScreens.podspec.json`) + - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNSVG.podspec.json`) + - RNTAztecView (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, commit `6fe745be5342afe1c82f45e57d12a02c1b72e1de`) - Starscream (= 3.0.6) - SVProgressHUD (= 2.2.5) - SwiftLint (~> 0.50) @@ -616,7 +616,7 @@ DEPENDENCIES: - WordPressShared (~> 2.2-beta) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) - - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/Yoga.podspec.json`) + - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/Yoga.podspec.json`) - ZendeskSupportSDK (= 5.3.0) - ZIPFoundation (~> 0.9.8) @@ -677,121 +677,121 @@ SPEC REPOS: EXTERNAL SOURCES: boost: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/boost.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/boost.podspec.json BVLinearGradient: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/BVLinearGradient.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/BVLinearGradient.podspec.json FBLazyVector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/FBLazyVector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/FBLazyVector.podspec.json FBReactNativeSpec: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 glog: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/glog.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/glog.podspec.json Gutenberg: + :commit: 6fe745be5342afe1c82f45e57d12a02c1b72e1de :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true - :tag: v1.98.1 RCT-Folly: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RCT-Folly.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCT-Folly.podspec.json RCTRequired: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RCTRequired.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCTRequired.podspec.json RCTTypeSafety: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RCTTypeSafety.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCTTypeSafety.podspec.json React: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React.podspec.json React-bridging: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-bridging.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-bridging.podspec.json React-callinvoker: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-callinvoker.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-callinvoker.podspec.json React-Codegen: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-Codegen.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-Codegen.podspec.json React-Core: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-Core.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-Core.podspec.json React-CoreModules: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-CoreModules.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-CoreModules.podspec.json React-cxxreact: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-cxxreact.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-cxxreact.podspec.json React-jsi: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-jsi.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsi.podspec.json React-jsiexecutor: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-jsiexecutor.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsiexecutor.podspec.json React-jsinspector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-jsinspector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsinspector.podspec.json React-logger: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-logger.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-logger.podspec.json react-native-blur: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-blur.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-blur.podspec.json react-native-get-random-values: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-get-random-values.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-get-random-values.podspec.json react-native-safe-area: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-safe-area.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-safe-area.podspec.json react-native-safe-area-context: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-safe-area-context.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-safe-area-context.podspec.json react-native-slider: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-slider.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-slider.podspec.json react-native-video: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-video.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-video.podspec.json react-native-webview: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/react-native-webview.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-webview.podspec.json React-perflogger: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-perflogger.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-perflogger.podspec.json React-RCTActionSheet: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTActionSheet.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTActionSheet.podspec.json React-RCTAnimation: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTAnimation.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTAnimation.podspec.json React-RCTBlob: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTBlob.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTBlob.podspec.json React-RCTImage: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTImage.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTImage.podspec.json React-RCTLinking: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTLinking.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTLinking.podspec.json React-RCTNetwork: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTNetwork.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTNetwork.podspec.json React-RCTSettings: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTSettings.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTSettings.podspec.json React-RCTText: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTText.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTText.podspec.json React-RCTVibration: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-RCTVibration.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTVibration.podspec.json React-runtimeexecutor: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/React-runtimeexecutor.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-runtimeexecutor.podspec.json ReactCommon: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/ReactCommon.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/ReactCommon.podspec.json RNCClipboard: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNCClipboard.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNCClipboard.podspec.json RNCMaskedView: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNCMaskedView.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNCMaskedView.podspec.json RNFastImage: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNFastImage.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNFastImage.podspec.json RNGestureHandler: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNGestureHandler.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNGestureHandler.podspec.json RNReanimated: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNReanimated.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNReanimated.podspec.json RNScreens: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNScreens.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNScreens.podspec.json RNSVG: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/RNSVG.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNSVG.podspec.json RNTAztecView: + :commit: 6fe745be5342afe1c82f45e57d12a02c1b72e1de :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true - :tag: v1.98.1 Yoga: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.98.1/third-party-podspecs/Yoga.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/Yoga.podspec.json CHECKOUT OPTIONS: FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 Gutenberg: + :commit: 6fe745be5342afe1c82f45e57d12a02c1b72e1de :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true - :tag: v1.98.1 RNTAztecView: + :commit: 6fe745be5342afe1c82f45e57d12a02c1b72e1de :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true - :tag: v1.98.1 SPEC CHECKSUMS: Alamofire: 3ec537f71edc9804815215393ae2b1a8ea33a844 @@ -816,7 +816,7 @@ SPEC CHECKSUMS: Gridicons: 17d660b97ce4231d582101b02f8280628b141c9a GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd GTMSessionFetcher: 5595ec75acf5be50814f81e9189490412bad82ba - Gutenberg: fd4cb66c253b00ccae222d01ed3824521ed50b76 + Gutenberg: 0b6b57b7c48f79212d23c947fd6c9decb6c196c6 JTAppleCalendar: 932cadea40b1051beab10f67843451d48ba16c99 Kanvas: f932eaed3d3f47aae8aafb6c2d27c968bdd49030 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef @@ -867,7 +867,7 @@ SPEC CHECKSUMS: RNReanimated: b5730b32243a35f955202d807ecb43755133ac62 RNScreens: bd1f43d7dfcd435bc11d4ee5c60086717c45a113 RNSVG: 259ef12cbec2591a45fc7c5f09d7aa09e6692533 - RNTAztecView: 0cf287757e0879ea9e87e6628851c24f798ba809 + RNTAztecView: d96d1e9b317e7bfe153bcb9e82f9287862893579 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d Sentry: 927dfb29d18a14d924229a59cc2ad149f43349f2 From d8240953f5e3a8a84828c63e5fa04636b03fc1a8 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 6 Jul 2023 13:11:19 +0200 Subject: [PATCH 132/175] Update release notes --- RELEASE-NOTES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index e782766f07c4..1460ce633839 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,7 +5,8 @@ * [**] [internal] Make sure synced posts are saved before calling completion block. [#20960] * [**] [internal] Fix observing Quick Start notifications. [#20997] * [**] [internal] Fixed an issue that was causing a memory leak in the domain selection flow. [#20813] - +* [*] [Jetpack-only] Block editor: Rename "Reusable blocks" to "Synced patterns", aligning with the web editor. [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5885] +* [**] Block editor: Fix a crash related to Reanimated when closing the editor [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5938] 22.7 ----- From 8af5da755774890c4c538d50e14c8f4f0411ed8d Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 6 Jul 2023 14:12:15 +0200 Subject: [PATCH 133/175] Update Gutenberg Mobile ref --- Gutenberg/version.rb | 2 +- Podfile.lock | 196 +++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/Gutenberg/version.rb b/Gutenberg/version.rb index 4b3181cf5096..cfafa964c325 100644 --- a/Gutenberg/version.rb +++ b/Gutenberg/version.rb @@ -11,7 +11,7 @@ # # LOCAL_GUTENBERG=../my-gutenberg-fork bundle exec pod install GUTENBERG_CONFIG = { - commit: '6fe745be5342afe1c82f45e57d12a02c1b72e1de' + commit: 'f408074e0a5daefe32df47ad1e8664a622841735' # tag: 'v1.98.1' } diff --git a/Podfile.lock b/Podfile.lock index d3d02b4a9ec2..52953995c0fc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -545,18 +545,18 @@ DEPENDENCIES: - AppCenter (~> 4.1) - AppCenter/Distribute (~> 4.1) - Automattic-Tracks-iOS (~> 2.2) - - boost (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/boost.podspec.json`) - - BVLinearGradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/BVLinearGradient.podspec.json`) + - boost (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/boost.podspec.json`) + - BVLinearGradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/BVLinearGradient.podspec.json`) - CocoaLumberjack/Swift (~> 3.0) - CropViewController (= 2.5.3) - Down (~> 0.6.6) - - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/FBLazyVector.podspec.json`) - - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json`) + - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/FBLazyVector.podspec.json`) + - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json`) - FSInteractiveMap (from `https://github.com/wordpress-mobile/FSInteractiveMap.git`, tag `0.2.0`) - Gifu (= 3.2.0) - - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/glog.podspec.json`) + - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/glog.podspec.json`) - Gridicons (~> 1.1.0) - - Gutenberg (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, commit `6fe745be5342afe1c82f45e57d12a02c1b72e1de`) + - Gutenberg (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, commit `f408074e0a5daefe32df47ad1e8664a622841735`) - JTAppleCalendar (~> 8.0.2) - Kanvas (~> 1.4.4) - MediaEditor (>= 1.2.2, ~> 1.2) @@ -565,48 +565,48 @@ DEPENDENCIES: - "NSURL+IDN (~> 0.4)" - OCMock (~> 3.4.3) - OHHTTPStubs/Swift (~> 9.1.0) - - RCT-Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCT-Folly.podspec.json`) - - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCTRequired.podspec.json`) - - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCTTypeSafety.podspec.json`) + - RCT-Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCT-Folly.podspec.json`) + - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCTRequired.podspec.json`) + - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCTTypeSafety.podspec.json`) - Reachability (= 3.2) - - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React.podspec.json`) - - React-bridging (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-bridging.podspec.json`) - - React-callinvoker (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-callinvoker.podspec.json`) - - React-Codegen (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-Codegen.podspec.json`) - - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-Core.podspec.json`) - - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-CoreModules.podspec.json`) - - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-cxxreact.podspec.json`) - - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsi.podspec.json`) - - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsiexecutor.podspec.json`) - - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsinspector.podspec.json`) - - React-logger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-logger.podspec.json`) - - react-native-blur (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-blur.podspec.json`) - - react-native-get-random-values (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-get-random-values.podspec.json`) - - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-safe-area.podspec.json`) - - react-native-safe-area-context (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-safe-area-context.podspec.json`) - - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-slider.podspec.json`) - - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-video.podspec.json`) - - react-native-webview (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-webview.podspec.json`) - - React-perflogger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-perflogger.podspec.json`) - - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTActionSheet.podspec.json`) - - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTAnimation.podspec.json`) - - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTBlob.podspec.json`) - - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTImage.podspec.json`) - - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTLinking.podspec.json`) - - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTNetwork.podspec.json`) - - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTSettings.podspec.json`) - - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTText.podspec.json`) - - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTVibration.podspec.json`) - - React-runtimeexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-runtimeexecutor.podspec.json`) - - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/ReactCommon.podspec.json`) - - RNCClipboard (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNCClipboard.podspec.json`) - - RNCMaskedView (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNCMaskedView.podspec.json`) - - RNFastImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNFastImage.podspec.json`) - - RNGestureHandler (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNGestureHandler.podspec.json`) - - RNReanimated (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNReanimated.podspec.json`) - - RNScreens (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNScreens.podspec.json`) - - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNSVG.podspec.json`) - - RNTAztecView (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, commit `6fe745be5342afe1c82f45e57d12a02c1b72e1de`) + - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React.podspec.json`) + - React-bridging (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-bridging.podspec.json`) + - React-callinvoker (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-callinvoker.podspec.json`) + - React-Codegen (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-Codegen.podspec.json`) + - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-Core.podspec.json`) + - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-CoreModules.podspec.json`) + - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-cxxreact.podspec.json`) + - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsi.podspec.json`) + - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsiexecutor.podspec.json`) + - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsinspector.podspec.json`) + - React-logger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-logger.podspec.json`) + - react-native-blur (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-blur.podspec.json`) + - react-native-get-random-values (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-get-random-values.podspec.json`) + - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-safe-area.podspec.json`) + - react-native-safe-area-context (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-safe-area-context.podspec.json`) + - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-slider.podspec.json`) + - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-video.podspec.json`) + - react-native-webview (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-webview.podspec.json`) + - React-perflogger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-perflogger.podspec.json`) + - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTActionSheet.podspec.json`) + - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTAnimation.podspec.json`) + - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTBlob.podspec.json`) + - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTImage.podspec.json`) + - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTLinking.podspec.json`) + - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTNetwork.podspec.json`) + - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTSettings.podspec.json`) + - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTText.podspec.json`) + - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTVibration.podspec.json`) + - React-runtimeexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-runtimeexecutor.podspec.json`) + - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/ReactCommon.podspec.json`) + - RNCClipboard (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNCClipboard.podspec.json`) + - RNCMaskedView (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNCMaskedView.podspec.json`) + - RNFastImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNFastImage.podspec.json`) + - RNGestureHandler (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNGestureHandler.podspec.json`) + - RNReanimated (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNReanimated.podspec.json`) + - RNScreens (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNScreens.podspec.json`) + - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNSVG.podspec.json`) + - RNTAztecView (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, commit `f408074e0a5daefe32df47ad1e8664a622841735`) - Starscream (= 3.0.6) - SVProgressHUD (= 2.2.5) - SwiftLint (~> 0.50) @@ -616,7 +616,7 @@ DEPENDENCIES: - WordPressShared (~> 2.2-beta) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) - - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/Yoga.podspec.json`) + - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/Yoga.podspec.json`) - ZendeskSupportSDK (= 5.3.0) - ZIPFoundation (~> 0.9.8) @@ -677,119 +677,119 @@ SPEC REPOS: EXTERNAL SOURCES: boost: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/boost.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/boost.podspec.json BVLinearGradient: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/BVLinearGradient.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/BVLinearGradient.podspec.json FBLazyVector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/FBLazyVector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/FBLazyVector.podspec.json FBReactNativeSpec: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 glog: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/glog.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/glog.podspec.json Gutenberg: - :commit: 6fe745be5342afe1c82f45e57d12a02c1b72e1de + :commit: f408074e0a5daefe32df47ad1e8664a622841735 :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true RCT-Folly: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCT-Folly.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCT-Folly.podspec.json RCTRequired: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCTRequired.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCTRequired.podspec.json RCTTypeSafety: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RCTTypeSafety.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCTTypeSafety.podspec.json React: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React.podspec.json React-bridging: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-bridging.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-bridging.podspec.json React-callinvoker: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-callinvoker.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-callinvoker.podspec.json React-Codegen: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-Codegen.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-Codegen.podspec.json React-Core: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-Core.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-Core.podspec.json React-CoreModules: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-CoreModules.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-CoreModules.podspec.json React-cxxreact: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-cxxreact.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-cxxreact.podspec.json React-jsi: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsi.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsi.podspec.json React-jsiexecutor: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsiexecutor.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsiexecutor.podspec.json React-jsinspector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-jsinspector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsinspector.podspec.json React-logger: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-logger.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-logger.podspec.json react-native-blur: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-blur.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-blur.podspec.json react-native-get-random-values: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-get-random-values.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-get-random-values.podspec.json react-native-safe-area: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-safe-area.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-safe-area.podspec.json react-native-safe-area-context: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-safe-area-context.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-safe-area-context.podspec.json react-native-slider: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-slider.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-slider.podspec.json react-native-video: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-video.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-video.podspec.json react-native-webview: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/react-native-webview.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-webview.podspec.json React-perflogger: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-perflogger.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-perflogger.podspec.json React-RCTActionSheet: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTActionSheet.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTActionSheet.podspec.json React-RCTAnimation: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTAnimation.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTAnimation.podspec.json React-RCTBlob: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTBlob.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTBlob.podspec.json React-RCTImage: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTImage.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTImage.podspec.json React-RCTLinking: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTLinking.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTLinking.podspec.json React-RCTNetwork: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTNetwork.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTNetwork.podspec.json React-RCTSettings: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTSettings.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTSettings.podspec.json React-RCTText: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTText.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTText.podspec.json React-RCTVibration: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-RCTVibration.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTVibration.podspec.json React-runtimeexecutor: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/React-runtimeexecutor.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-runtimeexecutor.podspec.json ReactCommon: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/ReactCommon.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/ReactCommon.podspec.json RNCClipboard: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNCClipboard.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNCClipboard.podspec.json RNCMaskedView: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNCMaskedView.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNCMaskedView.podspec.json RNFastImage: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNFastImage.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNFastImage.podspec.json RNGestureHandler: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNGestureHandler.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNGestureHandler.podspec.json RNReanimated: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNReanimated.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNReanimated.podspec.json RNScreens: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNScreens.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNScreens.podspec.json RNSVG: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/RNSVG.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNSVG.podspec.json RNTAztecView: - :commit: 6fe745be5342afe1c82f45e57d12a02c1b72e1de + :commit: f408074e0a5daefe32df47ad1e8664a622841735 :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true Yoga: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/6fe745be5342afe1c82f45e57d12a02c1b72e1de/third-party-podspecs/Yoga.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/Yoga.podspec.json CHECKOUT OPTIONS: FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 Gutenberg: - :commit: 6fe745be5342afe1c82f45e57d12a02c1b72e1de + :commit: f408074e0a5daefe32df47ad1e8664a622841735 :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true RNTAztecView: - :commit: 6fe745be5342afe1c82f45e57d12a02c1b72e1de + :commit: f408074e0a5daefe32df47ad1e8664a622841735 :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true From 5bf6338c302df62cd03744a39a62b1a9c3f7be14 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 6 Jul 2023 09:10:13 -0400 Subject: [PATCH 134/175] Remove unused code --- .../Cards/Blaze/DashboardBlazeCampaignsCardView.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift index b9a235af3567..78f08de1a331 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Blaze/DashboardBlazeCampaignsCardView.swift @@ -78,8 +78,6 @@ final class DashboardBlazeCampaignsCardView: UIView { } campaignView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(campainViewTapped))) - campaignView.isAccessibilityElement = true - campaignView.accessibilityTraits = .allowsDirectInteraction } private func showCampaignList() { From 6db9dca1c4ae42cb4f3e6fdfdb88f1522582d2e8 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 6 Jul 2023 09:13:17 -0400 Subject: [PATCH 135/175] Update unit tests --- .../Dashboard/DashboardBlazeCardCellViewModelTest.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift index 2e99d427cd08..31e543cf21ed 100644 --- a/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift +++ b/WordPress/WordPressTest/Dashboard/DashboardBlazeCardCellViewModelTest.swift @@ -51,8 +51,8 @@ final class DashboardBlazeCardCellViewModelTest: CoreDataTestCase { switch sut.state { case .promo: XCTFail("The card should display the latest campaign") - case .campaign(let viewModel): - XCTAssertEqual(viewModel.title, "Test Post - don't approve") + case .campaign(let campaign): + XCTAssertEqual(campaign.name, "Test Post - don't approve") } } @@ -70,8 +70,8 @@ final class DashboardBlazeCardCellViewModelTest: CoreDataTestCase { switch sut.state { case .promo: XCTFail("The card should display the latest campaign") - case .campaign(let viewModel): - XCTAssertEqual(viewModel.title, "Test Post - don't approve") + case .campaign(let campaign): + XCTAssertEqual(campaign.name, "Test Post - don't approve") } } From 19e9e58e81ac4b8ecbfb79508343e8f8dfbde050 Mon Sep 17 00:00:00 2001 From: Povilas Staskus Date: Thu, 6 Jul 2023 16:57:13 +0300 Subject: [PATCH 136/175] Update Plans card presentation criteria ignoring mapped domains criteria - hasMappedDomains criteria also includes subdomais such as ".art.blog" for which we still want to show the Plans card - Show the card for those who have free dotcom plans --- .../FreeToPaidPlansDashboardCardHelper.swift | 10 +--------- ...reeToPaidPlansDashboardCardHelperTests.swift | 17 ++--------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Free to Paid Plans/FreeToPaidPlansDashboardCardHelper.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Free to Paid Plans/FreeToPaidPlansDashboardCardHelper.swift index 65193f6a22b8..6f5ffdd459f4 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Free to Paid Plans/FreeToPaidPlansDashboardCardHelper.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Free to Paid Plans/FreeToPaidPlansDashboardCardHelper.swift @@ -12,15 +12,7 @@ import Foundation return false } - /// If this propery is empty, it indicates that domain information is not yet loaded - let hasLoadedDomains = blog.domains?.isEmpty == false - let hasMappedDomain = blog.hasMappedDomain() - let hasFreePlan = !blog.hasPaidPlan - - return blog.supports(.domains) - && hasFreePlan - && hasLoadedDomains - && !hasMappedDomain + return blog.supports(.domains) && !blog.hasPaidPlan } static func hideCard(for blog: Blog?) { diff --git a/WordPress/WordPressTest/Dashboard/FreeToPaidPlansDashboardCardHelperTests.swift b/WordPress/WordPressTest/Dashboard/FreeToPaidPlansDashboardCardHelperTests.swift index db0ae114461f..82b6bdc80c5f 100644 --- a/WordPress/WordPressTest/Dashboard/FreeToPaidPlansDashboardCardHelperTests.swift +++ b/WordPress/WordPressTest/Dashboard/FreeToPaidPlansDashboardCardHelperTests.swift @@ -28,7 +28,7 @@ final class FreeToPaidPlansDashboardCardHelperTests: CoreDataTestCase { XCTAssertFalse(result, "Card should not show for blogs without a free plan") } - func testShouldNotShowCardWithMappedDomain() { + func testShouldShowCardWithMappedDomain() { let blog = BlogBuilder(mainContext) .with(supportsDomains: true) .with(domainCount: 1, of: .wpCom) @@ -39,20 +39,7 @@ final class FreeToPaidPlansDashboardCardHelperTests: CoreDataTestCase { let result = FreeToPaidPlansDashboardCardHelper.shouldShowCard(for: blog, isJetpack: true, featureFlagEnabled: true) - XCTAssertFalse(result, "Card should not show for blogs with a mapped domain") - } - - func testShouldNotShowCardWhenDomainInformationIsNotLoaded() { - let blog = BlogBuilder(mainContext) - .with(supportsDomains: true) - .with(domainCount: 0, of: .wpCom) - .with(hasMappedDomain: false) - .with(hasPaidPlan: false) - .build() - - let result = FreeToPaidPlansDashboardCardHelper.shouldShowCard(for: blog, isJetpack: true, featureFlagEnabled: true) - - XCTAssertFalse(result, "Card should not show until domain information is loaded") + XCTAssertTrue(result, "Card should still be shown for blogs with a mapped domain and with a free plan") } func testShouldNotShowCardFeatureFlagDisabled() { From 4f45194f39367f9ffb1cfdc87120486e1367aea8 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 6 Jul 2023 16:03:58 +0200 Subject: [PATCH 137/175] Update release notes Adding `internal` tag to Reanimated crash entry as it's not guaranteed that would fix it. --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 1460ce633839..f4db083c0afa 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -6,7 +6,7 @@ * [**] [internal] Fix observing Quick Start notifications. [#20997] * [**] [internal] Fixed an issue that was causing a memory leak in the domain selection flow. [#20813] * [*] [Jetpack-only] Block editor: Rename "Reusable blocks" to "Synced patterns", aligning with the web editor. [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5885] -* [**] Block editor: Fix a crash related to Reanimated when closing the editor [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5938] +* [**] [internal] Block editor: Fix a crash related to Reanimated when closing the editor [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5938] 22.7 ----- From 53a7d53aa648c041c3377d16349cfbcd9ab8c577 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 6 Jul 2023 16:04:22 +0200 Subject: [PATCH 138/175] Update Gutenberg Mobile ref with tag --- Gutenberg/version.rb | 4 +- Podfile.lock | 196 +++++++++++++++++++++---------------------- 2 files changed, 100 insertions(+), 100 deletions(-) diff --git a/Gutenberg/version.rb b/Gutenberg/version.rb index cfafa964c325..567633e7b64b 100644 --- a/Gutenberg/version.rb +++ b/Gutenberg/version.rb @@ -11,8 +11,8 @@ # # LOCAL_GUTENBERG=../my-gutenberg-fork bundle exec pod install GUTENBERG_CONFIG = { - commit: 'f408074e0a5daefe32df47ad1e8664a622841735' - # tag: 'v1.98.1' + # commit: '' + tag: 'v1.99.0' } GITHUB_ORG = 'wordpress-mobile' diff --git a/Podfile.lock b/Podfile.lock index 52953995c0fc..13c21db26f3d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -545,18 +545,18 @@ DEPENDENCIES: - AppCenter (~> 4.1) - AppCenter/Distribute (~> 4.1) - Automattic-Tracks-iOS (~> 2.2) - - boost (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/boost.podspec.json`) - - BVLinearGradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/BVLinearGradient.podspec.json`) + - boost (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/boost.podspec.json`) + - BVLinearGradient (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/BVLinearGradient.podspec.json`) - CocoaLumberjack/Swift (~> 3.0) - CropViewController (= 2.5.3) - Down (~> 0.6.6) - - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/FBLazyVector.podspec.json`) - - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json`) + - FBLazyVector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/FBLazyVector.podspec.json`) + - FBReactNativeSpec (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json`) - FSInteractiveMap (from `https://github.com/wordpress-mobile/FSInteractiveMap.git`, tag `0.2.0`) - Gifu (= 3.2.0) - - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/glog.podspec.json`) + - glog (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/glog.podspec.json`) - Gridicons (~> 1.1.0) - - Gutenberg (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, commit `f408074e0a5daefe32df47ad1e8664a622841735`) + - Gutenberg (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, tag `v1.99.0`) - JTAppleCalendar (~> 8.0.2) - Kanvas (~> 1.4.4) - MediaEditor (>= 1.2.2, ~> 1.2) @@ -565,48 +565,48 @@ DEPENDENCIES: - "NSURL+IDN (~> 0.4)" - OCMock (~> 3.4.3) - OHHTTPStubs/Swift (~> 9.1.0) - - RCT-Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCT-Folly.podspec.json`) - - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCTRequired.podspec.json`) - - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCTTypeSafety.podspec.json`) + - RCT-Folly (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RCT-Folly.podspec.json`) + - RCTRequired (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RCTRequired.podspec.json`) + - RCTTypeSafety (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RCTTypeSafety.podspec.json`) - Reachability (= 3.2) - - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React.podspec.json`) - - React-bridging (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-bridging.podspec.json`) - - React-callinvoker (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-callinvoker.podspec.json`) - - React-Codegen (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-Codegen.podspec.json`) - - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-Core.podspec.json`) - - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-CoreModules.podspec.json`) - - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-cxxreact.podspec.json`) - - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsi.podspec.json`) - - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsiexecutor.podspec.json`) - - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsinspector.podspec.json`) - - React-logger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-logger.podspec.json`) - - react-native-blur (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-blur.podspec.json`) - - react-native-get-random-values (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-get-random-values.podspec.json`) - - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-safe-area.podspec.json`) - - react-native-safe-area-context (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-safe-area-context.podspec.json`) - - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-slider.podspec.json`) - - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-video.podspec.json`) - - react-native-webview (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-webview.podspec.json`) - - React-perflogger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-perflogger.podspec.json`) - - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTActionSheet.podspec.json`) - - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTAnimation.podspec.json`) - - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTBlob.podspec.json`) - - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTImage.podspec.json`) - - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTLinking.podspec.json`) - - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTNetwork.podspec.json`) - - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTSettings.podspec.json`) - - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTText.podspec.json`) - - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTVibration.podspec.json`) - - React-runtimeexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-runtimeexecutor.podspec.json`) - - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/ReactCommon.podspec.json`) - - RNCClipboard (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNCClipboard.podspec.json`) - - RNCMaskedView (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNCMaskedView.podspec.json`) - - RNFastImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNFastImage.podspec.json`) - - RNGestureHandler (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNGestureHandler.podspec.json`) - - RNReanimated (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNReanimated.podspec.json`) - - RNScreens (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNScreens.podspec.json`) - - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNSVG.podspec.json`) - - RNTAztecView (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, commit `f408074e0a5daefe32df47ad1e8664a622841735`) + - React (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React.podspec.json`) + - React-bridging (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-bridging.podspec.json`) + - React-callinvoker (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-callinvoker.podspec.json`) + - React-Codegen (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-Codegen.podspec.json`) + - React-Core (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-Core.podspec.json`) + - React-CoreModules (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-CoreModules.podspec.json`) + - React-cxxreact (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-cxxreact.podspec.json`) + - React-jsi (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-jsi.podspec.json`) + - React-jsiexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-jsiexecutor.podspec.json`) + - React-jsinspector (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-jsinspector.podspec.json`) + - React-logger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-logger.podspec.json`) + - react-native-blur (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-blur.podspec.json`) + - react-native-get-random-values (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-get-random-values.podspec.json`) + - react-native-safe-area (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-safe-area.podspec.json`) + - react-native-safe-area-context (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-safe-area-context.podspec.json`) + - react-native-slider (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-slider.podspec.json`) + - react-native-video (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-video.podspec.json`) + - react-native-webview (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-webview.podspec.json`) + - React-perflogger (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-perflogger.podspec.json`) + - React-RCTActionSheet (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTActionSheet.podspec.json`) + - React-RCTAnimation (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTAnimation.podspec.json`) + - React-RCTBlob (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTBlob.podspec.json`) + - React-RCTImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTImage.podspec.json`) + - React-RCTLinking (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTLinking.podspec.json`) + - React-RCTNetwork (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTNetwork.podspec.json`) + - React-RCTSettings (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTSettings.podspec.json`) + - React-RCTText (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTText.podspec.json`) + - React-RCTVibration (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTVibration.podspec.json`) + - React-runtimeexecutor (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-runtimeexecutor.podspec.json`) + - ReactCommon (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/ReactCommon.podspec.json`) + - RNCClipboard (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNCClipboard.podspec.json`) + - RNCMaskedView (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNCMaskedView.podspec.json`) + - RNFastImage (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNFastImage.podspec.json`) + - RNGestureHandler (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNGestureHandler.podspec.json`) + - RNReanimated (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNReanimated.podspec.json`) + - RNScreens (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNScreens.podspec.json`) + - RNSVG (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNSVG.podspec.json`) + - RNTAztecView (from `https://github.com/wordpress-mobile/gutenberg-mobile.git`, tag `v1.99.0`) - Starscream (= 3.0.6) - SVProgressHUD (= 2.2.5) - SwiftLint (~> 0.50) @@ -616,7 +616,7 @@ DEPENDENCIES: - WordPressShared (~> 2.2-beta) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) - - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/Yoga.podspec.json`) + - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/Yoga.podspec.json`) - ZendeskSupportSDK (= 5.3.0) - ZIPFoundation (~> 0.9.8) @@ -677,121 +677,121 @@ SPEC REPOS: EXTERNAL SOURCES: boost: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/boost.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/boost.podspec.json BVLinearGradient: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/BVLinearGradient.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/BVLinearGradient.podspec.json FBLazyVector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/FBLazyVector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/FBLazyVector.podspec.json FBReactNativeSpec: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/FBReactNativeSpec/FBReactNativeSpec.podspec.json FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 glog: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/glog.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/glog.podspec.json Gutenberg: - :commit: f408074e0a5daefe32df47ad1e8664a622841735 :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true + :tag: v1.99.0 RCT-Folly: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCT-Folly.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RCT-Folly.podspec.json RCTRequired: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCTRequired.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RCTRequired.podspec.json RCTTypeSafety: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RCTTypeSafety.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RCTTypeSafety.podspec.json React: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React.podspec.json React-bridging: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-bridging.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-bridging.podspec.json React-callinvoker: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-callinvoker.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-callinvoker.podspec.json React-Codegen: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-Codegen.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-Codegen.podspec.json React-Core: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-Core.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-Core.podspec.json React-CoreModules: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-CoreModules.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-CoreModules.podspec.json React-cxxreact: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-cxxreact.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-cxxreact.podspec.json React-jsi: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsi.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-jsi.podspec.json React-jsiexecutor: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsiexecutor.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-jsiexecutor.podspec.json React-jsinspector: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-jsinspector.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-jsinspector.podspec.json React-logger: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-logger.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-logger.podspec.json react-native-blur: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-blur.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-blur.podspec.json react-native-get-random-values: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-get-random-values.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-get-random-values.podspec.json react-native-safe-area: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-safe-area.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-safe-area.podspec.json react-native-safe-area-context: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-safe-area-context.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-safe-area-context.podspec.json react-native-slider: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-slider.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-slider.podspec.json react-native-video: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-video.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-video.podspec.json react-native-webview: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/react-native-webview.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/react-native-webview.podspec.json React-perflogger: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-perflogger.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-perflogger.podspec.json React-RCTActionSheet: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTActionSheet.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTActionSheet.podspec.json React-RCTAnimation: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTAnimation.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTAnimation.podspec.json React-RCTBlob: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTBlob.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTBlob.podspec.json React-RCTImage: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTImage.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTImage.podspec.json React-RCTLinking: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTLinking.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTLinking.podspec.json React-RCTNetwork: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTNetwork.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTNetwork.podspec.json React-RCTSettings: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTSettings.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTSettings.podspec.json React-RCTText: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTText.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTText.podspec.json React-RCTVibration: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-RCTVibration.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-RCTVibration.podspec.json React-runtimeexecutor: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/React-runtimeexecutor.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/React-runtimeexecutor.podspec.json ReactCommon: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/ReactCommon.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/ReactCommon.podspec.json RNCClipboard: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNCClipboard.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNCClipboard.podspec.json RNCMaskedView: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNCMaskedView.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNCMaskedView.podspec.json RNFastImage: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNFastImage.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNFastImage.podspec.json RNGestureHandler: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNGestureHandler.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNGestureHandler.podspec.json RNReanimated: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNReanimated.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNReanimated.podspec.json RNScreens: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNScreens.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNScreens.podspec.json RNSVG: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/RNSVG.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/RNSVG.podspec.json RNTAztecView: - :commit: f408074e0a5daefe32df47ad1e8664a622841735 :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true + :tag: v1.99.0 Yoga: - :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/f408074e0a5daefe32df47ad1e8664a622841735/third-party-podspecs/Yoga.podspec.json + :podspec: https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/Yoga.podspec.json CHECKOUT OPTIONS: FSInteractiveMap: :git: https://github.com/wordpress-mobile/FSInteractiveMap.git :tag: 0.2.0 Gutenberg: - :commit: f408074e0a5daefe32df47ad1e8664a622841735 :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true + :tag: v1.99.0 RNTAztecView: - :commit: f408074e0a5daefe32df47ad1e8664a622841735 :git: https://github.com/wordpress-mobile/gutenberg-mobile.git :submodules: true + :tag: v1.99.0 SPEC CHECKSUMS: Alamofire: 3ec537f71edc9804815215393ae2b1a8ea33a844 From dd4573cf7ec29527489ef1eaef4bf535dc9ff048 Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:24:00 -0400 Subject: [PATCH 139/175] Update string for Instagram to match API response --- .../Jetpack/Social/JetpackSocialNoConnectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift index 2c2f6fa6303a..ed954d7c49d0 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift @@ -84,7 +84,7 @@ class JetpackSocialNoConnectionViewModel: ObservableObject { case twitter case tumblr case linkedin - case instagram + case instagram = "instagram-business" case mastodon case unknown From 03c166c688c0fb634f7e0b1fc620611b55ac69fa Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:28:22 -0400 Subject: [PATCH 140/175] Split `configureShareCellForIndexPath` into multiple functions --- .../Post/PostSettingsViewController.m | 79 +++++++++++-------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m index 2455b7f05a4d..4660be87b398 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m @@ -875,6 +875,49 @@ - (nullable NSURL *)urlForFeaturedImage { return featuredURL; } +- (UITableViewCell *)configureSocialCellForIndexPath:(NSIndexPath *)indexPath + connection:(PublicizeConnection *)connection + canEditSharing:(BOOL)canEditSharing + section:(NSInteger)section +{ + UITableViewCell *cell = [self getWPTableViewImageAndAccessoryCell]; + UIImage *image = [WPStyleGuide iconForService: connection.service]; + [cell.imageView setImage:image]; + if (canEditSharing) { + cell.imageView.tintColor = [WPStyleGuide tintColorForConnectedService: connection.service]; + } + cell.textLabel.text = connection.externalDisplay; + cell.textLabel.enabled = canEditSharing; + if (connection.isBroken) { + cell.accessoryView = section == PostSettingsSectionShare ? + [WPStyleGuide sharingCellWarningAccessoryImageView] : + [WPStyleGuide sharingCellErrorAccessoryImageView]; + } else { + UISwitch *switchAccessory = [[UISwitch alloc] initWithFrame:CGRectZero]; + // This interaction is handled at a cell level + switchAccessory.userInteractionEnabled = NO; + switchAccessory.on = ![self.post publicizeConnectionDisabledForKeyringID:connection.keyringConnectionID]; + switchAccessory.enabled = canEditSharing; + cell.accessoryView = switchAccessory; + } + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.tag = PostSettingsRowShareConnection; + cell.accessibilityIdentifier = [NSString stringWithFormat:@"%@ %@", connection.service, connection.externalDisplay]; + return cell; +} + +- (UITableViewCell *)configureDisclosureCellWithSharing:(BOOL)canEditSharing +{ + UITableViewCell *cell = [self getWPTableViewDisclosureCell]; + cell.textLabel.text = NSLocalizedString(@"Message", @"Label for the share message field on the post settings."); + cell.textLabel.enabled = canEditSharing; + cell.detailTextLabel.text = self.post.publicizeMessage ? self.post.publicizeMessage : self.post.titleForDisplay; + cell.detailTextLabel.enabled = canEditSharing; + cell.tag = PostSettingsRowShareMessage; + cell.accessibilityIdentifier = @"Customize the message"; + return cell; +} + - (UITableViewCell *)configureShareCellForIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell; @@ -883,38 +926,12 @@ - (UITableViewCell *)configureShareCellForIndexPath:(NSIndexPath *)indexPath NSArray *connections = sec == PostSettingsSectionShare ? self.publicizeConnections : self.unsupportedConnections; if (indexPath.row < connections.count) { - cell = [self getWPTableViewImageAndAccessoryCell]; - PublicizeConnection *connection = connections[indexPath.row]; - UIImage *image = [WPStyleGuide iconForService: connection.service]; - [cell.imageView setImage:image]; - if (canEditSharing) { - cell.imageView.tintColor = [WPStyleGuide tintColorForConnectedService: connection.service]; - } - cell.textLabel.text = connection.externalDisplay; - cell.textLabel.enabled = canEditSharing; - if (connection.isBroken) { - cell.accessoryView = sec == PostSettingsSectionShare ? - [WPStyleGuide sharingCellWarningAccessoryImageView] : - [WPStyleGuide sharingCellErrorAccessoryImageView]; - } else { - UISwitch *switchAccessory = [[UISwitch alloc] initWithFrame:CGRectZero]; - // This interaction is handled at a cell level - switchAccessory.userInteractionEnabled = NO; - switchAccessory.on = ![self.post publicizeConnectionDisabledForKeyringID:connection.keyringConnectionID]; - switchAccessory.enabled = canEditSharing; - cell.accessoryView = switchAccessory; - } - cell.selectionStyle = UITableViewCellSelectionStyleNone; - cell.tag = PostSettingsRowShareConnection; - cell.accessibilityIdentifier = [NSString stringWithFormat:@"%@ %@", connection.service, connection.externalDisplay]; + cell = [self configureSocialCellForIndexPath:indexPath + connection:connections[indexPath.row] + canEditSharing:canEditSharing + section:sec]; } else { - cell = [self getWPTableViewDisclosureCell]; - cell.textLabel.text = NSLocalizedString(@"Message", @"Label for the share message field on the post settings."); - cell.textLabel.enabled = canEditSharing; - cell.detailTextLabel.text = self.post.publicizeMessage ? self.post.publicizeMessage : self.post.titleForDisplay; - cell.detailTextLabel.enabled = canEditSharing; - cell.tag = PostSettingsRowShareMessage; - cell.accessibilityIdentifier = @"Customize the message"; + cell = [self configureDisclosureCellWithSharing:canEditSharing]; } cell.userInteractionEnabled = canEditSharing; return cell; From 14da3efe961bc5f1b1ed51d9db56c260841a13c9 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 6 Jul 2023 15:47:44 -0400 Subject: [PATCH 141/175] Fix deprecation warnings --- .../ActionSheetViewController.swift | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/ActionSheetViewController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/ActionSheetViewController.swift index 246ce62f37ab..15562d1b6fab 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/ActionSheetViewController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/ActionSheetViewController.swift @@ -33,8 +33,8 @@ class ActionSheetViewController: UIViewController { enum Button { static let height: CGFloat = 54 - static let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 35) - static let titleInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) + static let contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 35) + static let imagePadding: CGFloat = 16 static let imageTintColor: UIColor = .neutral(.shade30) static let font: UIFont = .preferredFont(forTextStyle: .callout) static let textColor: UIColor = .text @@ -147,27 +147,32 @@ class ActionSheetViewController: UIViewController { updateScrollViewHeight() } - private func createButton(_ handler: @escaping () -> Void) -> UIButton { - let button = UIButton(type: .custom, primaryAction: UIAction(handler: { _ in handler() })) - button.titleLabel?.font = Constants.Button.font - button.setTitleColor(Constants.Button.textColor, for: .normal) - button.imageView?.tintColor = Constants.Button.imageTintColor - button.setBackgroundImage(UIImage(color: .divider), for: .highlighted) - button.titleEdgeInsets = Constants.Button.titleInsets - button.naturalContentHorizontalAlignment = .leading - button.contentEdgeInsets = Constants.Button.contentInsets - button.translatesAutoresizingMaskIntoConstraints = false - button.flipInsetsForRightToLeftLayoutDirection() - button.titleLabel?.adjustsFontForContentSizeCategory = true - return button - } - private func button(_ info: ActionSheetButton) -> UIButton { - let button = createButton(info.action) - - button.setTitle(info.title, for: .normal) - button.setImage(info.image, for: .normal) + let button = UIButton(type: .system, primaryAction: UIAction(handler: { _ in info.action() })) + + button.configuration = { + var configuration = UIButton.Configuration.plain() + configuration.attributedTitle = { + var string = AttributedString(info.title) + string.font = Constants.Button.font + string.foregroundColor = Constants.Button.textColor + return string + }() + configuration.image = info.image + configuration.imageColorTransformer = UIConfigurationColorTransformer { _ in + Constants.Button.imageTintColor + } + configuration.imagePadding = Constants.Button.imagePadding + configuration.contentInsets = Constants.Button.contentInsets + configuration.background.cornerRadius = 0 + return configuration + }() + button.configurationUpdateHandler = { button in + button.configuration?.background.backgroundColor = button.isHighlighted ? .divider : .clear + } + button.contentHorizontalAlignment = .leading button.accessibilityIdentifier = info.identifier + button.translatesAutoresizingMaskIntoConstraints = false if let badge = info.badge { button.addSubview(badge) From 38517cf8723239a1d9a91f594e940ec6933dd2af Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Thu, 6 Jul 2023 16:17:45 -0400 Subject: [PATCH 142/175] Update social icons on the post settings screen --- .../Blog/WPStyleGuide+Sharing.swift | 8 +++++++ .../JetpackSocialNoConnectionView.swift | 21 +------------------ .../Post/PostSettingsViewController.m | 10 +++++++-- .../Contents.json | 0 .../icon-instagram.svg | 0 5 files changed, 17 insertions(+), 22 deletions(-) rename WordPress/Jetpack/AppImages.xcassets/Social/{icon-instagram.imageset => icon-instagram-business.imageset}/Contents.json (100%) rename WordPress/Jetpack/AppImages.xcassets/Social/{icon-instagram.imageset => icon-instagram-business.imageset}/icon-instagram.svg (100%) diff --git a/WordPress/Classes/ViewRelated/Blog/WPStyleGuide+Sharing.swift b/WordPress/Classes/ViewRelated/Blog/WPStyleGuide+Sharing.swift index 86d845df4d09..b5a780414504 100644 --- a/WordPress/Classes/ViewRelated/Blog/WPStyleGuide+Sharing.swift +++ b/WordPress/Classes/ViewRelated/Blog/WPStyleGuide+Sharing.swift @@ -69,6 +69,14 @@ extension WPStyleGuide { return image!.withRenderingMode(.alwaysTemplate) } + @objc public class func socialIcon(for service: NSString) -> UIImage { + guard FeatureFlag.jetpackSocial.enabled else { + return iconForService(service) + } + + return UIImage(named: "icon-\(service)") ?? iconForService(service) + } + /// Get's the tint color to use for the specified service when it is connected. /// diff --git a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift index ed954d7c49d0..b604d0804cf0 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Social/JetpackSocialNoConnectionView.swift @@ -87,25 +87,6 @@ class JetpackSocialNoConnectionViewModel: ObservableObject { case instagram = "instagram-business" case mastodon case unknown - - var image: UIImage? { - switch self { - case .facebook: - return UIImage(named: "icon-facebook") - case .twitter: - return UIImage(named: "icon-twitter") - case .tumblr: - return UIImage(named: "icon-tumblr") - case .linkedin: - return UIImage(named: "icon-linkedin") - case .instagram: - return UIImage(named: "icon-instagram") - case .mastodon: - return UIImage(named: "icon-mastodon") - case .unknown: - return UIImage(named: "social-default")?.withRenderingMode(.alwaysTemplate) - } - } } private func updateIcons(_ services: [PublicizeService]) { @@ -113,7 +94,7 @@ class JetpackSocialNoConnectionViewModel: ObservableObject { var downloadTasks: [(url: URL, index: Int)] = [] for (index, service) in services.enumerated() { let serviceType = JetpackSocialService(rawValue: service.serviceID) ?? .unknown - let icon = serviceType.image ?? UIImage() + let icon = WPStyleGuide.socialIcon(for: service.serviceID as NSString) icons.append(icon) if serviceType == .unknown { diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m index 4660be87b398..f2527725dc2f 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m @@ -880,10 +880,16 @@ - (UITableViewCell *)configureSocialCellForIndexPath:(NSIndexPath *)indexPath canEditSharing:(BOOL)canEditSharing section:(NSInteger)section { + BOOL isJetpackSocialEnabled = [Feature enabled:FeatureFlagJetpackSocial]; UITableViewCell *cell = [self getWPTableViewImageAndAccessoryCell]; - UIImage *image = [WPStyleGuide iconForService: connection.service]; + UIImage *image = [WPStyleGuide socialIconFor:connection.service]; + if (isJetpackSocialEnabled) { + image = [image resizedImageWithContentMode:UIViewContentModeScaleAspectFill + bounds:CGSizeMake(28.0, 28.0) + interpolationQuality:kCGInterpolationDefault]; + } [cell.imageView setImage:image]; - if (canEditSharing) { + if (canEditSharing && !isJetpackSocialEnabled) { cell.imageView.tintColor = [WPStyleGuide tintColorForConnectedService: connection.service]; } cell.textLabel.text = connection.externalDisplay; diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/Contents.json b/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram-business.imageset/Contents.json similarity index 100% rename from WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/Contents.json rename to WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram-business.imageset/Contents.json diff --git a/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/icon-instagram.svg b/WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram-business.imageset/icon-instagram.svg similarity index 100% rename from WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram.imageset/icon-instagram.svg rename to WordPress/Jetpack/AppImages.xcassets/Social/icon-instagram-business.imageset/icon-instagram.svg From d31f77c85f613234a78bba924b9d3a321ea56735 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 6 Jul 2023 16:28:17 -0400 Subject: [PATCH 143/175] Fix WPTableViewHandler warnings --- .../Controllers/NotificationsViewController.swift | 6 ++++-- .../ViewRelated/Post/AbstractPostListViewController.swift | 2 +- .../Post/Revisions/RevisionsTableViewController.swift | 2 +- .../Reader/Manage/ReaderTagsTableViewModel.swift | 2 +- .../Reader/ReaderCardsStreamViewController.swift | 2 +- .../Reader/ReaderFollowedSitesViewController.swift | 2 +- .../Reader/ReaderSearchSuggestionsViewController.swift | 2 +- .../ViewRelated/Reader/ReaderStreamViewController.swift | 2 +- WordPress/WordPressTest/PostListTableViewHandlerTests.swift | 2 +- 9 files changed, 12 insertions(+), 10 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController.swift b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController.swift index 5b769ed57fc7..ac451be152ef 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController.swift @@ -1373,7 +1373,7 @@ extension NotificationsViewController: WPTableViewHandlerDelegate { return ContextManager.sharedInstance().mainContext } - func fetchRequest() -> NSFetchRequest { + func fetchRequest() -> NSFetchRequest? { let request = NSFetchRequest(entityName: entityName()) request.sortDescriptors = [NSSortDescriptor(key: Filter.sortKey, ascending: false)] request.predicate = predicateForFetchRequest() @@ -1817,7 +1817,9 @@ extension NotificationsViewController: WPSplitViewControllerDetailProvider { private func fetchFirstNotification() -> Notification? { let context = managedObjectContext() - let fetchRequest = self.fetchRequest() + guard let fetchRequest = self.fetchRequest() else { + return nil + } fetchRequest.fetchLimit = 1 if let results = try? context.fetch(fetchRequest) as? [Notification] { diff --git a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift index 5a8c7d549669..d1eb6df8f35d 100644 --- a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift @@ -434,7 +434,7 @@ class AbstractPostListViewController: UIViewController, return ContextManager.sharedInstance().mainContext } - func fetchRequest() -> NSFetchRequest { + func fetchRequest() -> NSFetchRequest? { let fetchRequest = NSFetchRequest(entityName: entityName()) fetchRequest.predicate = predicateForFetchRequest() fetchRequest.sortDescriptors = sortDescriptorsForFetchRequest() diff --git a/WordPress/Classes/ViewRelated/Post/Revisions/RevisionsTableViewController.swift b/WordPress/Classes/ViewRelated/Post/Revisions/RevisionsTableViewController.swift index 83f96b3ee6bc..4a87ad95ad75 100644 --- a/WordPress/Classes/ViewRelated/Post/Revisions/RevisionsTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Revisions/RevisionsTableViewController.swift @@ -173,7 +173,7 @@ extension RevisionsTableViewController: WPTableViewHandlerDelegate { return ContextManager.sharedInstance().mainContext } - func fetchRequest() -> NSFetchRequest { + func fetchRequest() -> NSFetchRequest? { guard let postId = post?.postID, let siteId = post?.blog.dotComID else { preconditionFailure("Expected a postId or a siteId") } diff --git a/WordPress/Classes/ViewRelated/Reader/Manage/ReaderTagsTableViewModel.swift b/WordPress/Classes/ViewRelated/Reader/Manage/ReaderTagsTableViewModel.swift index 811f6f7b958b..b47e448c9ae6 100644 --- a/WordPress/Classes/ViewRelated/Reader/Manage/ReaderTagsTableViewModel.swift +++ b/WordPress/Classes/ViewRelated/Reader/Manage/ReaderTagsTableViewModel.swift @@ -27,7 +27,7 @@ extension ReaderTagsTableViewModel: WPTableViewHandlerDelegate { return context } - func fetchRequest() -> NSFetchRequest { + func fetchRequest() -> NSFetchRequest? { return ReaderTagTopic.tagsFetchRequest } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderCardsStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderCardsStreamViewController.swift index 151348ea572a..d408e6f715e6 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderCardsStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderCardsStreamViewController.swift @@ -175,7 +175,7 @@ class ReaderCardsStreamViewController: ReaderStreamViewController { // MARK: - TableViewHandler - override func fetchRequest() -> NSFetchRequest { + override func fetchRequest() -> NSFetchRequest? { let fetchRequest = NSFetchRequest(entityName: ReaderCard.classNameWithoutNamespaces()) fetchRequest.sortDescriptors = sortDescriptorsForFetchRequest(ascending: true) return fetchRequest diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesViewController.swift index c97a5963bdc6..7bacde97f10e 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesViewController.swift @@ -393,7 +393,7 @@ extension ReaderFollowedSitesViewController: WPTableViewHandlerDelegate { } - func fetchRequest() -> NSFetchRequest { + func fetchRequest() -> NSFetchRequest? { let fetchRequest = NSFetchRequest(entityName: "ReaderSiteTopic") fetchRequest.predicate = NSPredicate(format: "following = YES") diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderSearchSuggestionsViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderSearchSuggestionsViewController.swift index c8483a61d375..f4dec4489ba2 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderSearchSuggestionsViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderSearchSuggestionsViewController.swift @@ -176,7 +176,7 @@ extension ReaderSearchSuggestionsViewController: WPTableViewHandlerDelegate { } - func fetchRequest() -> NSFetchRequest { + func fetchRequest() -> NSFetchRequest? { let request = NSFetchRequest(entityName: "ReaderSearchSuggestion") request.predicate = predicateForFetchRequest() request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)] diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift index 94b5ecbaa75a..fa55dfab257f 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift @@ -1494,7 +1494,7 @@ extension ReaderStreamViewController: WPTableViewHandlerDelegate { } - func fetchRequest() -> NSFetchRequest { + func fetchRequest() -> NSFetchRequest? { let fetchRequest = NSFetchRequest(entityName: ReaderPost.classNameWithoutNamespaces()) fetchRequest.predicate = predicateForFetchRequest() fetchRequest.sortDescriptors = sortDescriptorsForFetchRequest() diff --git a/WordPress/WordPressTest/PostListTableViewHandlerTests.swift b/WordPress/WordPressTest/PostListTableViewHandlerTests.swift index b5a08b61a5cd..90d812ebd94f 100644 --- a/WordPress/WordPressTest/PostListTableViewHandlerTests.swift +++ b/WordPress/WordPressTest/PostListTableViewHandlerTests.swift @@ -22,7 +22,7 @@ class PostListHandlerMock: NSObject, WPTableViewHandlerDelegate { return setUpInMemoryManagedObjectContext() } - func fetchRequest() -> NSFetchRequest { + func fetchRequest() -> NSFetchRequest? { let a = NSFetchRequest(entityName: String(describing: Post.self)) a.sortDescriptors = [NSSortDescriptor(key: BasePost.statusKeyPath, ascending: true)] return a From 0e902de3655ec71e85070868ceb4c72767f1e3b4 Mon Sep 17 00:00:00 2001 From: Chris McGraw <2454408+wargcm@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:01:15 -0400 Subject: [PATCH 144/175] Open social purchase link on subscribe now tap --- .../PostSettingsViewController+JetpackSocial.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift index f72fc1e1f7a1..9d360d61ec96 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+JetpackSocial.swift @@ -44,9 +44,17 @@ extension PostSettingsViewController { } @objc func createRemainingSharesView() -> UIView { - let viewModel = JetpackSocialRemainingSharesViewModel { - // TODO - print("Subscribe tap") + let viewModel = JetpackSocialRemainingSharesViewModel { [weak self] in + guard let blog = self?.apost.blog, + let hostname = blog.hostname, + let url = URL(string: "https://wordpress.com/checkout/\(hostname)/jetpack_social_basic_yearly") else { + return + } + let webViewController = WebViewControllerFactory.controller(url: url, + blog: blog, + source: "post_settings_remaining_shares_subscribe_now") + let navigationController = UINavigationController(rootViewController: webViewController) + self?.present(navigationController, animated: true) } let hostController = UIHostingController(rootView: JetpackSocialSettingsRemainingSharesView(viewModel: viewModel)) hostController.view.translatesAutoresizingMaskIntoConstraints = false From 13e4fb7ea319ad75899f7394d52bd7e7f956dd76 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 7 Jul 2023 11:54:18 +1200 Subject: [PATCH 145/175] Move liked users query to a static function --- .../Services/CommentService+Likes.swift | 37 +++---------------- .../Classes/Services/LikeUserHelpers.swift | 36 ++++++++++++++---- .../Classes/Services/PostService+Likes.swift | 3 +- .../Likes/LikesListController.swift | 4 +- 4 files changed, 38 insertions(+), 42 deletions(-) diff --git a/WordPress/Classes/Services/CommentService+Likes.swift b/WordPress/Classes/Services/CommentService+Likes.swift index fa2d761440c2..188a623805ea 100644 --- a/WordPress/Classes/Services/CommentService+Likes.swift +++ b/WordPress/Classes/Services/CommentService+Likes.swift @@ -40,9 +40,10 @@ extension CommentService { commentID: commentID, siteID: siteID, purgeExisting: purgeExisting) { - let users = self.likeUsersFor(commentID: commentID, siteID: siteID) + assert(Thread.isMainThread) + + let users = LikeUserHelper.likeUsersFor(commentID: commentID, siteID: siteID, in: self.coreDataStack.mainContext) success(users, totalLikes.intValue, count) - LikeUserHelper.purgeStaleLikes() } }, failure: { error in DDLogError(String(describing: error)) @@ -50,36 +51,6 @@ extension CommentService { }) } - /** - Fetches a list of users from Core Data that liked the comment with the given IDs. - - @param commentID The ID of the comment to fetch likes for. - @param siteID The ID of the site that contains the post. - @param after Filter results to likes after this Date. Optional. - */ - func likeUsersFor(commentID: NSNumber, siteID: NSNumber, after: Date? = nil) -> [LikeUser] { - self.coreDataStack.performQuery { context in - let request = LikeUser.fetchRequest() as NSFetchRequest - - request.predicate = { - if let after = after { - // The date comparison is 'less than' because Likes are in descending order. - return NSPredicate(format: "likedSiteID = %@ AND likedCommentID = %@ AND dateLiked < %@", siteID, commentID, after as CVarArg) - } - - return NSPredicate(format: "likedSiteID = %@ AND likedCommentID = %@", siteID, commentID) - }() - - request.sortDescriptors = [NSSortDescriptor(key: "dateLiked", ascending: false)] - - if let users = try? context.fetch(request) { - return users - } - - return [LikeUser]() - } - } - } private extension CommentService { @@ -106,6 +77,8 @@ private extension CommentService { if purgeExisting { self.deleteExistingUsersFor(commentID: commentID, siteID: siteID, from: derivedContext, likesToKeep: likers) } + + LikeUserHelper.purgeStaleLikes(fromContext: derivedContext) }, completion: onComplete, on: .main) } diff --git a/WordPress/Classes/Services/LikeUserHelpers.swift b/WordPress/Classes/Services/LikeUserHelpers.swift index b96f0df1c2fc..8c3f0b21ba92 100644 --- a/WordPress/Classes/Services/LikeUserHelpers.swift +++ b/WordPress/Classes/Services/LikeUserHelpers.swift @@ -39,6 +39,34 @@ import CoreData return try? context.fetch(request).first } + /** + Fetches a list of users from Core Data that liked the comment with the given IDs. + + @param commentID The ID of the comment to fetch likes for. + @param siteID The ID of the site that contains the post. + @param after Filter results to likes after this Date. Optional. + */ + class func likeUsersFor(commentID: NSNumber, siteID: NSNumber, after: Date? = nil, in context: NSManagedObjectContext) -> [LikeUser] { + let request = LikeUser.fetchRequest() as NSFetchRequest + + request.predicate = { + if let after = after { + // The date comparison is 'less than' because Likes are in descending order. + return NSPredicate(format: "likedSiteID = %@ AND likedCommentID = %@ AND dateLiked < %@", siteID, commentID, after as CVarArg) + } + + return NSPredicate(format: "likedSiteID = %@ AND likedCommentID = %@", siteID, commentID) + }() + + request.sortDescriptors = [NSSortDescriptor(key: "dateLiked", ascending: false)] + + if let users = try? context.fetch(request) { + return users + } + + return [LikeUser]() + } + private class func updatePreferredBlog(for user: LikeUser, with remoteUser: RemoteLikeUser, context: NSManagedObjectContext) { guard let remotePreferredBlog = remoteUser.preferredBlog else { if let existingPreferredBlog = user.preferredBlog { @@ -58,14 +86,8 @@ import CoreData preferredBlog.user = user } - class func purgeStaleLikes() { - ContextManager.shared.performAndSave { - purgeStaleLikes(fromContext: $0) - } - } - // Delete all LikeUsers that were last fetched at least 7 days ago. - private class func purgeStaleLikes(fromContext context: NSManagedObjectContext) { + class func purgeStaleLikes(fromContext context: NSManagedObjectContext) { guard let staleDate = Calendar.current.date(byAdding: .day, value: -7, to: Date()) else { DDLogError("Error creating date to purge stale Likes.") return diff --git a/WordPress/Classes/Services/PostService+Likes.swift b/WordPress/Classes/Services/PostService+Likes.swift index fccfda6d5cbd..97cb37375d40 100644 --- a/WordPress/Classes/Services/PostService+Likes.swift +++ b/WordPress/Classes/Services/PostService+Likes.swift @@ -42,7 +42,6 @@ extension PostService { purgeExisting: purgeExisting) { let users = self.likeUsersFor(postID: postID, siteID: siteID) success(users, totalLikes.intValue, count) - LikeUserHelper.purgeStaleLikes() } }, failure: { error in DDLogError(String(describing: error)) @@ -104,6 +103,8 @@ private extension PostService { if purgeExisting { self.deleteExistingUsersFor(postID: postID, siteID: siteID, from: derivedContext, likesToKeep: likers) } + + LikeUserHelper.purgeStaleLikes(fromContext: derivedContext) }, completion: onComplete, on: .main) } diff --git a/WordPress/Classes/ViewRelated/Likes/LikesListController.swift b/WordPress/Classes/ViewRelated/Likes/LikesListController.swift index 5f0b99ea7b09..b4a8bcfb400b 100644 --- a/WordPress/Classes/ViewRelated/Likes/LikesListController.swift +++ b/WordPress/Classes/ViewRelated/Likes/LikesListController.swift @@ -231,7 +231,7 @@ class LikesListController: NSObject { case .post(let postID): likingUsers = postService.likeUsersFor(postID: postID, siteID: siteID) case .comment(let commentID): - likingUsers = commentService.likeUsersFor(commentID: commentID, siteID: siteID) + likingUsers = LikeUserHelper.likeUsersFor(commentID: commentID, siteID: siteID, in: ContextManager.shared.mainContext) } } @@ -283,7 +283,7 @@ class LikesListController: NSObject { case .post(let postID): fetchedUsers = postService.likeUsersFor(postID: postID, siteID: siteID, after: modifiedDate) case .comment(let commentID): - fetchedUsers = commentService.likeUsersFor(commentID: commentID, siteID: siteID, after: modifiedDate) + fetchedUsers = LikeUserHelper.likeUsersFor(commentID: commentID, siteID: siteID, after: modifiedDate, in: ContextManager.shared.mainContext) } excludeUserIDs = fetchedUsers.map { NSNumber(value: $0.userID) } From dfc2007f199e01b3c2beb3f4fb7216f5ce2ac58f Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 7 Jul 2023 14:02:39 +1200 Subject: [PATCH 146/175] Replace Blog argument with URL --- .../Utility/Editor/GutenbergSettings.swift | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/WordPress/Classes/Utility/Editor/GutenbergSettings.swift b/WordPress/Classes/Utility/Editor/GutenbergSettings.swift index c15048e33866..bfac423a6b1e 100644 --- a/WordPress/Classes/Utility/Editor/GutenbergSettings.swift +++ b/WordPress/Classes/Utility/Editor/GutenbergSettings.swift @@ -4,19 +4,19 @@ class GutenbergSettings { // MARK: - Enabled Editors Keys enum Key { static let appWideEnabled = "kUserDefaultsGutenbergEditorEnabled" - static func enabledOnce(for blog: Blog) -> String { - let url = urlStringFrom(blog) + static func enabledOnce(forBlogURL url: String?) -> String { + let url = urlString(fromBlogURL: url) return "com.wordpress.gutenberg-autoenabled-" + url } - static func showPhase2Dialog(for blog: Blog) -> String { - let url = urlStringFrom(blog) + static func showPhase2Dialog(forBlogURL url: String?) -> String { + let url = urlString(fromBlogURL: url) return "kShowGutenbergPhase2Dialog-" + url } static let focalPointPickerTooltipShown = "kGutenbergFocalPointPickerTooltipShown" static let blockTypeImpressions = "kBlockTypeImpressions" - private static func urlStringFrom(_ blog: Blog) -> String { - return (blog.url ?? "") + private static func urlString(fromBlogURL url: String?) -> String { + return (url ?? "") // New sites will add a slash at the end of URL. // This is removed when the URL is refreshed from remote. // Removing trailing '/' in case there is one for consistency. @@ -61,7 +61,7 @@ class GutenbergSettings { softSetGutenbergEnabled(isEnabled, for: blog, source: source) if isEnabled { - database.set(true, forKey: Key.enabledOnce(for: blog)) + database.set(true, forKey: Key.enabledOnce(forBlogURL: blog.url)) } } @@ -86,7 +86,7 @@ class GutenbergSettings { allBlogs.forEach { blog in if blog.editor == .aztec { setShowPhase2Dialog(true, for: blog) - database.set(true, forKey: Key.enabledOnce(for: blog)) + database.set(true, forKey: Key.enabledOnce(forBlogURL: blog.url)) } } let editorSettingsService = EditorSettingsService(coreDataStack: coreDataStack) @@ -96,11 +96,11 @@ class GutenbergSettings { } func shouldPresentInformativeDialog(for blog: Blog) -> Bool { - return database.bool(forKey: Key.showPhase2Dialog(for: blog)) + return database.bool(forKey: Key.showPhase2Dialog(forBlogURL: blog.url)) } func setShowPhase2Dialog(_ showDialog: Bool, for blog: Blog) { - database.set(showDialog, forKey: Key.showPhase2Dialog(for: blog)) + database.set(showDialog, forKey: Key.showPhase2Dialog(forBlogURL: blog.url)) } /// Sets gutenberg enabled without registering the enabled action ("enabledOnce") @@ -153,7 +153,7 @@ class GutenbergSettings { /// True if gutenberg editor has been enabled at least once on the given blog func wasGutenbergEnabledOnce(for blog: Blog) -> Bool { - return database.object(forKey: Key.enabledOnce(for: blog)) != nil + return database.object(forKey: Key.enabledOnce(forBlogURL: blog.url)) != nil } /// True if gutenberg should be autoenabled for the blog hosting the given post. @@ -162,7 +162,7 @@ class GutenbergSettings { } func willShowDialog(for blog: Blog) { - database.set(true, forKey: Key.enabledOnce(for: blog)) + database.set(true, forKey: Key.enabledOnce(forBlogURL: blog.url)) } /// True if it should show the tooltip for the focal point picker @@ -208,7 +208,7 @@ class GutenbergSettings { } func getDefaultEditor(for blog: Blog) -> MobileEditor { - database.set(true, forKey: Key.enabledOnce(for: blog)) + database.set(true, forKey: Key.enabledOnce(forBlogURL: blog.url)) return .gutenberg } } From 288c26e736187f87e958e3738cb9655075b5c798 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 7 Jul 2023 14:50:09 +1200 Subject: [PATCH 147/175] Return blog urls from performQuery call --- .../Utility/Editor/GutenbergSettings.swift | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/Utility/Editor/GutenbergSettings.swift b/WordPress/Classes/Utility/Editor/GutenbergSettings.swift index bfac423a6b1e..228083e8eae9 100644 --- a/WordPress/Classes/Utility/Editor/GutenbergSettings.swift +++ b/WordPress/Classes/Utility/Editor/GutenbergSettings.swift @@ -82,12 +82,16 @@ class GutenbergSettings { } private func setGutenbergEnabledForAllSites() { - let allBlogs = coreDataStack.performQuery({ (try? BlogQuery().blogs(in: $0)) ?? [] }) - allBlogs.forEach { blog in - if blog.editor == .aztec { - setShowPhase2Dialog(true, for: blog) - database.set(true, forKey: Key.enabledOnce(forBlogURL: blog.url)) - } + let blogURLs: [String?] = coreDataStack.performQuery { context in + guard let blogs = try? BlogQuery().blogs(in: context) else { return [] } + + return blogs + .filter { $0.editor == .aztec } + .map { $0.url } + } + blogURLs.forEach { blogURL in + setShowPhase2Dialog(true, forBlogURL: blogURL) + database.set(true, forKey: Key.enabledOnce(forBlogURL: blogURL)) } let editorSettingsService = EditorSettingsService(coreDataStack: coreDataStack) editorSettingsService.migrateGlobalSettingToRemote(isGutenbergEnabled: true, overrideRemote: true, onSuccess: { @@ -103,6 +107,10 @@ class GutenbergSettings { database.set(showDialog, forKey: Key.showPhase2Dialog(forBlogURL: blog.url)) } + func setShowPhase2Dialog(_ showDialog: Bool, forBlogURL url: String?) { + database.set(showDialog, forKey: Key.showPhase2Dialog(forBlogURL: url)) + } + /// Sets gutenberg enabled without registering the enabled action ("enabledOnce") /// Use this to set gutenberg and still show the auto-enabled dialog. /// From ba40bd21d0d85557ad8e95ca60a45d763ce58385 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 7 Jul 2023 15:37:19 +1200 Subject: [PATCH 148/175] Add tests for fetching liked user --- .../WordPressTest/LikeUserHelperTests.swift | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/WordPress/WordPressTest/LikeUserHelperTests.swift b/WordPress/WordPressTest/LikeUserHelperTests.swift index f5a789a5cf47..cf23bc79c4e6 100644 --- a/WordPress/WordPressTest/LikeUserHelperTests.swift +++ b/WordPress/WordPressTest/LikeUserHelperTests.swift @@ -3,15 +3,21 @@ import XCTest class LikeUserHelperTests: CoreDataTestCase { - func createTestRemoteUserDictionary(withPreferredBlog hasPreferredBlog: Bool) -> [String: Any] { + var siteID: NSNumber = 20 + + func createTestRemoteUserDictionary( + withPreferredBlog hasPreferredBlog: Bool, + siteID: Int? = nil, + year: Int = 2021 + ) -> [String: Any] { var remoteUserDictionary: [String: Any] = [ - "ID": 15, + "ID": Int.random(in: 0...Int.max), "login": "testlogin", "name": "testname", - "site_ID": 20, + "site_ID": siteID ?? self.siteID.intValue, "avatar_URL": "wordpress.org/test2", "bio": "testbio", - "date_liked": "2021-11-24T04:02:42+0000", + "date_liked": "\(year)-11-24T04:02:42+0000", ] if hasPreferredBlog { @@ -77,4 +83,38 @@ class LikeUserHelperTests: CoreDataTestCase { waitForExpectations(timeout: 5) } + + func testFetchingLikedUser() { + XCTAssertEqual(mainContext.countObjects(ofType: LikeUser.self), 0) + + let commentID: NSNumber = 1 + let otherCommentID: NSNumber = 2 + // Insert likes with a recent date + for _ in 1...10 { + let dict = createTestRemoteUserDictionary(withPreferredBlog: false, year: 2010) + let user = RemoteLikeUser(dictionary: dict, commentID: commentID, siteID: siteID) + _ = LikeUserHelper.createOrUpdateFrom(remoteUser: user, context: mainContext) + } + // Insert likes with an older data + for _ in 1...5 { + let dict = createTestRemoteUserDictionary(withPreferredBlog: false, year: 1990) + let user = RemoteLikeUser(dictionary: dict, commentID: commentID, siteID: siteID) + _ = LikeUserHelper.createOrUpdateFrom(remoteUser: user, context: mainContext) + } + // Insert likes on another comment + for _ in 1...3 { + let dict = createTestRemoteUserDictionary(withPreferredBlog: false) + let user = RemoteLikeUser(dictionary: dict, commentID: otherCommentID, siteID: siteID) + _ = LikeUserHelper.createOrUpdateFrom(remoteUser: user, context: mainContext) + } + + // There are 18 like saved in the database in total + XCTAssertEqual(mainContext.countObjects(ofType: LikeUser.self), 18) + + // There are 15 likes on the comment with `commentID` + XCTAssertEqual(LikeUserHelper.likeUsersFor(commentID: commentID, siteID: siteID, in: mainContext).count, 15) + + // There are 10 likes since 2001 + XCTAssertEqual(LikeUserHelper.likeUsersFor(commentID: commentID, siteID: siteID, after: Date(timeIntervalSinceReferenceDate: 0), in: mainContext).count, 10) + } } From d7dd9582fd4c8a8976eab5129bf0cc65925f2b20 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 6 Jul 2023 23:51:13 -0400 Subject: [PATCH 149/175] Update ActionSheetViewController.swift --- .../System/Action Sheet/ActionSheetViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/System/Action Sheet/ActionSheetViewController.swift b/WordPress/Classes/ViewRelated/System/Action Sheet/ActionSheetViewController.swift index 15562d1b6fab..0ec101e40121 100644 --- a/WordPress/Classes/ViewRelated/System/Action Sheet/ActionSheetViewController.swift +++ b/WordPress/Classes/ViewRelated/System/Action Sheet/ActionSheetViewController.swift @@ -33,7 +33,7 @@ class ActionSheetViewController: UIViewController { enum Button { static let height: CGFloat = 54 - static let contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 35) + static let contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 35) static let imagePadding: CGFloat = 16 static let imageTintColor: UIColor = .neutral(.shade30) static let font: UIFont = .preferredFont(forTextStyle: .callout) From d8c080a047eb6b428ad1feaedc85563364efc49b Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 7 Jul 2023 16:09:10 +1200 Subject: [PATCH 150/175] Update a test assertion --- WordPress/WordPressTest/LikeUserHelperTests.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WordPress/WordPressTest/LikeUserHelperTests.swift b/WordPress/WordPressTest/LikeUserHelperTests.swift index cf23bc79c4e6..322484593c63 100644 --- a/WordPress/WordPressTest/LikeUserHelperTests.swift +++ b/WordPress/WordPressTest/LikeUserHelperTests.swift @@ -114,7 +114,8 @@ class LikeUserHelperTests: CoreDataTestCase { // There are 15 likes on the comment with `commentID` XCTAssertEqual(LikeUserHelper.likeUsersFor(commentID: commentID, siteID: siteID, in: mainContext).count, 15) - // There are 10 likes since 2001 - XCTAssertEqual(LikeUserHelper.likeUsersFor(commentID: commentID, siteID: siteID, after: Date(timeIntervalSinceReferenceDate: 0), in: mainContext).count, 10) + // There are 10 likes since 2001 and 5 likes before. + // How the `after` argument should behave might be confusing. See https://github.com/wordpress-mobile/WordPress-iOS/pull/21028#issuecomment-1624661943 + XCTAssertEqual(LikeUserHelper.likeUsersFor(commentID: commentID, siteID: siteID, after: Date(timeIntervalSinceReferenceDate: 0), in: mainContext).count, 5) } } From f307f480659b33af961b54704945373e3f807f56 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 5 Jun 2023 15:29:44 +1000 Subject: [PATCH 151/175] Add `WPAccount` fixture utility This is part of an effort to make `AccountSettingsServiceTests.testUpdateFailure()` less flaky in CI. --- WordPress/WordPress.xcodeproj/project.pbxproj | 24 +++++----- .../WordPressTest/AccountServiceTests.swift | 33 +++----------- .../AccountSettingsServiceTests.swift | 6 +-- WordPress/WordPressTest/BlogTests.swift | 4 +- .../WordPressTest/ContextManagerTests.swift | 44 +++++-------------- .../WordPressTest/PeopleServiceTests.swift | 4 +- .../WordPressTest/WPAccount+Fixture.swift | 20 +++++++++ 7 files changed, 54 insertions(+), 81 deletions(-) create mode 100644 WordPress/WordPressTest/WPAccount+Fixture.swift diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 6cfebe3c1810..abf3845cd057 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -862,6 +862,7 @@ 3F73BE5F24EB3B4400BE99FF /* WhatIsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F73BE5E24EB3B4400BE99FF /* WhatIsNewView.swift */; }; 3F751D462491A93D0008A2B1 /* ZendeskUtilsTests+Plans.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F751D452491A93D0008A2B1 /* ZendeskUtilsTests+Plans.swift */; }; 3F758FD524F6FB4900BBA2FC /* AnnouncementsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F758FD424F6FB4900BBA2FC /* AnnouncementsStore.swift */; }; + 3F759FBA2A2DA93B0039A845 /* WPAccount+Fixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F759FB92A2DA93B0039A845 /* WPAccount+Fixture.swift */; }; 3F762E9326784A950088CD45 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9226784A950088CD45 /* Logger.swift */; }; 3F762E9526784B540088CD45 /* WireMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9426784B540088CD45 /* WireMock.swift */; }; 3F762E9726784BED0088CD45 /* FancyAlertComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9626784BED0088CD45 /* FancyAlertComponent.swift */; }; @@ -6560,6 +6561,7 @@ 3F73BE5E24EB3B4400BE99FF /* WhatIsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatIsNewView.swift; sourceTree = ""; }; 3F751D452491A93D0008A2B1 /* ZendeskUtilsTests+Plans.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZendeskUtilsTests+Plans.swift"; sourceTree = ""; }; 3F758FD424F6FB4900BBA2FC /* AnnouncementsStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnouncementsStore.swift; sourceTree = ""; }; + 3F759FB92A2DA93B0039A845 /* WPAccount+Fixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WPAccount+Fixture.swift"; sourceTree = ""; }; 3F762E9226784A950088CD45 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 3F762E9426784B540088CD45 /* WireMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireMock.swift; sourceTree = ""; }; 3F762E9626784BED0088CD45 /* FancyAlertComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyAlertComponent.swift; sourceTree = ""; }; @@ -12374,26 +12376,27 @@ 5D7A577D1AFBFD7C0097C028 /* Models */ = { isa = PBXGroup; children = ( - F1B1E7A224098FA100549E2A /* BlogTests.swift */, + E6A2158F1D1065F200DE5270 /* AbstractPostTest.swift */, + 8B8C814C2318073300A0E620 /* BasePostTests.swift */, 246D0A0225E97D5D0028B83F /* Blog+ObjcTests.m */, + FEE48EFE2A4C9855008A48E0 /* Blog+PublicizeTests.swift */, 4AD5657128E543A30054C676 /* BlogQueryTests.swift */, + B55F1AA11C107CE200FD04D4 /* BlogSettingsDiscussionTests.swift */, + F1B1E7A224098FA100549E2A /* BlogTests.swift */, + 24A2948225D602710000A51E /* BlogTimeZoneTests.m */, D848CC1620FF38EA00A9038F /* FormattableCommentRangeTests.swift */, + 0879FC151E9301DD00E1EFC8 /* MediaTests.swift */, + C38C5D8027F61D2C002F517E /* MenuItemTests.swift */, D848CC1420FF33FC00A9038F /* NotificationContentRangeTests.swift */, + D826D67E211D21C700A5D8FE /* NullMockUserDefaults.swift */, + 5960967E1CF7959300848496 /* PostTests.swift */, 8BBBEBB124B8F8C0005E358E /* ReaderCardTests.swift */, E6B9B8A91B94E1FE0001B92F /* ReaderPostTest.m */, - B55F1AA11C107CE200FD04D4 /* BlogSettingsDiscussionTests.swift */, - E6A2158F1D1065F200DE5270 /* AbstractPostTest.swift */, - 5960967E1CF7959300848496 /* PostTests.swift */, - 0879FC151E9301DD00E1EFC8 /* MediaTests.swift */, - D826D67E211D21C700A5D8FE /* NullMockUserDefaults.swift */, - 8B8C814C2318073300A0E620 /* BasePostTests.swift */, - 24A2948225D602710000A51E /* BlogTimeZoneTests.m */, 24C69A8A2612421900312D9A /* UserSettingsTests.swift */, 24C69AC12612467C00312D9A /* UserSettingsTestsObjc.m */, + 3F759FB92A2DA93B0039A845 /* WPAccount+Fixture.swift */, 2481B1E7260D4EAC00AE59DB /* WPAccount+LookupTests.swift */, 2481B20B260D8FED00AE59DB /* WPAccount+ObjCLookupTests.m */, - C38C5D8027F61D2C002F517E /* MenuItemTests.swift */, - FEE48EFE2A4C9855008A48E0 /* Blog+PublicizeTests.swift */, ); name = Models; sourceTree = ""; @@ -23630,6 +23633,7 @@ F17A2A2023BFBD84001E96AC /* UIView+ExistingConstraints.swift in Sources */, 9A9D34FD23607CCC00BC95A3 /* AsyncOperationTests.swift in Sources */, B5552D821CD1061F00B26DF6 /* StringExtensionsTests.swift in Sources */, + 3F759FBA2A2DA93B0039A845 /* WPAccount+Fixture.swift in Sources */, 8B821F3C240020E2006B697E /* PostServiceUploadingListTests.swift in Sources */, 73178C3521BEE9AC00E37C9A /* TitleSubtitleHeaderTests.swift in Sources */, 08AAD6A11CBEA610002B2418 /* MenusServiceTests.m in Sources */, diff --git a/WordPress/WordPressTest/AccountServiceTests.swift b/WordPress/WordPressTest/AccountServiceTests.swift index d41409d8f705..ba0abe17782c 100644 --- a/WordPress/WordPressTest/AccountServiceTests.swift +++ b/WordPress/WordPressTest/AccountServiceTests.swift @@ -147,24 +147,10 @@ class AccountServiceTests: CoreDataTestCase { func testMergeMultipleDuplicateAccounts() throws { let context = contextManager.mainContext - let account1 = WPAccount(context: context) - account1.userID = 1 - account1.username = "username" - account1.authToken = "authToken" - account1.uuid = UUID().uuidString - - let account2 = WPAccount(context: context) - account2.userID = 1 - account2.username = "username" - account2.authToken = "authToken" - account2.uuid = UUID().uuidString - - let account3 = WPAccount(context: context) - account3.userID = 1 - account3.username = "username" - account3.authToken = "authToken" - account3.uuid = UUID().uuidString + let account1 = WPAccount.fixture(context: context, userID: 1) + let account2 = WPAccount.fixture(context: context, userID: 1) + let account3 = WPAccount.fixture(context: context, userID: 1) account1.addBlogs(createMockBlogs(withIDs: [1, 2, 3, 4, 5, 6], in: context)) account2.addBlogs(createMockBlogs(withIDs: [1, 2, 3], in: context)) @@ -240,17 +226,8 @@ class AccountServiceTests: CoreDataTestCase { } func testPurgeAccount() throws { - let account1 = WPAccount(context: mainContext) - account1.userID = 1 - account1.username = "username" - account1.authToken = "authToken" - account1.uuid = UUID().uuidString - - let account2 = WPAccount(context: mainContext) - account2.userID = 1 - account2.username = "username" - account2.authToken = "authToken" - account2.uuid = UUID().uuidString + let account1 = WPAccount.fixture(context: mainContext, userID: 1) + let account2 = WPAccount.fixture(context: mainContext, userID: 2) contextManager.saveContextAndWait(mainContext) try XCTAssertEqual(mainContext.count(for: WPAccount.fetchRequest()), 2) diff --git a/WordPress/WordPressTest/AccountSettingsServiceTests.swift b/WordPress/WordPressTest/AccountSettingsServiceTests.swift index b365eb39fa6d..6f4dbccdcae5 100644 --- a/WordPress/WordPressTest/AccountSettingsServiceTests.swift +++ b/WordPress/WordPressTest/AccountSettingsServiceTests.swift @@ -8,11 +8,7 @@ class AccountSettingsServiceTests: CoreDataTestCase { private var service: AccountSettingsService! override func setUp() { - let account = WPAccount(context: mainContext) - account.username = "test" - account.authToken = "token" - account.userID = 1 - account.uuid = UUID().uuidString + let account = WPAccount.fixture(context: mainContext) let settings = ManagedAccountSettings(context: mainContext) settings.account = account diff --git a/WordPress/WordPressTest/BlogTests.swift b/WordPress/WordPressTest/BlogTests.swift index c5048fdc2f80..b50b72a9f566 100644 --- a/WordPress/WordPressTest/BlogTests.swift +++ b/WordPress/WordPressTest/BlogTests.swift @@ -199,9 +199,7 @@ final class BlogTests: CoreDataTestCase { // Create an account with duplicated blogs let xmlrpc = "https://xmlrpc.test.wordpress.com" let account = try await contextManager.performAndSave { context in - let account = WPAccount(context: context) - account.username = "username" - account.authToken = "authToken" + let account = WPAccount.fixture(context: context) account.blogs = Set( (1...10).map { _ in let blog = BlogBuilder(context).build() diff --git a/WordPress/WordPressTest/ContextManagerTests.swift b/WordPress/WordPressTest/ContextManagerTests.swift index 5a9f0f16a3f6..c9268ab92fcb 100644 --- a/WordPress/WordPressTest/ContextManagerTests.swift +++ b/WordPress/WordPressTest/ContextManagerTests.swift @@ -146,9 +146,7 @@ class ContextManagerTests: XCTestCase { let derivedContext = contextManager.newDerivedContext() derivedContext.perform { - let account = WPAccount(context: derivedContext) - account.userID = 1 - account.username = "First User" + _ = WPAccount.fixture(context: derivedContext, userID: 1, username: "First User") contextManager.saveContextAndWait(derivedContext) } @@ -165,9 +163,7 @@ class ContextManagerTests: XCTestCase { // Save another user waitUntil { done in derivedContext.perform { - let account = WPAccount(context: derivedContext) - account.userID = 2 - account.username = "Second account" + _ = WPAccount.fixture(context: derivedContext, userID: 2) contextManager.saveContextAndWait(derivedContext) done() } @@ -193,17 +189,13 @@ class ContextManagerTests: XCTestCase { XCTAssertEqual(numberOfAccounts(), 0) try await contextManager.performAndSave { context in - let account = WPAccount(context: context) - account.userID = 1 - account.username = "First User" + _ = WPAccount.fixture(context: context, userID: 1) } XCTAssertEqual(numberOfAccounts(), 1) do { try await contextManager.performAndSave { context in - let account = WPAccount(context: context) - account.userID = 100 - account.username = "Unknown User" + _ = WPAccount.fixture(context: context, userID: 100) throw NSError(domain: "save", code: 1) } XCTFail("The above call should throw") @@ -221,9 +213,7 @@ class ContextManagerTests: XCTestCase { // > "In non-async functions, and closures without any await expression, the compiler selects the non-async overload" let sync: () -> Void = { contextManager.performAndSave { context in - let account = WPAccount(context: context) - account.userID = 2 - account.username = "Second User" + _ = WPAccount.fixture(context: context, userID: 2) } } sync() @@ -245,14 +235,10 @@ class ContextManagerTests: XCTestCase { ] contextManager.performAndSave({ - let account = WPAccount(context: $0) - account.userID = 1 - account.username = "First User" + _ = WPAccount.fixture(context: $0, userID: 1, username: "First User") contextManager.performAndSave { - let account = WPAccount(context: $0) - account.userID = 2 - account.username = "Second User" + _ = WPAccount.fixture(context: $0, userID: 2, username: "Second User") } saveOperations[1].fulfill() @@ -279,14 +265,10 @@ class ContextManagerTests: XCTestCase { ] contextManager.performAndSave({ - let account = WPAccount(context: $0) - account.userID = 1 - account.username = "First User" + _ = WPAccount.fixture(context: $0, userID: 1, username: "First User") contextManager.performAndSave({ - let account = WPAccount(context: $0) - account.userID = 2 - account.username = "Second User" + _ = WPAccount.fixture(context: $0, userID: 2, username: "Second User") }, completion: { saveOperations[1].fulfill() }, on: .main) @@ -380,9 +362,7 @@ class ContextManagerTests: XCTestCase { // First, insert an account into the database. let contextManager = ContextManager.forTesting() contextManager.performAndSave { context in - let account = WPAccount(context: context) - account.userID = 1 - account.username = "First User" + _ = WPAccount.fixture(context: context, userID: 1, username: "First User") } // Fetch the account in the main context @@ -441,8 +421,8 @@ class ContextManagerTests: XCTestCase { private func createOrUpdateAccount(username: String, newToken: String, in context: NSManagedObjectContext) throws { var account = try WPAccount.lookup(withUsername: username, in: context) if account == nil { - account = WPAccount(context: context) - account?.username = username + // Will this make tests fail because of the default userID in the fixture? + account = WPAccount.fixture(context: context, username: username) } account?.authToken = newToken } diff --git a/WordPress/WordPressTest/PeopleServiceTests.swift b/WordPress/WordPressTest/PeopleServiceTests.swift index 128a27306586..48d46b3bee5c 100644 --- a/WordPress/WordPressTest/PeopleServiceTests.swift +++ b/WordPress/WordPressTest/PeopleServiceTests.swift @@ -18,9 +18,7 @@ class PeopleServiceTests: CoreDataTestCase { } contextManager.performAndSave { context in - let account = WPAccount(context: context) - account.username = "username" - account.authToken = "token" + let account = WPAccount.fixture(context: context) let blog = Blog(context: context) blog.dotComID = NSNumber(value: self.siteID) diff --git a/WordPress/WordPressTest/WPAccount+Fixture.swift b/WordPress/WordPressTest/WPAccount+Fixture.swift new file mode 100644 index 000000000000..c56b8c08df0e --- /dev/null +++ b/WordPress/WordPressTest/WPAccount+Fixture.swift @@ -0,0 +1,20 @@ +/// Centralized utility to generate preconfigured WPAccount instances +extension WPAccount { + + static func fixture( + context: NSManagedObjectContext, + // Using a constant UUID by default to keep the tests deterministic. + // There's nothing special in the value itself. It's just a UUID() value copied over. + uuid: UUID = UUID(uuidString: "D0D0298F-D7EF-4F32-A1F8-DDDBB8ADB8DF")!, + userID: Int = 1, + username: String = "username", + authToken: String = "authToken" + ) -> WPAccount { + let account = WPAccount(context: context) + account.userID = NSNumber(value: userID) + account.username = username + account.authToken = authToken + account.uuid = uuid.uuidString + return account + } +} From aae784b80ffb33f60923f280f91288d76f9593fd Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 5 Jun 2023 16:06:50 +1000 Subject: [PATCH 152/175] Define `TestError`, an error type for tests --- WordPress/WordPress.xcodeproj/project.pbxproj | 12 ++++++++---- WordPress/WordPressTest/TestError.swift | 8 ++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 WordPress/WordPressTest/TestError.swift diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index abf3845cd057..0d179d7ef7dd 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -863,6 +863,7 @@ 3F751D462491A93D0008A2B1 /* ZendeskUtilsTests+Plans.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F751D452491A93D0008A2B1 /* ZendeskUtilsTests+Plans.swift */; }; 3F758FD524F6FB4900BBA2FC /* AnnouncementsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F758FD424F6FB4900BBA2FC /* AnnouncementsStore.swift */; }; 3F759FBA2A2DA93B0039A845 /* WPAccount+Fixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F759FB92A2DA93B0039A845 /* WPAccount+Fixture.swift */; }; + 3F759FBC2A2DB2CF0039A845 /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F759FBB2A2DB2CF0039A845 /* TestError.swift */; }; 3F762E9326784A950088CD45 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9226784A950088CD45 /* Logger.swift */; }; 3F762E9526784B540088CD45 /* WireMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9426784B540088CD45 /* WireMock.swift */; }; 3F762E9726784BED0088CD45 /* FancyAlertComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9626784BED0088CD45 /* FancyAlertComponent.swift */; }; @@ -6562,6 +6563,7 @@ 3F751D452491A93D0008A2B1 /* ZendeskUtilsTests+Plans.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZendeskUtilsTests+Plans.swift"; sourceTree = ""; }; 3F758FD424F6FB4900BBA2FC /* AnnouncementsStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnouncementsStore.swift; sourceTree = ""; }; 3F759FB92A2DA93B0039A845 /* WPAccount+Fixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WPAccount+Fixture.swift"; sourceTree = ""; }; + 3F759FBB2A2DB2CF0039A845 /* TestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestError.swift; sourceTree = ""; }; 3F762E9226784A950088CD45 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 3F762E9426784B540088CD45 /* WireMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireMock.swift; sourceTree = ""; }; 3F762E9626784BED0088CD45 /* FancyAlertComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyAlertComponent.swift; sourceTree = ""; }; @@ -12212,13 +12214,14 @@ 59B48B601B99E0B0008EBB84 /* TestUtilities */ = { isa = PBXGroup; children = ( - 59B48B611B99E132008EBB84 /* JSONObject.swift */, - E157D5DF1C690A6C00F04FB9 /* ImmuTableTestUtils.swift */, - 570BFD8F2282418A007859A8 /* PostBuilder.swift */, + 2481B1D4260D4E8B00AE59DB /* AccountBuilder.swift */, 57B71D4D230DB5F200789A68 /* BlogBuilder.swift */, + E157D5DF1C690A6C00F04FB9 /* ImmuTableTestUtils.swift */, + 59B48B611B99E132008EBB84 /* JSONObject.swift */, F11023A223186BCA00C4E84A /* MediaBuilder.swift */, 57889AB723589DF100DAE56D /* PageBuilder.swift */, - 2481B1D4260D4E8B00AE59DB /* AccountBuilder.swift */, + 570BFD8F2282418A007859A8 /* PostBuilder.swift */, + 3F759FBB2A2DB2CF0039A845 /* TestError.swift */, ); name = TestUtilities; sourceTree = ""; @@ -23528,6 +23531,7 @@ FF9A6E7121F9361700D36D14 /* MediaUploadHashTests.swift in Sources */, B532ACCF1DC3AB8E00FFFA57 /* NotificationSyncMediatorTests.swift in Sources */, 7E4A772F20F7FDF8001C706D /* ActivityLogTestData.swift in Sources */, + 3F759FBC2A2DB2CF0039A845 /* TestError.swift in Sources */, 40E7FEC82211EEC00032834E /* LastPostStatsRecordValueTests.swift in Sources */, 57240224234E5BE200227067 /* PostServiceSelfHostedTests.swift in Sources */, B59D40A61DB522DF003D2D79 /* NSAttributedStringTests.swift in Sources */, diff --git a/WordPress/WordPressTest/TestError.swift b/WordPress/WordPressTest/TestError.swift new file mode 100644 index 000000000000..417e1b1162d7 --- /dev/null +++ b/WordPress/WordPressTest/TestError.swift @@ -0,0 +1,8 @@ +struct TestError: Error { + + let id: Int + + init(id: Int = 1) { + self.id = id + } +} From 0d947e77815b47e54720031b44c1dc8e1853b6fe Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 5 Jun 2023 16:24:47 +1000 Subject: [PATCH 153/175] Use a test double over HTTP stub in `testUpdateFailure` --- WordPress/WordPress.xcodeproj/project.pbxproj | 4 + .../AccountSettingsRemoteInterfaceStub.swift | 77 +++++++++++++++++ .../AccountSettingsServiceTests.swift | 82 ++++++++++++------- 3 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 WordPress/WordPressTest/AccountSettingsRemoteInterfaceStub.swift diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 0d179d7ef7dd..1437352faa27 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -864,6 +864,7 @@ 3F758FD524F6FB4900BBA2FC /* AnnouncementsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F758FD424F6FB4900BBA2FC /* AnnouncementsStore.swift */; }; 3F759FBA2A2DA93B0039A845 /* WPAccount+Fixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F759FB92A2DA93B0039A845 /* WPAccount+Fixture.swift */; }; 3F759FBC2A2DB2CF0039A845 /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F759FBB2A2DB2CF0039A845 /* TestError.swift */; }; + 3F759FBE2A2DB3280039A845 /* AccountSettingsRemoteInterfaceStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F759FBD2A2DB3280039A845 /* AccountSettingsRemoteInterfaceStub.swift */; }; 3F762E9326784A950088CD45 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9226784A950088CD45 /* Logger.swift */; }; 3F762E9526784B540088CD45 /* WireMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9426784B540088CD45 /* WireMock.swift */; }; 3F762E9726784BED0088CD45 /* FancyAlertComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F762E9626784BED0088CD45 /* FancyAlertComponent.swift */; }; @@ -6564,6 +6565,7 @@ 3F758FD424F6FB4900BBA2FC /* AnnouncementsStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnouncementsStore.swift; sourceTree = ""; }; 3F759FB92A2DA93B0039A845 /* WPAccount+Fixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WPAccount+Fixture.swift"; sourceTree = ""; }; 3F759FBB2A2DB2CF0039A845 /* TestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestError.swift; sourceTree = ""; }; + 3F759FBD2A2DB3280039A845 /* AccountSettingsRemoteInterfaceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingsRemoteInterfaceStub.swift; sourceTree = ""; }; 3F762E9226784A950088CD45 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 3F762E9426784B540088CD45 /* WireMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireMock.swift; sourceTree = ""; }; 3F762E9626784BED0088CD45 /* FancyAlertComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyAlertComponent.swift; sourceTree = ""; }; @@ -15387,6 +15389,7 @@ children = ( 9363113E19FA996700B0C739 /* AccountServiceTests.swift */, 4A9948E129714EF1006282A9 /* AccountSettingsServiceTests.swift */, + 3F759FBD2A2DB3280039A845 /* AccountSettingsRemoteInterfaceStub.swift */, F1F083F5241FFE930056D3B1 /* AtomicAuthenticationServiceTests.swift */, 46F58500262605930010A723 /* BlockEditorSettingsServiceTests.swift */, FEA312832987FB0100FFD405 /* BlogJetpackTests.swift */, @@ -23519,6 +23522,7 @@ 7E987F58210811CC00CAFB88 /* NotificationContentRouterTests.swift in Sources */, E1C9AA561C10427100732665 /* MathTest.swift in Sources */, 93A379EC19FFBF7900415023 /* KeychainTest.m in Sources */, + 3F759FBE2A2DB3280039A845 /* AccountSettingsRemoteInterfaceStub.swift in Sources */, F1BB660C274E704D00A319BE /* LikeUserHelperTests.swift in Sources */, 436D5655212209D600CEAA33 /* RegisterDomainDetailsServiceProxyMock.swift in Sources */, F1450CF92437EEBB00A28BFE /* MediaRequestAuthenticatorTests.swift in Sources */, diff --git a/WordPress/WordPressTest/AccountSettingsRemoteInterfaceStub.swift b/WordPress/WordPressTest/AccountSettingsRemoteInterfaceStub.swift new file mode 100644 index 000000000000..59cd089c1887 --- /dev/null +++ b/WordPress/WordPressTest/AccountSettingsRemoteInterfaceStub.swift @@ -0,0 +1,77 @@ +@testable import WordPress +import WordPressKit + +class AccountSettingsRemoteInterfaceStub: AccountSettingsRemoteInterface { + + let updateSettingResult: Result<(), Error> + let getSettingsResult: Result + let changeUsernameShouldSucceed: Bool + let suggestUsernamesResult: [String] + let updatePasswordResult: Result<(), Error> + let closeAccountResult: Result<(), Error> + + init( + updateSettingResult: Result = .success(()), + // Defaulting to failure to avoid having to create AccountSettings here, because it required an NSManagedContext + getSettingsResult: Result = .failure(TestError()), + changeUsernameShouldSucceed: Bool = true, + suggestUsernamesResult: [String] = [], + updatePasswordResult: Result = .success(()), + closeAccountResult: Result = .success(()) + ) { + self.updateSettingResult = updateSettingResult + self.getSettingsResult = getSettingsResult + self.changeUsernameShouldSucceed = changeUsernameShouldSucceed + self.suggestUsernamesResult = suggestUsernamesResult + self.updatePasswordResult = updatePasswordResult + self.closeAccountResult = closeAccountResult + } + + func updateSetting(_ change: AccountSettingsChange, success: @escaping () -> Void, failure: @escaping (Error) -> Void) { + switch updateSettingResult { + case .success: + success() + case .failure(let error): + failure(error) + } + } + + func getSettings(success: @escaping (WordPressKit.AccountSettings) -> Void, failure: @escaping (Error) -> Void) { + switch getSettingsResult { + case .success(let settings): + success(settings) + case .failure(let error): + failure(error) + } + } + + func changeUsername(to username: String, success: @escaping () -> Void, failure: @escaping () -> Void) { + if changeUsernameShouldSucceed { + success() + } else { + failure() + } + } + + func suggestUsernames(base: String, finished: @escaping ([String]) -> Void) { + finished(suggestUsernamesResult) + } + + func updatePassword(_ password: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) { + switch updatePasswordResult { + case .success: + success() + case .failure(let error): + failure(error) + } + } + + func closeAccount(success: @escaping () -> Void, failure: @escaping (Error) -> Void) { + switch closeAccountResult { + case .success: + success() + case .failure(let error): + failure(error) + } + } +} diff --git a/WordPress/WordPressTest/AccountSettingsServiceTests.swift b/WordPress/WordPressTest/AccountSettingsServiceTests.swift index 6f4dbccdcae5..427963a3ff25 100644 --- a/WordPress/WordPressTest/AccountSettingsServiceTests.swift +++ b/WordPress/WordPressTest/AccountSettingsServiceTests.swift @@ -9,20 +9,9 @@ class AccountSettingsServiceTests: CoreDataTestCase { override func setUp() { let account = WPAccount.fixture(context: mainContext) - - let settings = ManagedAccountSettings(context: mainContext) - settings.account = account - settings.username = "Username" - settings.displayName = "Display Name" - settings.primarySiteID = 1 - settings.aboutMe = "" - settings.email = "test@email.com" - settings.firstName = "Test" - settings.lastName = "User" - settings.language = "en" - settings.webAddress = "https://test.wordpress.com" - + _ = makeManagedAccountSettings(context: mainContext, account: account) contextManager.saveContextAndWait(mainContext) + service = makeService(contextManager: contextManager, account: account) service = AccountSettingsService( userID: account.userID.intValue, @@ -31,18 +20,6 @@ class AccountSettingsServiceTests: CoreDataTestCase { ) } - private func managedAccountSettings() -> ManagedAccountSettings? { - contextManager.performQuery { context in - let request = NSFetchRequest(entityName: ManagedAccountSettings.entityName()) - request.predicate = NSPredicate(format: "account.userID = %d", self.service.userID) - request.fetchLimit = 1 - guard let results = (try? context.fetch(request)) as? [ManagedAccountSettings] else { - return nil - } - return results.first - } - } - func testUpdateSuccess() throws { stub(condition: isPath("/rest/v1.1/me/settings")) { _ in HTTPStubsResponse(jsonObject: [String: Any](), statusCode: 200, headers: nil) @@ -57,15 +34,21 @@ class AccountSettingsServiceTests: CoreDataTestCase { } func testUpdateFailure() throws { - stub(condition: isPath("/rest/v1.1/me/settings")) { _ in - HTTPStubsResponse(jsonObject: [String: Any](), statusCode: 500, headers: nil) - } + // We've seen some flakiness in CI on this test, and therefore are using a stub object rather than stubbing the HTTP requests. + // Since this approach bypasses the entire network stack, the hope is that it'll result in a more robust test. + let service = AccountSettingsService( + userID: 1, + remote: AccountSettingsRemoteInterfaceStub(updateSettingResult: .failure(TestError())), + coreDataStack: contextManager + ) + waitUntil { done in - self.service.saveChange(.firstName("Updated"), finished: { success in + service.saveChange(.firstName("Updated"), finished: { success in expect(success).to(beFalse()) done() }) } + expect(self.managedAccountSettings()?.firstName).to(equal("Test")) } @@ -96,3 +79,44 @@ class AccountSettingsServiceTests: CoreDataTestCase { wait(for: [notCrash], timeout: 0.5) } } + +extension AccountSettingsServiceTests { + + private func makeManagedAccountSettings( + context: NSManagedObjectContext, + account: WPAccount + ) -> ManagedAccountSettings { + let settings = ManagedAccountSettings(context: context) + settings.account = account + settings.username = "Username" + settings.displayName = "Display Name" + settings.primarySiteID = 1 + settings.aboutMe = "" + settings.email = "test@email.com" + settings.firstName = "Test" + settings.lastName = "User" + settings.language = "en" + settings.webAddress = "https://test.wordpress.com" + return settings + } + + private func makeService(contextManager: ContextManager, account: WPAccount) -> AccountSettingsService { + AccountSettingsService( + userID: account.userID.intValue, + remote: AccountSettingsRemote(wordPressComRestApi: account.wordPressComRestApi), + coreDataStack: contextManager + ) + } + + private func managedAccountSettings() -> ManagedAccountSettings? { + contextManager.performQuery { context in + let request = NSFetchRequest(entityName: ManagedAccountSettings.entityName()) + request.predicate = NSPredicate(format: "account.userID = %d", self.service.userID) + request.fetchLimit = 1 + guard let results = (try? context.fetch(request)) as? [ManagedAccountSettings] else { + return nil + } + return results.first + } + } +} From d244fdcfa5a4b8b2ee343fd9b5eb6b281d2af648 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 5 Jun 2023 20:21:04 +1000 Subject: [PATCH 154/175] Use a test double over HTTP stubs in `testUpdateSuccess` --- .../AccountSettingsServiceTests.swift | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/WordPress/WordPressTest/AccountSettingsServiceTests.swift b/WordPress/WordPressTest/AccountSettingsServiceTests.swift index 427963a3ff25..0681613fed44 100644 --- a/WordPress/WordPressTest/AccountSettingsServiceTests.swift +++ b/WordPress/WordPressTest/AccountSettingsServiceTests.swift @@ -21,15 +21,24 @@ class AccountSettingsServiceTests: CoreDataTestCase { } func testUpdateSuccess() throws { - stub(condition: isPath("/rest/v1.1/me/settings")) { _ in - HTTPStubsResponse(jsonObject: [String: Any](), statusCode: 200, headers: nil) - } + // We've seen some flakiness in CI on this test, and therefore are using a stub object rather than stubbing the HTTP requests. + // Since this approach bypasses the entire network stack, the hope is that it'll result in a more robust test. + // + // This is the second test in this class edited this way. + // If we'll need to update a third, we shall also take the time to update the rest of the tests. + let service = AccountSettingsService( + userID: 1, + remote: AccountSettingsRemoteInterfaceStub(updateSettingResult: .success(())), + coreDataStack: contextManager + ) + waitUntil { done in - self.service.saveChange(.firstName("Updated"), finished: { success in + service.saveChange(.firstName("Updated"), finished: { success in expect(success).to(beTrue()) done() }) } + expect(self.managedAccountSettings()?.firstName).to(equal("Updated")) } From 9d1438796025abaf34972c93d0dc72ae03879763 Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 7 Jul 2023 12:00:39 +0700 Subject: [PATCH 155/175] use default timeout value on screen init --- WordPress/UITestsFoundation/Screens/ActionSheetComponent.swift | 3 +-- WordPress/UITestsFoundation/Screens/ActivityLogScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/CommentsScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/DomainsScreen.swift | 3 +-- .../UITestsFoundation/Screens/DomainsSuggestionsScreen.swift | 3 +-- .../UITestsFoundation/Screens/Editor/AztecEditorScreen.swift | 3 +-- .../UITestsFoundation/Screens/Editor/BlockEditorScreen.swift | 3 +-- .../UITestsFoundation/Screens/Editor/ChooseLayoutScreen.swift | 3 +-- .../Screens/Editor/EditorNoticeComponent.swift | 3 +-- .../UITestsFoundation/Screens/Editor/EditorPostSettings.swift | 3 +-- .../Screens/Editor/EditorPublishEpilogueScreen.swift | 3 +-- .../Editor/EditorSettingsComponents/CategoriesComponent.swift | 3 +-- .../Editor/EditorSettingsComponents/TagsComponent.swift | 3 +-- .../UITestsFoundation/Screens/Editor/FeaturedImageScreen.swift | 3 +-- .../Screens/Jetpack/JetpackBackupOptionsScreen.swift | 3 +-- .../Screens/Jetpack/JetpackBackupScreen.swift | 3 +-- .../UITestsFoundation/Screens/Jetpack/JetpackScanScreen.swift | 3 +-- .../Screens/Login/FeatureIntroductionScreen.swift | 3 +-- .../UITestsFoundation/Screens/Login/LinkOrPasswordScreen.swift | 3 +-- .../Screens/Login/LoginCheckMagicLinkScreen.swift | 3 +-- .../UITestsFoundation/Screens/Login/LoginEmailScreen.swift | 3 +-- .../UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift | 2 +- .../UITestsFoundation/Screens/Login/LoginPasswordScreen.swift | 3 +-- .../Screens/Login/LoginSiteAddressScreen.swift | 3 +-- .../Screens/Login/LoginUsernamePasswordScreen.swift | 3 +-- .../Screens/Login/OnboardingQuestionsPromptScreen.swift | 3 +-- .../Screens/Login/QuickStartPromptScreen.swift | 3 +-- .../Screens/Login/Unified/GetStartedScreen.swift | 3 +-- .../Screens/Login/Unified/PasswordScreen.swift | 3 +-- .../Screens/Login/Unified/PrologueScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/Login/WelcomeScreen.swift | 3 +-- .../Screens/Login/WelcomeScreenLoginComponent.swift | 3 +-- WordPress/UITestsFoundation/Screens/Me/ContactUsScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/Me/SupportScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/MeTabScreen.swift | 3 +-- .../Screens/Media/MediaPickerAlbumListScreen.swift | 3 +-- .../Screens/Media/MediaPickerAlbumScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/MediaScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/MySiteScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/MySitesScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/NotificationsScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/PagesScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/PeopleScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/PlanSelectionScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/PostsScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/ReaderScreen.swift | 3 +-- .../Screens/Signup/SignupCheckMagicLinkScreen.swift | 3 +-- .../UITestsFoundation/Screens/Signup/SignupEmailScreen.swift | 3 +-- .../Screens/Signup/SignupEpilogueScreen.swift | 3 +-- .../Screens/Signup/WelcomeScreenSignupComponent.swift | 3 +-- WordPress/UITestsFoundation/Screens/SiteIntentScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/SiteSettingsScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/StatsScreen.swift | 3 +-- WordPress/UITestsFoundation/Screens/TabNavComponent.swift | 3 +-- 54 files changed, 54 insertions(+), 107 deletions(-) diff --git a/WordPress/UITestsFoundation/Screens/ActionSheetComponent.swift b/WordPress/UITestsFoundation/Screens/ActionSheetComponent.swift index 3f2bb2ce082a..d9675b8dac68 100644 --- a/WordPress/UITestsFoundation/Screens/ActionSheetComponent.swift +++ b/WordPress/UITestsFoundation/Screens/ActionSheetComponent.swift @@ -18,8 +18,7 @@ public class ActionSheetComponent: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [Self.getBlogPostButton, Self.getSitePageButton], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/ActivityLogScreen.swift b/WordPress/UITestsFoundation/Screens/ActivityLogScreen.swift index f7d13c501c19..604702eac863 100644 --- a/WordPress/UITestsFoundation/Screens/ActivityLogScreen.swift +++ b/WordPress/UITestsFoundation/Screens/ActivityLogScreen.swift @@ -20,8 +20,7 @@ public class ActivityLogScreen: ScreenObject { try super.init( expectedElementGetters: [ dateRangeButtonGetter, activityTypeButtonGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/CommentsScreen.swift b/WordPress/UITestsFoundation/Screens/CommentsScreen.swift index a56aa97cda52..da892230e8b4 100644 --- a/WordPress/UITestsFoundation/Screens/CommentsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/CommentsScreen.swift @@ -29,8 +29,7 @@ public class CommentsScreen: ScreenObject { navigationBarTitleGetter, replyFieldGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/DomainsScreen.swift b/WordPress/UITestsFoundation/Screens/DomainsScreen.swift index f604112f60b3..0227cdfff314 100644 --- a/WordPress/UITestsFoundation/Screens/DomainsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/DomainsScreen.swift @@ -15,8 +15,7 @@ public class DomainsScreen: ScreenObject { try super.init( expectedElementGetters: [ siteDomainsNavbarHeaderGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/DomainsSuggestionsScreen.swift b/WordPress/UITestsFoundation/Screens/DomainsSuggestionsScreen.swift index b9de5528d52b..eedb233add83 100644 --- a/WordPress/UITestsFoundation/Screens/DomainsSuggestionsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/DomainsSuggestionsScreen.swift @@ -12,8 +12,7 @@ public class DomainsSuggestionsScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ siteDomainsNavbarHeaderGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Editor/AztecEditorScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/AztecEditorScreen.swift index ea9281f7b287..9d9f467fb947 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/AztecEditorScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/AztecEditorScreen.swift @@ -49,8 +49,7 @@ public class AztecEditorScreen: ScreenObject { try super.init( expectedElementGetters: [ textViewGetter(textField) ], - app: app, - waitTimeout: 7 + app: app ) showOptionsStrip() diff --git a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift index e1808fe8ba66..fbb2d566ca6f 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/BlockEditorScreen.swift @@ -36,8 +36,7 @@ public class BlockEditorScreen: ScreenObject { // expect to encase the screen. try super.init( expectedElementGetters: [ editorCloseButtonGetter, addBlockButtonGetter ], - app: app, - waitTimeout: 10 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Editor/ChooseLayoutScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/ChooseLayoutScreen.swift index 82efcabbe5f7..3c871a6b8f4f 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/ChooseLayoutScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/ChooseLayoutScreen.swift @@ -10,8 +10,7 @@ public class ChooseLayoutScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [closeButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Editor/EditorNoticeComponent.swift b/WordPress/UITestsFoundation/Screens/Editor/EditorNoticeComponent.swift index fa697fbc05bb..21d96321b673 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/EditorNoticeComponent.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/EditorNoticeComponent.swift @@ -19,8 +19,7 @@ public class EditorNoticeComponent: ScreenObject { try super.init( expectedElementGetters: [ noticeTitleGetter, noticeActionGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Editor/EditorPostSettings.swift b/WordPress/UITestsFoundation/Screens/Editor/EditorPostSettings.swift index 82fad5c1b2a2..0ab2e404d0f4 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/EditorPostSettings.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/EditorPostSettings.swift @@ -15,8 +15,7 @@ public class EditorPostSettings: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.tables["SettingsTable"] } ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Editor/EditorPublishEpilogueScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/EditorPublishEpilogueScreen.swift index 498889a66639..b8540f16bac7 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/EditorPublishEpilogueScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/EditorPublishEpilogueScreen.swift @@ -22,8 +22,7 @@ public class EditorPublishEpilogueScreen: ScreenObject { try super.init( expectedElementGetters: [ getDoneButton, getViewButton, publishedPostStatusGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Editor/EditorSettingsComponents/CategoriesComponent.swift b/WordPress/UITestsFoundation/Screens/Editor/EditorSettingsComponents/CategoriesComponent.swift index e85876fe2a2f..81a4785ff1d4 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/EditorSettingsComponents/CategoriesComponent.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/EditorSettingsComponents/CategoriesComponent.swift @@ -6,8 +6,7 @@ public class CategoriesComponent: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.tables["CategoriesList"] } ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Editor/EditorSettingsComponents/TagsComponent.swift b/WordPress/UITestsFoundation/Screens/Editor/EditorSettingsComponents/TagsComponent.swift index a1971d7c459a..70d1dce5d570 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/EditorSettingsComponents/TagsComponent.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/EditorSettingsComponents/TagsComponent.swift @@ -9,8 +9,7 @@ public class TagsComponent: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.textViews["add-tags"] } ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Editor/FeaturedImageScreen.swift b/WordPress/UITestsFoundation/Screens/Editor/FeaturedImageScreen.swift index 85fa28be4af4..25b53ae7eaed 100644 --- a/WordPress/UITestsFoundation/Screens/Editor/FeaturedImageScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Editor/FeaturedImageScreen.swift @@ -9,8 +9,7 @@ public class FeaturedImageScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.navigationBars.buttons["Remove Featured Image"] } ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Jetpack/JetpackBackupOptionsScreen.swift b/WordPress/UITestsFoundation/Screens/Jetpack/JetpackBackupOptionsScreen.swift index 8f70944b0ad5..e58c7e2b39c4 100644 --- a/WordPress/UITestsFoundation/Screens/Jetpack/JetpackBackupOptionsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Jetpack/JetpackBackupOptionsScreen.swift @@ -6,8 +6,7 @@ public class JetpackBackupOptionsScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.otherElements.firstMatch } ], - app: app, - waitTimeout: 7 + app: app ) } } diff --git a/WordPress/UITestsFoundation/Screens/Jetpack/JetpackBackupScreen.swift b/WordPress/UITestsFoundation/Screens/Jetpack/JetpackBackupScreen.swift index 9d76d50092a4..999f5daae144 100644 --- a/WordPress/UITestsFoundation/Screens/Jetpack/JetpackBackupScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Jetpack/JetpackBackupScreen.swift @@ -13,8 +13,7 @@ public class JetpackBackupScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ellipsisButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Jetpack/JetpackScanScreen.swift b/WordPress/UITestsFoundation/Screens/Jetpack/JetpackScanScreen.swift index c7f4ffc71168..e8163214592d 100644 --- a/WordPress/UITestsFoundation/Screens/Jetpack/JetpackScanScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Jetpack/JetpackScanScreen.swift @@ -6,8 +6,7 @@ public class JetpackScanScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.otherElements.firstMatch } ], - app: app, - waitTimeout: 7 + app: app ) } } diff --git a/WordPress/UITestsFoundation/Screens/Login/FeatureIntroductionScreen.swift b/WordPress/UITestsFoundation/Screens/Login/FeatureIntroductionScreen.swift index 286aec0b8ac7..cc24651c330d 100644 --- a/WordPress/UITestsFoundation/Screens/Login/FeatureIntroductionScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/FeatureIntroductionScreen.swift @@ -11,8 +11,7 @@ public class FeatureIntroductionScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [closeButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/LinkOrPasswordScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LinkOrPasswordScreen.swift index 112c4874552b..047fc58dbfa1 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LinkOrPasswordScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LinkOrPasswordScreen.swift @@ -15,8 +15,7 @@ public class LinkOrPasswordScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [passwordOptionGetter, linkButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/LoginCheckMagicLinkScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LoginCheckMagicLinkScreen.swift index bef86bbdd037..b139ef346caa 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LoginCheckMagicLinkScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LoginCheckMagicLinkScreen.swift @@ -14,8 +14,7 @@ public class LoginCheckMagicLinkScreen: ScreenObject { // swiftlint:disable:next opening_brace { $0.buttons["Open Mail Button"] } ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/LoginEmailScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LoginEmailScreen.swift index 6cda9eb87a1b..0891e67a5501 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LoginEmailScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LoginEmailScreen.swift @@ -19,8 +19,7 @@ public class LoginEmailScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [emailTextFieldGetter, nextButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift index bc604324c582..84d5c51a3ddc 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift @@ -13,7 +13,7 @@ public class LoginEpilogueScreen: ScreenObject { try super.init( expectedElementGetters: [loginEpilogueTableGetter], app: app, - waitTimeout: 60 + waitTimeout: 65 ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/LoginPasswordScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LoginPasswordScreen.swift index 6efe5deee507..925beaae4f39 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LoginPasswordScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LoginPasswordScreen.swift @@ -12,8 +12,7 @@ class LoginPasswordScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [passwordTextFieldGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/LoginSiteAddressScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LoginSiteAddressScreen.swift index badf06ea22bb..fb26648ded3d 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LoginSiteAddressScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LoginSiteAddressScreen.swift @@ -26,8 +26,7 @@ public class LoginSiteAddressScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [siteAddressTextFieldGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/LoginUsernamePasswordScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LoginUsernamePasswordScreen.swift index cf09c4119dbb..e4cad8fc1423 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LoginUsernamePasswordScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LoginUsernamePasswordScreen.swift @@ -44,8 +44,7 @@ public class LoginUsernamePasswordScreen: ScreenObject { usernameTextFieldGetter, passwordTextFieldGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/OnboardingQuestionsPromptScreen.swift b/WordPress/UITestsFoundation/Screens/Login/OnboardingQuestionsPromptScreen.swift index 2040762b4965..e635424686a8 100644 --- a/WordPress/UITestsFoundation/Screens/Login/OnboardingQuestionsPromptScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/OnboardingQuestionsPromptScreen.swift @@ -11,8 +11,7 @@ public class OnboardingQuestionsPromptScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [skipButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/QuickStartPromptScreen.swift b/WordPress/UITestsFoundation/Screens/Login/QuickStartPromptScreen.swift index df95937f0914..7030d33819a0 100644 --- a/WordPress/UITestsFoundation/Screens/Login/QuickStartPromptScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/QuickStartPromptScreen.swift @@ -12,8 +12,7 @@ public class QuickStartPromptScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [noThanksButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/Unified/GetStartedScreen.swift b/WordPress/UITestsFoundation/Screens/Login/Unified/GetStartedScreen.swift index 5dfd2c9cbd96..9ca50cf44f44 100644 --- a/WordPress/UITestsFoundation/Screens/Login/Unified/GetStartedScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/Unified/GetStartedScreen.swift @@ -38,8 +38,7 @@ public class GetStartedScreen: ScreenObject { emailTextFieldGetter, helpButtonGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/Unified/PasswordScreen.swift b/WordPress/UITestsFoundation/Screens/Login/Unified/PasswordScreen.swift index 72391366b384..acfb9af7c8d1 100644 --- a/WordPress/UITestsFoundation/Screens/Login/Unified/PasswordScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/Unified/PasswordScreen.swift @@ -22,8 +22,7 @@ public class PasswordScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ passwordTextFieldGetter, continueButtonGetter ], - app: app, - waitTimeout: 10 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/Unified/PrologueScreen.swift b/WordPress/UITestsFoundation/Screens/Login/Unified/PrologueScreen.swift index 0ce3bbe70e84..0553f0f62187 100644 --- a/WordPress/UITestsFoundation/Screens/Login/Unified/PrologueScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/Unified/PrologueScreen.swift @@ -17,8 +17,7 @@ public class PrologueScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [continueButtonGetter, siteAddressButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/WelcomeScreen.swift b/WordPress/UITestsFoundation/Screens/Login/WelcomeScreen.swift index ef5359088ae4..89b7a16bb708 100644 --- a/WordPress/UITestsFoundation/Screens/Login/WelcomeScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/WelcomeScreen.swift @@ -19,8 +19,7 @@ public class WelcomeScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [logInButtonGetter, signupButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Login/WelcomeScreenLoginComponent.swift b/WordPress/UITestsFoundation/Screens/Login/WelcomeScreenLoginComponent.swift index 90b8adc111e4..fb4b2b8510cf 100644 --- a/WordPress/UITestsFoundation/Screens/Login/WelcomeScreenLoginComponent.swift +++ b/WordPress/UITestsFoundation/Screens/Login/WelcomeScreenLoginComponent.swift @@ -15,8 +15,7 @@ public class WelcomeScreenLoginComponent: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [emailLoginButtonGetter, siteAddressButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Me/ContactUsScreen.swift b/WordPress/UITestsFoundation/Screens/Me/ContactUsScreen.swift index 8c6532cd9789..d72765b6fa03 100644 --- a/WordPress/UITestsFoundation/Screens/Me/ContactUsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Me/ContactUsScreen.swift @@ -34,8 +34,7 @@ public class ContactUsScreen: ScreenObject { closeButtonGetter, attachButtonGetter, ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Me/SupportScreen.swift b/WordPress/UITestsFoundation/Screens/Me/SupportScreen.swift index 96c415b73623..2a5f47896790 100644 --- a/WordPress/UITestsFoundation/Screens/Me/SupportScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Me/SupportScreen.swift @@ -39,8 +39,7 @@ public class SupportScreen: ScreenObject { { $0.cells["activity-logs-button"] } // swiftlint:enable opening_brace ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/MeTabScreen.swift b/WordPress/UITestsFoundation/Screens/MeTabScreen.swift index 5e1d0a5e6309..e0381e8f4788 100644 --- a/WordPress/UITestsFoundation/Screens/MeTabScreen.swift +++ b/WordPress/UITestsFoundation/Screens/MeTabScreen.swift @@ -20,8 +20,7 @@ public class MeTabScreen: ScreenObject { try super.init( expectedElementGetter: { $0.cells["appSettings"] }, - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Media/MediaPickerAlbumListScreen.swift b/WordPress/UITestsFoundation/Screens/Media/MediaPickerAlbumListScreen.swift index 4b9ed8d1b3bf..78cc4ea51377 100644 --- a/WordPress/UITestsFoundation/Screens/Media/MediaPickerAlbumListScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Media/MediaPickerAlbumListScreen.swift @@ -10,8 +10,7 @@ public class MediaPickerAlbumListScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetter: albumListGetter, - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Media/MediaPickerAlbumScreen.swift b/WordPress/UITestsFoundation/Screens/Media/MediaPickerAlbumScreen.swift index 8f4f73d97de6..133381f8689b 100644 --- a/WordPress/UITestsFoundation/Screens/Media/MediaPickerAlbumScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Media/MediaPickerAlbumScreen.swift @@ -9,8 +9,7 @@ public class MediaPickerAlbumScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [mediaCollectionGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/MediaScreen.swift b/WordPress/UITestsFoundation/Screens/MediaScreen.swift index 7e6bc5a7e9a6..771bf1f50c12 100644 --- a/WordPress/UITestsFoundation/Screens/MediaScreen.swift +++ b/WordPress/UITestsFoundation/Screens/MediaScreen.swift @@ -6,8 +6,7 @@ public class MediaScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.collectionViews["MediaCollection"] } ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/MySiteScreen.swift b/WordPress/UITestsFoundation/Screens/MySiteScreen.swift index 3dec24a98350..6178c4e7cae8 100644 --- a/WordPress/UITestsFoundation/Screens/MySiteScreen.swift +++ b/WordPress/UITestsFoundation/Screens/MySiteScreen.swift @@ -128,8 +128,7 @@ public class MySiteScreen: ScreenObject { mediaButtonGetter, createButtonGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/MySitesScreen.swift b/WordPress/UITestsFoundation/Screens/MySitesScreen.swift index 3400d18d604d..f04bf90a0627 100644 --- a/WordPress/UITestsFoundation/Screens/MySitesScreen.swift +++ b/WordPress/UITestsFoundation/Screens/MySitesScreen.swift @@ -24,8 +24,7 @@ public class MySitesScreen: ScreenObject { cancelButtonGetter, plusButtonGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/NotificationsScreen.swift b/WordPress/UITestsFoundation/Screens/NotificationsScreen.swift index d32f47c3926c..ad7699648e2c 100644 --- a/WordPress/UITestsFoundation/Screens/NotificationsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/NotificationsScreen.swift @@ -6,8 +6,7 @@ public class NotificationsScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.tables["Notifications Table"] } ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/PagesScreen.swift b/WordPress/UITestsFoundation/Screens/PagesScreen.swift index 607e6cb68118..020b04fddcd8 100644 --- a/WordPress/UITestsFoundation/Screens/PagesScreen.swift +++ b/WordPress/UITestsFoundation/Screens/PagesScreen.swift @@ -15,8 +15,7 @@ public class PagesScreen: ScreenObject { try super.init( expectedElementGetters: [ pagesTableGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/PeopleScreen.swift b/WordPress/UITestsFoundation/Screens/PeopleScreen.swift index 2c4c3feec618..378c44a7cb3a 100644 --- a/WordPress/UITestsFoundation/Screens/PeopleScreen.swift +++ b/WordPress/UITestsFoundation/Screens/PeopleScreen.swift @@ -17,8 +17,7 @@ public class PeopleScreen: ScreenObject { filterButtonGetter("followers"), filterButtonGetter("email") ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/PlanSelectionScreen.swift b/WordPress/UITestsFoundation/Screens/PlanSelectionScreen.swift index c7f25de4f311..66d59d41658a 100644 --- a/WordPress/UITestsFoundation/Screens/PlanSelectionScreen.swift +++ b/WordPress/UITestsFoundation/Screens/PlanSelectionScreen.swift @@ -9,8 +9,7 @@ public class PlanSelectionScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ webViewGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/PostsScreen.swift b/WordPress/UITestsFoundation/Screens/PostsScreen.swift index 91e4aa7f60ec..a74d99b906bd 100644 --- a/WordPress/UITestsFoundation/Screens/PostsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/PostsScreen.swift @@ -13,8 +13,7 @@ public class PostsScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.tables["PostsTable"] } ], - app: app, - waitTimeout: 7 + app: app ) showOnly(.published) } diff --git a/WordPress/UITestsFoundation/Screens/ReaderScreen.swift b/WordPress/UITestsFoundation/Screens/ReaderScreen.swift index 7a8e88b4670e..3262e451c5e0 100644 --- a/WordPress/UITestsFoundation/Screens/ReaderScreen.swift +++ b/WordPress/UITestsFoundation/Screens/ReaderScreen.swift @@ -16,8 +16,7 @@ public class ReaderScreen: ScreenObject { { $0.tables["Reader"] }, discoverButtonGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Signup/SignupCheckMagicLinkScreen.swift b/WordPress/UITestsFoundation/Screens/Signup/SignupCheckMagicLinkScreen.swift index 5f1c5e354e7b..4f5e23593c74 100644 --- a/WordPress/UITestsFoundation/Screens/Signup/SignupCheckMagicLinkScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Signup/SignupCheckMagicLinkScreen.swift @@ -7,8 +7,7 @@ public class SignupCheckMagicLinkScreen: ScreenObject { try super.init( // swiftlint:disable:next opening_brace expectedElementGetters: [{ $0.buttons["Open Mail Button"] }], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Signup/SignupEmailScreen.swift b/WordPress/UITestsFoundation/Screens/Signup/SignupEmailScreen.swift index 96300e13f8a5..3d164b737497 100644 --- a/WordPress/UITestsFoundation/Screens/Signup/SignupEmailScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Signup/SignupEmailScreen.swift @@ -19,8 +19,7 @@ public class SignupEmailScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [emailTextFieldGetter, nextButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Signup/SignupEpilogueScreen.swift b/WordPress/UITestsFoundation/Screens/Signup/SignupEpilogueScreen.swift index 8da53c217c21..a73de050180c 100644 --- a/WordPress/UITestsFoundation/Screens/Signup/SignupEpilogueScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Signup/SignupEpilogueScreen.swift @@ -6,8 +6,7 @@ public class SignupEpilogueScreen: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ { $0.staticTexts["New Account Header"] } ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/Signup/WelcomeScreenSignupComponent.swift b/WordPress/UITestsFoundation/Screens/Signup/WelcomeScreenSignupComponent.swift index ff23b0cd0dc9..7f9a73546a38 100644 --- a/WordPress/UITestsFoundation/Screens/Signup/WelcomeScreenSignupComponent.swift +++ b/WordPress/UITestsFoundation/Screens/Signup/WelcomeScreenSignupComponent.swift @@ -14,8 +14,7 @@ public class WelcomeScreenSignupComponent: ScreenObject { init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [emailSignupButtonGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/SiteIntentScreen.swift b/WordPress/UITestsFoundation/Screens/SiteIntentScreen.swift index 36036dd0aad0..6fe6aac44eb5 100644 --- a/WordPress/UITestsFoundation/Screens/SiteIntentScreen.swift +++ b/WordPress/UITestsFoundation/Screens/SiteIntentScreen.swift @@ -14,8 +14,7 @@ public class SiteIntentScreen: ScreenObject { { $0.tables["Site Intent Table"] }, cancelButtonGetter ], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/SiteSettingsScreen.swift b/WordPress/UITestsFoundation/Screens/SiteSettingsScreen.swift index 04f2fa1752c2..66b8e84fd0f1 100644 --- a/WordPress/UITestsFoundation/Screens/SiteSettingsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/SiteSettingsScreen.swift @@ -16,8 +16,7 @@ public class SiteSettingsScreen: ScreenObject { public init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [blockEditorToggleGetter], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/StatsScreen.swift b/WordPress/UITestsFoundation/Screens/StatsScreen.swift index 48b6aa3b6c7c..909287fbac4b 100644 --- a/WordPress/UITestsFoundation/Screens/StatsScreen.swift +++ b/WordPress/UITestsFoundation/Screens/StatsScreen.swift @@ -19,8 +19,7 @@ public class StatsScreen: ScreenObject { try super.init( // swiftlint:disable:next opening_brace expectedElementGetters: [{ $0.otherElements.firstMatch }], - app: app, - waitTimeout: 7 + app: app ) } diff --git a/WordPress/UITestsFoundation/Screens/TabNavComponent.swift b/WordPress/UITestsFoundation/Screens/TabNavComponent.swift index c4c627d0f901..00e05583f4a6 100644 --- a/WordPress/UITestsFoundation/Screens/TabNavComponent.swift +++ b/WordPress/UITestsFoundation/Screens/TabNavComponent.swift @@ -30,8 +30,7 @@ public class TabNavComponent: ScreenObject { readerTabButtonGetter, notificationsTabButtonGetter ], - app: app, - waitTimeout: 7 + app: app ) } From d47ca80f41c53d396431f7d25f6faeb13a0ee632 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 7 Jul 2023 15:54:01 +1000 Subject: [PATCH 156/175] Fix a minor typo in the tests --- WordPress/WordPressTest/LikeUserHelperTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/WordPressTest/LikeUserHelperTests.swift b/WordPress/WordPressTest/LikeUserHelperTests.swift index 322484593c63..dedd508a8f1a 100644 --- a/WordPress/WordPressTest/LikeUserHelperTests.swift +++ b/WordPress/WordPressTest/LikeUserHelperTests.swift @@ -95,7 +95,7 @@ class LikeUserHelperTests: CoreDataTestCase { let user = RemoteLikeUser(dictionary: dict, commentID: commentID, siteID: siteID) _ = LikeUserHelper.createOrUpdateFrom(remoteUser: user, context: mainContext) } - // Insert likes with an older data + // Insert likes with an older date for _ in 1...5 { let dict = createTestRemoteUserDictionary(withPreferredBlog: false, year: 1990) let user = RemoteLikeUser(dictionary: dict, commentID: commentID, siteID: siteID) From 2a9545aa6819e7b8940f1ee1e6c70aae87767ada Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 7 Jul 2023 13:02:06 +0700 Subject: [PATCH 157/175] update timeout for save password and login epilogue screen --- .../UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift | 2 +- WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift b/WordPress/UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift index 84d5c51a3ddc..92d52d0f4d38 100644 --- a/WordPress/UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift +++ b/WordPress/UITestsFoundation/Screens/Login/LoginEpilogueScreen.swift @@ -13,7 +13,7 @@ public class LoginEpilogueScreen: ScreenObject { try super.init( expectedElementGetters: [loginEpilogueTableGetter], app: app, - waitTimeout: 65 + waitTimeout: 70 ) } diff --git a/WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift b/WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift index 8edf6e440b7b..dac04c35873d 100644 --- a/WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift +++ b/WordPress/UITestsFoundation/XCUIApplication+SavePassword.swift @@ -6,7 +6,7 @@ extension XCUIApplication { // This method encapsulates the logic to dimiss the prompt. func dismissSavePasswordPrompt() { XCTContext.runActivity(named: "Dismiss save password prompt if needed.") { _ in - guard buttons["Save Password"].waitForExistence(timeout: 5) else { return } + guard buttons["Save Password"].waitForExistence(timeout: 10) else { return } // There should be no need to wait for this button to exist since it's part of the same // alert where "Save Password" is. From dee45b8aee38cd3abe92656a8852e4a83dd19e37 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 7 Jul 2023 16:08:36 +1000 Subject: [PATCH 158/175] =?UTF-8?q?Update=20app=20translations=20=E2=80=93?= =?UTF-8?q?=20`Localizable.strings`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WordPress/Resources/id.lproj/Localizable.strings | 5 ++++- WordPress/Resources/ro.lproj/Localizable.strings | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/WordPress/Resources/id.lproj/Localizable.strings b/WordPress/Resources/id.lproj/Localizable.strings index b57ee70158fa..200041fc6e3a 100644 --- a/WordPress/Resources/id.lproj/Localizable.strings +++ b/WordPress/Resources/id.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-06-27 09:54:29+0000 */ +/* Translation-Revision-Date: 2023-06-30 15:06:09+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/4.0.0-alpha.4 */ /* Language: id */ @@ -9728,6 +9728,9 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* Short status description */ "blazeCampaign.status.rejected" = "Ditolak"; +/* Short status description */ +"blazeCampaign.status.scheduled" = "Terjadwal"; + /* Title for budget stats view */ "blazeCampaigns.budget" = "Anggaran:"; diff --git a/WordPress/Resources/ro.lproj/Localizable.strings b/WordPress/Resources/ro.lproj/Localizable.strings index d8ed769b33d1..28720b3d26f6 100644 --- a/WordPress/Resources/ro.lproj/Localizable.strings +++ b/WordPress/Resources/ro.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2023-06-26 13:11:37+0000 */ +/* Translation-Revision-Date: 2023-07-02 16:09:18+0000 */ /* Plural-Forms: nplurals=3; plural=(n == 1) ? 0 : ((n == 0 || n % 100 >= 2 && n % 100 <= 19) ? 1 : 2); */ /* Generator: GlotPress/4.0.0-alpha.4 */ /* Language: ro */ @@ -2314,7 +2314,7 @@ translators: Block name. %s: The localized block name */ /* Label for selecting the default post format Title for screen to select a default post format for a blog */ -"Default Post Format" = "Format articol implicit"; +"Default Post Format" = "Format implicit pentru articole"; /* Placeholder for the reader CSS URL */ "Default URL" = "URL implicit"; From 482e809102c0360d430c589c0e5a560a44725f6c Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 7 Jul 2023 16:10:23 +1000 Subject: [PATCH 159/175] Update WordPress metadata translations --- fastlane/metadata/ar-SA/release_notes.txt | 6 ++++++ fastlane/metadata/de-DE/release_notes.txt | 6 ++++++ fastlane/metadata/default/release_notes.txt | 11 +++++------ fastlane/metadata/es-ES/release_notes.txt | 6 ++++++ fastlane/metadata/fr-FR/release_notes.txt | 6 ++++++ fastlane/metadata/he/release_notes.txt | 6 ++++++ fastlane/metadata/id/release_notes.txt | 6 ++++++ fastlane/metadata/it/release_notes.txt | 6 ++++++ fastlane/metadata/ja/release_notes.txt | 6 ++++++ fastlane/metadata/ko/release_notes.txt | 6 ++++++ fastlane/metadata/nl-NL/release_notes.txt | 6 ++++++ fastlane/metadata/ru/release_notes.txt | 6 ++++++ fastlane/metadata/sv/release_notes.txt | 6 ++++++ fastlane/metadata/tr/release_notes.txt | 6 ++++++ fastlane/metadata/zh-Hans/release_notes.txt | 6 ++++++ fastlane/metadata/zh-Hant/release_notes.txt | 6 ++++++ 16 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 fastlane/metadata/ar-SA/release_notes.txt create mode 100644 fastlane/metadata/de-DE/release_notes.txt create mode 100644 fastlane/metadata/es-ES/release_notes.txt create mode 100644 fastlane/metadata/fr-FR/release_notes.txt create mode 100644 fastlane/metadata/he/release_notes.txt create mode 100644 fastlane/metadata/id/release_notes.txt create mode 100644 fastlane/metadata/it/release_notes.txt create mode 100644 fastlane/metadata/ja/release_notes.txt create mode 100644 fastlane/metadata/ko/release_notes.txt create mode 100644 fastlane/metadata/nl-NL/release_notes.txt create mode 100644 fastlane/metadata/ru/release_notes.txt create mode 100644 fastlane/metadata/sv/release_notes.txt create mode 100644 fastlane/metadata/tr/release_notes.txt create mode 100644 fastlane/metadata/zh-Hans/release_notes.txt create mode 100644 fastlane/metadata/zh-Hant/release_notes.txt diff --git a/fastlane/metadata/ar-SA/release_notes.txt b/fastlane/metadata/ar-SA/release_notes.txt new file mode 100644 index 000000000000..165c71a28dd5 --- /dev/null +++ b/fastlane/metadata/ar-SA/release_notes.txt @@ -0,0 +1,6 @@ +أصلحنا مشكلة في بطاقة "العمل على مسودة تدوينة" في الشاشة الرئيسية. لن يتعطل التطبيق بعد الآن عند الوصول إلى المسودات في أثناء الوجود في منتصف عملية الرفع. + +قمنا بحل مشكلتين في محرر المكوّنات. + +- ناحية المين، تعرض مكوّنات الصور الآن نسبة العرض إلى الارتفاع الصحيحة، سواء أكانت الصورة تحتوي على عرض وارتفاع محدَدين أم لا. +- عندما تكتب نصًا، سيظل مرضع المؤشر في المكان المفترض أن يكون فيه؛ ولن يتحرك. تحلَّ بالهدوء وواصل الكتابة. diff --git a/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/de-DE/release_notes.txt new file mode 100644 index 000000000000..6738dbabed4e --- /dev/null +++ b/fastlane/metadata/de-DE/release_notes.txt @@ -0,0 +1,6 @@ +Wir haben ein Problem mit der Karte „An einem Beitragsentwurf arbeiten“ der Startseite behoben. Die App stürzt nun nicht mehr ab, wenn du auf Entwürfe zugreifst, während sie hochgeladen werden. + +Außerdem haben wir einige Probleme im Block-Editor behoben. + +- Bei Bildblöcken wird jetzt das richtige Bildformat angezeigt, egal ob Höhe und Breite des Bildes definiert sind oder nicht. +- Beim Diktieren bleibt der Cursor an der gewünschten Stelle und springt nicht mehr herum. So kannst du ganz in Ruhe weiterdiktieren. diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index 637ca793c29c..3c0fb643df2b 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,7 +1,6 @@ -* [**] [internal] Blaze: Switch to using new canBlaze property to determine Blaze eligiblity. [#20916] -* [**] Fixed crash issue when accessing drafts that are mid-upload from the Home 'Work on a Draft Post' card. [#20872] -* [**] [internal] Make sure media-related features function correctly. [#20889], [20887] -* [*] [internal] Posts list: Disable action bar/menu button when a post is being uploaded [#20885] -* [*] Block editor: Image block - Fix issue where in some cases the image doesn't display the right aspect ratio [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5869] -* [*] Block editor: Fix cursor positioning when dictating text on iOS [https://github.com/WordPress/gutenberg/issues/51227] +We fixed an issue with the home screen’s “Work on a draft post” card. The app will no longer crash when you access drafts while they’re in the middle of uploading. +We also solved a couple of problems in the block editor. + +- Right on—image blocks now display the correct aspect ratio, whether or not the image has a set width and height. +- When you’re dictating text, the cursor’s position will stay where it’s supposed to—no more jumping around. Keep calm and dictate on. diff --git a/fastlane/metadata/es-ES/release_notes.txt b/fastlane/metadata/es-ES/release_notes.txt new file mode 100644 index 000000000000..7601ed233ddd --- /dev/null +++ b/fastlane/metadata/es-ES/release_notes.txt @@ -0,0 +1,6 @@ +Hemos corregido un problema que se producía en la tarjeta “Trabajar en un borrador de entrada” de la pantalla de inicio. La aplicación ya no se bloqueará si accedes a los borradores mientras se cargan. + +También hemos resuelto un par de problemas en el editor de bloques. + +- Ahora, los bloques de imagen muestran la relación de aspecto correcta, tanto si la imagen tiene una anchura y una altura determinadas como si no. +- Al dictar un texto, la posición del cursor se mantendrá en su sitio y no se producirán saltos. De esta manera, podrás dictar con tranquilidad. diff --git a/fastlane/metadata/fr-FR/release_notes.txt b/fastlane/metadata/fr-FR/release_notes.txt new file mode 100644 index 000000000000..86cc0dc7247c --- /dev/null +++ b/fastlane/metadata/fr-FR/release_notes.txt @@ -0,0 +1,6 @@ +Nous avons corrigé un problème avec la carte « Travailler sur un brouillon d’article » de l’écran d’accueil. L’application ne rencontre plus d’incident lorsque vous accédez à des brouillons en cours de chargement. + +Nous avons également résolu quelques problèmes dans l’éditeur de blocs. + +- Les blocs d’images de droite affichent désormais le bon rapport hauteur/largeur, que l’image soit ou non définie en largeur et en hauteur. +- Lorsque vous dictez du texte, la position du curseur reste à l’endroit prévu, il n’y a plus de sauts. Restez calme et continuez à dicter. diff --git a/fastlane/metadata/he/release_notes.txt b/fastlane/metadata/he/release_notes.txt new file mode 100644 index 000000000000..2646a7b35fc9 --- /dev/null +++ b/fastlane/metadata/he/release_notes.txt @@ -0,0 +1,6 @@ +תיקנו בעיה עם הכרטיס "לערוך פוסט טיוטה" שהופיע במסך הבית. האפליקציה לא קורסת עוד כאשר ניגשים לטיוטות שנמצאות בתהליך העלאה. + +בנוסף, פתרנו כמה בעיות בעורך הבלוקים. + +- ישר ולעניין – בלוקים של תמונה כעת מוצגים ביחס גובה-רוחב מתאים, לא משנה אם הרוחב והגובה של התמונה הוגדרו מראש. +- אם מכתיבים טקסט, המיקום של הסמן יישאר במקום הנכון – ולא יקפוץ ברחבי העמוד. אפשר להכתיב בשקט. diff --git a/fastlane/metadata/id/release_notes.txt b/fastlane/metadata/id/release_notes.txt new file mode 100644 index 000000000000..7b97938cba29 --- /dev/null +++ b/fastlane/metadata/id/release_notes.txt @@ -0,0 +1,6 @@ +Kami sudah membereskan masalah kartu “Buat draft pos” di layar beranda. Aplikasi kini tidak akan mengalami crash lagi ketika Anda mengakses draft saat sedang diunggah. + +Kami juga telah mengatasi beberapa masalah di editor blok. + +- Betul sekali. Blok gambar kini menampilkan rasio aspek yang tepat, terlepas dari apakah lebar dan tinggi gambar sudah ditentukan. +- Ketika Anda mendiktekan teks, kursor tetap berada di posisinya dan tidak lagi melompat-lompat. Tetaplah tenang dan teruslah mendikte. diff --git a/fastlane/metadata/it/release_notes.txt b/fastlane/metadata/it/release_notes.txt new file mode 100644 index 000000000000..696c2a845eda --- /dev/null +++ b/fastlane/metadata/it/release_notes.txt @@ -0,0 +1,6 @@ +Abbiamo risolto un problema con la scheda "Lavora su un articolo bozza" nella schermata iniziale. L'app non si arresta più in modo anomalo quando si accede alle bozze mentre sono in fase di caricamento. + +Abbiamo anche risolto un paio di problemi nell'editor a blocchi. + +- Miglioramento immediato: i blocchi di immagini ora mostrano le proporzioni corrette, indipendentemente dal fatto che l'immagine abbia o meno larghezza e altezza impostate. +- Durante la dettatura di un testo, la posizione del cursore rimarrà dove deve, non dovrai muoverti nel testo. Calma e inizia a dettare. diff --git a/fastlane/metadata/ja/release_notes.txt b/fastlane/metadata/ja/release_notes.txt new file mode 100644 index 000000000000..f7169a5a66a9 --- /dev/null +++ b/fastlane/metadata/ja/release_notes.txt @@ -0,0 +1,6 @@ +ホーム画面の「下書き投稿を作成」カードの問題を修正しました。 今後はアップロード中に下書きにアクセスしてもアプリがクラッシュすることはありません。 + +ブロックエディターの問題もいくつか解決しました。 + +- 修正完了 - 画像の幅と高さが設定されているかどうかにかかわらず、画像ブロックでは正しい縦横比が表示されるようになりました。 +- テキストを音声入力する際に、カーソルが所定の位置に留まり、飛び回ることがなくなります。 落ち着いて音声で入力することができます。 diff --git a/fastlane/metadata/ko/release_notes.txt b/fastlane/metadata/ko/release_notes.txt new file mode 100644 index 000000000000..6ccdb644a4d9 --- /dev/null +++ b/fastlane/metadata/ko/release_notes.txt @@ -0,0 +1,6 @@ +홈 화면의 "임시글로 글 작업" 카드와 관련한 문제를 해결했습니다. 이제는 임시글을 업로드하는 도중에 접근할 때 앱이 충돌하지 않습니다. + +블록 편집기의 몇 가지 문제도 해결했습니다. + +- 정말입니다. 설정된 너비와 높이가 이미지에 있는지 여부와 관계없이 이제는 이미지 블록이 올바른 화면 비율로 표시됩니다. +- 텍스트를 받아쓰기할 때 커서의 위치가 이리저리 돌아다니지 않고 유지됩니다. 계속 차분하게 받아쓰세요. diff --git a/fastlane/metadata/nl-NL/release_notes.txt b/fastlane/metadata/nl-NL/release_notes.txt new file mode 100644 index 000000000000..cffee33237c0 --- /dev/null +++ b/fastlane/metadata/nl-NL/release_notes.txt @@ -0,0 +1,6 @@ +We hebben een probleem opgelost met de kaart ‘Aan een conceptbericht werken’ op het startscherm. De app crasht niet meer wanneer je concepten opent terwijl ze nog worden geüpload. + +We hebben ook een aantal problemen opgelost met de blokeditor. + +- Geweldig, afbeeldingblokken worden nu in de juiste beeldverhouding weergegeven, of er nou wel of niet een breedte en hoogte zijn ingesteld voor het beeld. +- Wanneer je een tekst dicteert, blijft de cursor waar die zou moeten zijn en springt deze niet meer over het scherm. Blijf kalm en dicteer verder. diff --git a/fastlane/metadata/ru/release_notes.txt b/fastlane/metadata/ru/release_notes.txt new file mode 100644 index 000000000000..f48a54c1082b --- /dev/null +++ b/fastlane/metadata/ru/release_notes.txt @@ -0,0 +1,6 @@ +Исправлена проблема с карточкой "Работа над черновиком" на главном экране. Приложение больше не будет аварийно закрываться, когда вы пытаетесь открыть черновики в процессе их загрузки на сервер. + +Также решена пара проблем в редакторе блоков. + +— Блоки изображений наконец-то будут иметь верное соотношение сторон независимо от заданной ширины и высоты изображения. +— Курсор больше не скачет при диктовке текста. Вы можете спокойно продолжать диктовку, ни на что не отвлекаясь. diff --git a/fastlane/metadata/sv/release_notes.txt b/fastlane/metadata/sv/release_notes.txt new file mode 100644 index 000000000000..a5ae9b99bc1a --- /dev/null +++ b/fastlane/metadata/sv/release_notes.txt @@ -0,0 +1,6 @@ +Vi har åtgärdat ett problem med startskärmskortet "Arbeta med ett inläggsutkast". Appen kommer inte längre att krascha om du öppnar utkast medan de fortfarande håller på att laddas upp. + +Vi har även löst ett antal problem i blockredigeraren. + +- Fixat – bildblock visas nu med rätt bildförhållande, oavsett om bilden har en angiven bredd och höjd eller inte. +- När du dikterar text kommer markörens position att förbli där den ska vara – inget mer omkringhoppande. Ta det lugnt och diktera vidare. diff --git a/fastlane/metadata/tr/release_notes.txt b/fastlane/metadata/tr/release_notes.txt new file mode 100644 index 000000000000..0fae38e25b8d --- /dev/null +++ b/fastlane/metadata/tr/release_notes.txt @@ -0,0 +1,6 @@ +Ana ekranın "Bir taslak yazı üzerinde çalışın" kartıyla ilgili bir sorunu düzelttik. Yüklenmekte olan taslaklara eriştiğinizde artık uygulama kilitlenmeyecek. + +Ayrıca, blok düzenleyicisiyle ilgili birkaç problemi de giderdik. + +- Tam isabet—Görsel blokları, görsel için belirlenmiş bir genişlik ve yükseklik olsun ya da olmasın artık doğru en boy oranını gösteriyor. +- Metni dikte ederken imlecin konumu olması gerektiği yerde kalır ve sağa sola hareket etmez. Sakin olun ve dikte etmeye devam edin. diff --git a/fastlane/metadata/zh-Hans/release_notes.txt b/fastlane/metadata/zh-Hans/release_notes.txt new file mode 100644 index 000000000000..1dce585107b9 --- /dev/null +++ b/fastlane/metadata/zh-Hans/release_notes.txt @@ -0,0 +1,6 @@ +我们修复了主屏幕上“继续撰写文章草稿”卡片的问题。 访问正在上传的草稿时,应用不会再崩溃。 + +我们还解决了区块编辑器中的几个问题。 + +- 现在,无论否设置了图片宽度和高度,图片编辑器上的图片都可以显示正确的宽高比。 +- 口述文字时,光标的位置会停留在正确位置,不会再出现跳跃的情况。 保持冷静,继续口述。 diff --git a/fastlane/metadata/zh-Hant/release_notes.txt b/fastlane/metadata/zh-Hant/release_notes.txt new file mode 100644 index 000000000000..d02f16c8380f --- /dev/null +++ b/fastlane/metadata/zh-Hant/release_notes.txt @@ -0,0 +1,6 @@ +我們修正了主畫面「編輯草稿文章」資訊卡的問題。 當你存取正在上傳的草稿時,應用程式不會再當機。 + +我們也解決了區塊編輯器的幾個問題。 + +- 不論圖片是否設定寬度和高度,圖片區塊現在會以正確的長寬比顯示。 +- 聽寫文字時,游標位置會停留在應該顯示的位置,不會再跳來跳去, 讓你安心使用聽寫功能。 From cd1b009385645389c23f4ec55e64927a98f77730 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 7 Jul 2023 16:10:30 +1000 Subject: [PATCH 160/175] Update Jetpack metadata translations --- fastlane/jetpack_metadata/ar-SA/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/de-DE/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/default/release_notes.txt | 11 +++++------ fastlane/jetpack_metadata/es-ES/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/fr-FR/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/he/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/id/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/it/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/ja/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/ko/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/nl-NL/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/pt-BR/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/ru/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/sv/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/tr/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/zh-Hans/release_notes.txt | 6 ++++++ fastlane/jetpack_metadata/zh-Hant/release_notes.txt | 6 ++++++ 17 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 fastlane/jetpack_metadata/ar-SA/release_notes.txt create mode 100644 fastlane/jetpack_metadata/de-DE/release_notes.txt create mode 100644 fastlane/jetpack_metadata/es-ES/release_notes.txt create mode 100644 fastlane/jetpack_metadata/fr-FR/release_notes.txt create mode 100644 fastlane/jetpack_metadata/he/release_notes.txt create mode 100644 fastlane/jetpack_metadata/id/release_notes.txt create mode 100644 fastlane/jetpack_metadata/it/release_notes.txt create mode 100644 fastlane/jetpack_metadata/ja/release_notes.txt create mode 100644 fastlane/jetpack_metadata/ko/release_notes.txt create mode 100644 fastlane/jetpack_metadata/nl-NL/release_notes.txt create mode 100644 fastlane/jetpack_metadata/pt-BR/release_notes.txt create mode 100644 fastlane/jetpack_metadata/ru/release_notes.txt create mode 100644 fastlane/jetpack_metadata/sv/release_notes.txt create mode 100644 fastlane/jetpack_metadata/tr/release_notes.txt create mode 100644 fastlane/jetpack_metadata/zh-Hans/release_notes.txt create mode 100644 fastlane/jetpack_metadata/zh-Hant/release_notes.txt diff --git a/fastlane/jetpack_metadata/ar-SA/release_notes.txt b/fastlane/jetpack_metadata/ar-SA/release_notes.txt new file mode 100644 index 000000000000..165c71a28dd5 --- /dev/null +++ b/fastlane/jetpack_metadata/ar-SA/release_notes.txt @@ -0,0 +1,6 @@ +أصلحنا مشكلة في بطاقة "العمل على مسودة تدوينة" في الشاشة الرئيسية. لن يتعطل التطبيق بعد الآن عند الوصول إلى المسودات في أثناء الوجود في منتصف عملية الرفع. + +قمنا بحل مشكلتين في محرر المكوّنات. + +- ناحية المين، تعرض مكوّنات الصور الآن نسبة العرض إلى الارتفاع الصحيحة، سواء أكانت الصورة تحتوي على عرض وارتفاع محدَدين أم لا. +- عندما تكتب نصًا، سيظل مرضع المؤشر في المكان المفترض أن يكون فيه؛ ولن يتحرك. تحلَّ بالهدوء وواصل الكتابة. diff --git a/fastlane/jetpack_metadata/de-DE/release_notes.txt b/fastlane/jetpack_metadata/de-DE/release_notes.txt new file mode 100644 index 000000000000..6738dbabed4e --- /dev/null +++ b/fastlane/jetpack_metadata/de-DE/release_notes.txt @@ -0,0 +1,6 @@ +Wir haben ein Problem mit der Karte „An einem Beitragsentwurf arbeiten“ der Startseite behoben. Die App stürzt nun nicht mehr ab, wenn du auf Entwürfe zugreifst, während sie hochgeladen werden. + +Außerdem haben wir einige Probleme im Block-Editor behoben. + +- Bei Bildblöcken wird jetzt das richtige Bildformat angezeigt, egal ob Höhe und Breite des Bildes definiert sind oder nicht. +- Beim Diktieren bleibt der Cursor an der gewünschten Stelle und springt nicht mehr herum. So kannst du ganz in Ruhe weiterdiktieren. diff --git a/fastlane/jetpack_metadata/default/release_notes.txt b/fastlane/jetpack_metadata/default/release_notes.txt index 637ca793c29c..3c0fb643df2b 100644 --- a/fastlane/jetpack_metadata/default/release_notes.txt +++ b/fastlane/jetpack_metadata/default/release_notes.txt @@ -1,7 +1,6 @@ -* [**] [internal] Blaze: Switch to using new canBlaze property to determine Blaze eligiblity. [#20916] -* [**] Fixed crash issue when accessing drafts that are mid-upload from the Home 'Work on a Draft Post' card. [#20872] -* [**] [internal] Make sure media-related features function correctly. [#20889], [20887] -* [*] [internal] Posts list: Disable action bar/menu button when a post is being uploaded [#20885] -* [*] Block editor: Image block - Fix issue where in some cases the image doesn't display the right aspect ratio [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5869] -* [*] Block editor: Fix cursor positioning when dictating text on iOS [https://github.com/WordPress/gutenberg/issues/51227] +We fixed an issue with the home screen’s “Work on a draft post” card. The app will no longer crash when you access drafts while they’re in the middle of uploading. +We also solved a couple of problems in the block editor. + +- Right on—image blocks now display the correct aspect ratio, whether or not the image has a set width and height. +- When you’re dictating text, the cursor’s position will stay where it’s supposed to—no more jumping around. Keep calm and dictate on. diff --git a/fastlane/jetpack_metadata/es-ES/release_notes.txt b/fastlane/jetpack_metadata/es-ES/release_notes.txt new file mode 100644 index 000000000000..7601ed233ddd --- /dev/null +++ b/fastlane/jetpack_metadata/es-ES/release_notes.txt @@ -0,0 +1,6 @@ +Hemos corregido un problema que se producía en la tarjeta “Trabajar en un borrador de entrada” de la pantalla de inicio. La aplicación ya no se bloqueará si accedes a los borradores mientras se cargan. + +También hemos resuelto un par de problemas en el editor de bloques. + +- Ahora, los bloques de imagen muestran la relación de aspecto correcta, tanto si la imagen tiene una anchura y una altura determinadas como si no. +- Al dictar un texto, la posición del cursor se mantendrá en su sitio y no se producirán saltos. De esta manera, podrás dictar con tranquilidad. diff --git a/fastlane/jetpack_metadata/fr-FR/release_notes.txt b/fastlane/jetpack_metadata/fr-FR/release_notes.txt new file mode 100644 index 000000000000..86cc0dc7247c --- /dev/null +++ b/fastlane/jetpack_metadata/fr-FR/release_notes.txt @@ -0,0 +1,6 @@ +Nous avons corrigé un problème avec la carte « Travailler sur un brouillon d’article » de l’écran d’accueil. L’application ne rencontre plus d’incident lorsque vous accédez à des brouillons en cours de chargement. + +Nous avons également résolu quelques problèmes dans l’éditeur de blocs. + +- Les blocs d’images de droite affichent désormais le bon rapport hauteur/largeur, que l’image soit ou non définie en largeur et en hauteur. +- Lorsque vous dictez du texte, la position du curseur reste à l’endroit prévu, il n’y a plus de sauts. Restez calme et continuez à dicter. diff --git a/fastlane/jetpack_metadata/he/release_notes.txt b/fastlane/jetpack_metadata/he/release_notes.txt new file mode 100644 index 000000000000..2646a7b35fc9 --- /dev/null +++ b/fastlane/jetpack_metadata/he/release_notes.txt @@ -0,0 +1,6 @@ +תיקנו בעיה עם הכרטיס "לערוך פוסט טיוטה" שהופיע במסך הבית. האפליקציה לא קורסת עוד כאשר ניגשים לטיוטות שנמצאות בתהליך העלאה. + +בנוסף, פתרנו כמה בעיות בעורך הבלוקים. + +- ישר ולעניין – בלוקים של תמונה כעת מוצגים ביחס גובה-רוחב מתאים, לא משנה אם הרוחב והגובה של התמונה הוגדרו מראש. +- אם מכתיבים טקסט, המיקום של הסמן יישאר במקום הנכון – ולא יקפוץ ברחבי העמוד. אפשר להכתיב בשקט. diff --git a/fastlane/jetpack_metadata/id/release_notes.txt b/fastlane/jetpack_metadata/id/release_notes.txt new file mode 100644 index 000000000000..7b97938cba29 --- /dev/null +++ b/fastlane/jetpack_metadata/id/release_notes.txt @@ -0,0 +1,6 @@ +Kami sudah membereskan masalah kartu “Buat draft pos” di layar beranda. Aplikasi kini tidak akan mengalami crash lagi ketika Anda mengakses draft saat sedang diunggah. + +Kami juga telah mengatasi beberapa masalah di editor blok. + +- Betul sekali. Blok gambar kini menampilkan rasio aspek yang tepat, terlepas dari apakah lebar dan tinggi gambar sudah ditentukan. +- Ketika Anda mendiktekan teks, kursor tetap berada di posisinya dan tidak lagi melompat-lompat. Tetaplah tenang dan teruslah mendikte. diff --git a/fastlane/jetpack_metadata/it/release_notes.txt b/fastlane/jetpack_metadata/it/release_notes.txt new file mode 100644 index 000000000000..d389c5a30905 --- /dev/null +++ b/fastlane/jetpack_metadata/it/release_notes.txt @@ -0,0 +1,6 @@ +Abbiamo risolto un problema con la scheda "Lavora su un articolo bozza" nella schermata iniziale. L'app non si arresta più in modo anomalo quando si accede alle bozze mentre sono in fase di caricamento. + +Abbiamo anche risolto un paio di problemi nell'Editor a blocchi. + +- Miglioramento immediato: i blocchi di immagini ora mostrano le proporzioni corrette, indipendentemente dal fatto che l'immagine abbia o meno larghezza e altezza impostate. +- Durante la dettatura di un testo, la posizione del cursore rimarrà dove deve, non dovrai muoverti nel testo. Keep calm and dictate on. diff --git a/fastlane/jetpack_metadata/ja/release_notes.txt b/fastlane/jetpack_metadata/ja/release_notes.txt new file mode 100644 index 000000000000..f7169a5a66a9 --- /dev/null +++ b/fastlane/jetpack_metadata/ja/release_notes.txt @@ -0,0 +1,6 @@ +ホーム画面の「下書き投稿を作成」カードの問題を修正しました。 今後はアップロード中に下書きにアクセスしてもアプリがクラッシュすることはありません。 + +ブロックエディターの問題もいくつか解決しました。 + +- 修正完了 - 画像の幅と高さが設定されているかどうかにかかわらず、画像ブロックでは正しい縦横比が表示されるようになりました。 +- テキストを音声入力する際に、カーソルが所定の位置に留まり、飛び回ることがなくなります。 落ち着いて音声で入力することができます。 diff --git a/fastlane/jetpack_metadata/ko/release_notes.txt b/fastlane/jetpack_metadata/ko/release_notes.txt new file mode 100644 index 000000000000..6ccdb644a4d9 --- /dev/null +++ b/fastlane/jetpack_metadata/ko/release_notes.txt @@ -0,0 +1,6 @@ +홈 화면의 "임시글로 글 작업" 카드와 관련한 문제를 해결했습니다. 이제는 임시글을 업로드하는 도중에 접근할 때 앱이 충돌하지 않습니다. + +블록 편집기의 몇 가지 문제도 해결했습니다. + +- 정말입니다. 설정된 너비와 높이가 이미지에 있는지 여부와 관계없이 이제는 이미지 블록이 올바른 화면 비율로 표시됩니다. +- 텍스트를 받아쓰기할 때 커서의 위치가 이리저리 돌아다니지 않고 유지됩니다. 계속 차분하게 받아쓰세요. diff --git a/fastlane/jetpack_metadata/nl-NL/release_notes.txt b/fastlane/jetpack_metadata/nl-NL/release_notes.txt new file mode 100644 index 000000000000..cffee33237c0 --- /dev/null +++ b/fastlane/jetpack_metadata/nl-NL/release_notes.txt @@ -0,0 +1,6 @@ +We hebben een probleem opgelost met de kaart ‘Aan een conceptbericht werken’ op het startscherm. De app crasht niet meer wanneer je concepten opent terwijl ze nog worden geüpload. + +We hebben ook een aantal problemen opgelost met de blokeditor. + +- Geweldig, afbeeldingblokken worden nu in de juiste beeldverhouding weergegeven, of er nou wel of niet een breedte en hoogte zijn ingesteld voor het beeld. +- Wanneer je een tekst dicteert, blijft de cursor waar die zou moeten zijn en springt deze niet meer over het scherm. Blijf kalm en dicteer verder. diff --git a/fastlane/jetpack_metadata/pt-BR/release_notes.txt b/fastlane/jetpack_metadata/pt-BR/release_notes.txt new file mode 100644 index 000000000000..950261413c2e --- /dev/null +++ b/fastlane/jetpack_metadata/pt-BR/release_notes.txt @@ -0,0 +1,6 @@ +Corrigimos um problema no cartão "Trabalhar em um post em rascunho" da tela inicial. O aplicativo não vai mais travar quando você acessar os rascunhos durante o upload deles. + +Também resolvemos alguns problemas no editor de blocos. + +- Sem defeitos: agora os blocos de imagem vão exibir a proporção correta mesmo se a largura e altura da imagem não estiverem definidas. +- Quando você ditar o texto, a posição do cursor vai ficar no lugar certo e não pulando para lá e para cá. Continue a ditar, continue a ditar... diff --git a/fastlane/jetpack_metadata/ru/release_notes.txt b/fastlane/jetpack_metadata/ru/release_notes.txt new file mode 100644 index 000000000000..b0efc98c6e3d --- /dev/null +++ b/fastlane/jetpack_metadata/ru/release_notes.txt @@ -0,0 +1,6 @@ +Исправлена проблема с карточкой «Работа над черновиком» на главном экране. Приложение прекратило аварийно закрываться при попытке открыть черновики в процессе загрузки на сервер. + +Также решена пара проблем в редакторе блоков. + +— У блоков изображений сохраняется верное соотношение сторон независимо от заданной ширины и высоты изображения. +— Курсор больше не скачет при диктовке текста. Вы можете спокойно продолжать диктовать, ни на что не отвлекаясь. diff --git a/fastlane/jetpack_metadata/sv/release_notes.txt b/fastlane/jetpack_metadata/sv/release_notes.txt new file mode 100644 index 000000000000..a5ae9b99bc1a --- /dev/null +++ b/fastlane/jetpack_metadata/sv/release_notes.txt @@ -0,0 +1,6 @@ +Vi har åtgärdat ett problem med startskärmskortet "Arbeta med ett inläggsutkast". Appen kommer inte längre att krascha om du öppnar utkast medan de fortfarande håller på att laddas upp. + +Vi har även löst ett antal problem i blockredigeraren. + +- Fixat – bildblock visas nu med rätt bildförhållande, oavsett om bilden har en angiven bredd och höjd eller inte. +- När du dikterar text kommer markörens position att förbli där den ska vara – inget mer omkringhoppande. Ta det lugnt och diktera vidare. diff --git a/fastlane/jetpack_metadata/tr/release_notes.txt b/fastlane/jetpack_metadata/tr/release_notes.txt new file mode 100644 index 000000000000..0fae38e25b8d --- /dev/null +++ b/fastlane/jetpack_metadata/tr/release_notes.txt @@ -0,0 +1,6 @@ +Ana ekranın "Bir taslak yazı üzerinde çalışın" kartıyla ilgili bir sorunu düzelttik. Yüklenmekte olan taslaklara eriştiğinizde artık uygulama kilitlenmeyecek. + +Ayrıca, blok düzenleyicisiyle ilgili birkaç problemi de giderdik. + +- Tam isabet—Görsel blokları, görsel için belirlenmiş bir genişlik ve yükseklik olsun ya da olmasın artık doğru en boy oranını gösteriyor. +- Metni dikte ederken imlecin konumu olması gerektiği yerde kalır ve sağa sola hareket etmez. Sakin olun ve dikte etmeye devam edin. diff --git a/fastlane/jetpack_metadata/zh-Hans/release_notes.txt b/fastlane/jetpack_metadata/zh-Hans/release_notes.txt new file mode 100644 index 000000000000..1dce585107b9 --- /dev/null +++ b/fastlane/jetpack_metadata/zh-Hans/release_notes.txt @@ -0,0 +1,6 @@ +我们修复了主屏幕上“继续撰写文章草稿”卡片的问题。 访问正在上传的草稿时,应用不会再崩溃。 + +我们还解决了区块编辑器中的几个问题。 + +- 现在,无论否设置了图片宽度和高度,图片编辑器上的图片都可以显示正确的宽高比。 +- 口述文字时,光标的位置会停留在正确位置,不会再出现跳跃的情况。 保持冷静,继续口述。 diff --git a/fastlane/jetpack_metadata/zh-Hant/release_notes.txt b/fastlane/jetpack_metadata/zh-Hant/release_notes.txt new file mode 100644 index 000000000000..d02f16c8380f --- /dev/null +++ b/fastlane/jetpack_metadata/zh-Hant/release_notes.txt @@ -0,0 +1,6 @@ +我們修正了主畫面「編輯草稿文章」資訊卡的問題。 當你存取正在上傳的草稿時,應用程式不會再當機。 + +我們也解決了區塊編輯器的幾個問題。 + +- 不論圖片是否設定寬度和高度,圖片區塊現在會以正確的長寬比顯示。 +- 聽寫文字時,游標位置會停留在應該顯示的位置,不會再跳來跳去, 讓你安心使用聽寫功能。 From d910d4f274e68f228d40bcd94621fe30fd857a79 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 7 Jul 2023 16:10:43 +1000 Subject: [PATCH 161/175] Bump version number --- config/Version.internal.xcconfig | 2 +- config/Version.public.xcconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/Version.internal.xcconfig b/config/Version.internal.xcconfig index fa13abaf8b2c..8be7920a3779 100644 --- a/config/Version.internal.xcconfig +++ b/config/Version.internal.xcconfig @@ -1,4 +1,4 @@ VERSION_SHORT=22.7 // Internal long version example: VERSION_LONG=9.9.0.20180423 -VERSION_LONG=22.7.0.20230630 +VERSION_LONG=22.7.0.20230707 diff --git a/config/Version.public.xcconfig b/config/Version.public.xcconfig index 042eddd2a355..e4f36ae3cc00 100644 --- a/config/Version.public.xcconfig +++ b/config/Version.public.xcconfig @@ -1,4 +1,4 @@ VERSION_SHORT=22.7 // Public long version example: VERSION_LONG=9.9.0.0 -VERSION_LONG=22.7.0.1 +VERSION_LONG=22.7.0.2 From 2f0299a0d8efa0d0a7bfecfc4c052d7e3dcc71fc Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 7 Jul 2023 16:23:43 +1000 Subject: [PATCH 162/175] Make `setShowPhase2Dialog(_, for:)` call version with `forBlogURL` --- WordPress/Classes/Utility/Editor/GutenbergSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/Utility/Editor/GutenbergSettings.swift b/WordPress/Classes/Utility/Editor/GutenbergSettings.swift index 228083e8eae9..603c2fee212b 100644 --- a/WordPress/Classes/Utility/Editor/GutenbergSettings.swift +++ b/WordPress/Classes/Utility/Editor/GutenbergSettings.swift @@ -104,7 +104,7 @@ class GutenbergSettings { } func setShowPhase2Dialog(_ showDialog: Bool, for blog: Blog) { - database.set(showDialog, forKey: Key.showPhase2Dialog(forBlogURL: blog.url)) + setShowPhase2Dialog(showDialog, forBlogURL: blog.url) } func setShowPhase2Dialog(_ showDialog: Bool, forBlogURL url: String?) { From 47e2ac827c6db943d1cadb9b8f8344370f04c631 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 7 Jul 2023 16:32:53 +1000 Subject: [PATCH 163/175] Update `GutenbergSettingsTests` after "breaking" source changes See dfc2007f199e01b3c2beb3f4fb7216f5ce2ac58f --- WordPress/WordPressTest/GutenbergSettingsTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/WordPressTest/GutenbergSettingsTests.swift b/WordPress/WordPressTest/GutenbergSettingsTests.swift index daf8e81d283e..c6ea339c3e3d 100644 --- a/WordPress/WordPressTest/GutenbergSettingsTests.swift +++ b/WordPress/WordPressTest/GutenbergSettingsTests.swift @@ -294,7 +294,7 @@ class GutenbergSettingsTests: CoreDataTestCase { blog.mobileEditor = editor if gutenbergEnabledFlag != nil { - let perSiteEnabledKey = GutenbergSettings.Key.enabledOnce(for: blog) + let perSiteEnabledKey = GutenbergSettings.Key.enabledOnce(forBlogURL: blog.url) database.set(true, forKey: perSiteEnabledKey) } } @@ -308,7 +308,7 @@ class GutenbergSettingsTests: CoreDataTestCase { settings.performGutenbergPhase2MigrationIfNeeded() - XCTAssertTrue(database.bool(forKey: GutenbergSettings.Key.showPhase2Dialog(for: blog))) + XCTAssertTrue(database.bool(forKey: GutenbergSettings.Key.showPhase2Dialog(forBlogURL: blog.url))) XCTAssertTrue(GutenbergRollout(database: database).isUserInRolloutGroup) } From 1e62b4868bed5f6d524e8251d6d29b3d4e737d4e Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:04:24 +0700 Subject: [PATCH 164/175] empty commit to rerun tests From e46e8afb312c686806f94634b5eb8dc1f57957f9 Mon Sep 17 00:00:00 2001 From: jos <17252150+jostnes@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:59:52 +0700 Subject: [PATCH 165/175] empty commit to rerun tests From 9d08541fe3ad618de76a8d8dec41ddacab7860b3 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 09:46:41 -0400 Subject: [PATCH 166/175] 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 8be7920a3779..3f4183673d42 100644 --- a/config/Version.internal.xcconfig +++ b/config/Version.internal.xcconfig @@ -1,4 +1,4 @@ -VERSION_SHORT=22.7 +VERSION_SHORT=22.8 // Internal long version example: VERSION_LONG=9.9.0.20180423 -VERSION_LONG=22.7.0.20230707 +VERSION_LONG=22.8.0.20230710 diff --git a/config/Version.public.xcconfig b/config/Version.public.xcconfig index e4f36ae3cc00..0441a4bd8f3c 100644 --- a/config/Version.public.xcconfig +++ b/config/Version.public.xcconfig @@ -1,4 +1,4 @@ -VERSION_SHORT=22.7 +VERSION_SHORT=22.8 // Public long version example: VERSION_LONG=9.9.0.0 -VERSION_LONG=22.7.0.2 +VERSION_LONG=22.8.0.0 From de3863176fbb1c6c1504c50042132bb26c61dfab Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 09:46:41 -0400 Subject: [PATCH 167/175] Update draft release notes for 22.8. --- WordPress/Resources/release_notes.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/WordPress/Resources/release_notes.txt b/WordPress/Resources/release_notes.txt index 3c0fb643df2b..42816e59d62b 100644 --- a/WordPress/Resources/release_notes.txt +++ b/WordPress/Resources/release_notes.txt @@ -1,6 +1,8 @@ -We fixed an issue with the home screen’s “Work on a draft post” card. The app will no longer crash when you access drafts while they’re in the middle of uploading. +* [*] Blogging Reminders: Disabled prompt for self-hosted sites not connected to Jetpack. [#20970] +* [**] [internal] Do not save synced blogs if the app has signed out. [#20959] +* [**] [internal] Make sure synced posts are saved before calling completion block. [#20960] +* [**] [internal] Fix observing Quick Start notifications. [#20997] +* [**] [internal] Fixed an issue that was causing a memory leak in the domain selection flow. [#20813] +* [*] [Jetpack-only] Block editor: Rename "Reusable blocks" to "Synced patterns", aligning with the web editor. [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5885] +* [**] [internal] Block editor: Fix a crash related to Reanimated when closing the editor [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5938] -We also solved a couple of problems in the block editor. - -- Right on—image blocks now display the correct aspect ratio, whether or not the image has a set width and height. -- When you’re dictating text, the cursor’s position will stay where it’s supposed to—no more jumping around. Keep calm and dictate on. From bd9ca1e636307562f6765ac321a9360b72bccd94 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 09:46:41 -0400 Subject: [PATCH 168/175] Update draft release notes for 22.8. --- WordPress/Jetpack/Resources/release_notes.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/WordPress/Jetpack/Resources/release_notes.txt b/WordPress/Jetpack/Resources/release_notes.txt index 3c0fb643df2b..42816e59d62b 100644 --- a/WordPress/Jetpack/Resources/release_notes.txt +++ b/WordPress/Jetpack/Resources/release_notes.txt @@ -1,6 +1,8 @@ -We fixed an issue with the home screen’s “Work on a draft post” card. The app will no longer crash when you access drafts while they’re in the middle of uploading. +* [*] Blogging Reminders: Disabled prompt for self-hosted sites not connected to Jetpack. [#20970] +* [**] [internal] Do not save synced blogs if the app has signed out. [#20959] +* [**] [internal] Make sure synced posts are saved before calling completion block. [#20960] +* [**] [internal] Fix observing Quick Start notifications. [#20997] +* [**] [internal] Fixed an issue that was causing a memory leak in the domain selection flow. [#20813] +* [*] [Jetpack-only] Block editor: Rename "Reusable blocks" to "Synced patterns", aligning with the web editor. [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5885] +* [**] [internal] Block editor: Fix a crash related to Reanimated when closing the editor [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5938] -We also solved a couple of problems in the block editor. - -- Right on—image blocks now display the correct aspect ratio, whether or not the image has a set width and height. -- When you’re dictating text, the cursor’s position will stay where it’s supposed to—no more jumping around. Keep calm and dictate on. From d98b25450d26ab6833bd56791f036d6bd7b1f9c4 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 09:46:41 -0400 Subject: [PATCH 169/175] Release Notes: add new section for next version (22.9) --- RELEASE-NOTES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f4db083c0afa..bf2d6f7fc5d0 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,3 +1,7 @@ +22.9 +----- + + 22.8 ----- * [*] Blogging Reminders: Disabled prompt for self-hosted sites not connected to Jetpack. [#20970] From bfed0b97ae094721ded3076c3d8cc2601269053c Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 11:36:43 -0400 Subject: [PATCH 170/175] Update WordPressShared to 2.2 --- Podfile | 2 +- Podfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Podfile b/Podfile index e3bc85766801..8e77821749b4 100644 --- a/Podfile +++ b/Podfile @@ -23,7 +23,7 @@ workspace 'WordPress.xcworkspace' ## =================================== ## def wordpress_shared - pod 'WordPressShared', '~> 2.2-beta' + pod 'WordPressShared', '~> 2.2' # pod 'WordPressShared', git: 'https://github.com/wordpress-mobile/WordPress-iOS-Shared.git', tag: '' # pod 'WordPressShared', git: 'https://github.com/wordpress-mobile/WordPress-iOS-Shared.git', branch: 'trunk' # pod 'WordPressShared', git: 'https://github.com/wordpress-mobile/WordPress-iOS-Shared.git', commit: '' diff --git a/Podfile.lock b/Podfile.lock index 13c21db26f3d..dc587a4cd61a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -613,7 +613,7 @@ DEPENDENCIES: - WordPress-Editor-iOS (~> 1.19.8) - WordPressAuthenticator (~> 6.1-beta) - WordPressKit (~> 8.5-beta) - - WordPressShared (~> 2.2-beta) + - WordPressShared (~> 2.2) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/Yoga.podspec.json`) @@ -895,6 +895,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 -PODFILE CHECKSUM: dd2923ba0655d06a0e85bdf9aa0d38304e8f087e +PODFILE CHECKSUM: 083ba0431a3f9b99fbe76f863d853355f9432eae COCOAPODS: 1.12.1 From 99a948aa96f7b12bb9995040bfe6181c10d2f0be Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 11:38:50 -0400 Subject: [PATCH 171/175] Update WordPressKit to 8.5 --- Podfile | 2 +- Podfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Podfile b/Podfile index 8e77821749b4..818456a9bc41 100644 --- a/Podfile +++ b/Podfile @@ -50,7 +50,7 @@ def wordpress_ui end def wordpress_kit - pod 'WordPressKit', '~> 8.5-beta' + pod 'WordPressKit', '~> 8.5' # 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', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '' diff --git a/Podfile.lock b/Podfile.lock index dc587a4cd61a..bcbc4ab9b1ac 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -512,7 +512,7 @@ PODS: - WordPressKit (~> 8.0-beta) - WordPressShared (~> 2.1-beta) - WordPressUI (~> 1.7-beta) - - WordPressKit (8.5.0-beta.2): + - WordPressKit (8.5.0): - Alamofire (~> 4.8.0) - NSObject-SafeExpectations (~> 0.0.4) - UIDeviceIdentifier (~> 2.0) @@ -612,7 +612,7 @@ DEPENDENCIES: - SwiftLint (~> 0.50) - WordPress-Editor-iOS (~> 1.19.8) - WordPressAuthenticator (~> 6.1-beta) - - WordPressKit (~> 8.5-beta) + - WordPressKit (~> 8.5) - WordPressShared (~> 2.2) - WordPressUI (~> 1.12.5) - WPMediaPicker (~> 1.8-beta) @@ -880,7 +880,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 WordPress-Editor-iOS: 9eb9f12f21a5209cb837908d81ffe1e31cb27345 WordPressAuthenticator: b0b900696de5129a215adcd1e9ae6eb89da36ac8 - WordPressKit: 31f5a9809b9c732e0da517967d8a94de725c15e6 + WordPressKit: f6943a6e927e9f57bc8793938af1e3a4c3adb614 WordPressShared: 87f3ee89b0a3e83106106f13a8b71605fb8eb6d2 WordPressUI: c5be816f6c7b3392224ac21de9e521e89fa108ac WPMediaPicker: 0d40b8d66b6dfdaa2d6a41e3be51249ff5898775 @@ -895,6 +895,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 -PODFILE CHECKSUM: 083ba0431a3f9b99fbe76f863d853355f9432eae +PODFILE CHECKSUM: a148c9229bd69ca5a13d46500cfecc3c7c2cedbe COCOAPODS: 1.12.1 From 12c7aa56a5e3216afcbcc3a995d46adc4c9384ea Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 11:40:01 -0400 Subject: [PATCH 172/175] Update WPMediaPicker to 1.8.8 --- Podfile | 2 +- Podfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Podfile b/Podfile index 818456a9bc41..a93ed2947f3c 100644 --- a/Podfile +++ b/Podfile @@ -142,7 +142,7 @@ abstract_target 'Apps' do pod 'NSURL+IDN', '~> 0.4' - pod 'WPMediaPicker', '~> 1.8-beta' + pod 'WPMediaPicker', '~> 1.8.8' ## while PR is in review: # pod 'WPMediaPicker', git: 'https://github.com/wordpress-mobile/MediaPicker-iOS.git', branch: '' # pod 'WPMediaPicker', path: '../MediaPicker-iOS' diff --git a/Podfile.lock b/Podfile.lock index bcbc4ab9b1ac..b2202a8d379c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -615,7 +615,7 @@ DEPENDENCIES: - WordPressKit (~> 8.5) - WordPressShared (~> 2.2) - WordPressUI (~> 1.12.5) - - WPMediaPicker (~> 1.8-beta) + - WPMediaPicker (~> 1.8.8) - Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.99.0/third-party-podspecs/Yoga.podspec.json`) - ZendeskSupportSDK (= 5.3.0) - ZIPFoundation (~> 0.9.8) @@ -895,6 +895,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 -PODFILE CHECKSUM: a148c9229bd69ca5a13d46500cfecc3c7c2cedbe +PODFILE CHECKSUM: 52738e8a9294398036caa4be0c073e4996b83424 COCOAPODS: 1.12.1 From 289db2e21ecd62d9b6f41cce0ea467c311219363 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 11:41:40 -0400 Subject: [PATCH 173/175] Update WordPressAuthenticator to 6.2.0 --- Podfile | 2 +- Podfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Podfile b/Podfile index a93ed2947f3c..766ca7036a5e 100644 --- a/Podfile +++ b/Podfile @@ -149,7 +149,7 @@ abstract_target 'Apps' do pod 'Gridicons', '~> 1.1.0' - pod 'WordPressAuthenticator', '~> 6.1-beta' + pod 'WordPressAuthenticator', '~> 6.2.0' # pod 'WordPressAuthenticator', git: 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', branch: '' # pod 'WordPressAuthenticator', git: 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', commit: '' # pod 'WordPressAuthenticator', path: '../WordPressAuthenticator-iOS' diff --git a/Podfile.lock b/Podfile.lock index b2202a8d379c..03c54408dff3 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -611,7 +611,7 @@ DEPENDENCIES: - SVProgressHUD (= 2.2.5) - SwiftLint (~> 0.50) - WordPress-Editor-iOS (~> 1.19.8) - - WordPressAuthenticator (~> 6.1-beta) + - WordPressAuthenticator (~> 6.2.0) - WordPressKit (~> 8.5) - WordPressShared (~> 2.2) - WordPressUI (~> 1.12.5) @@ -895,6 +895,6 @@ SPEC CHECKSUMS: ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 -PODFILE CHECKSUM: 52738e8a9294398036caa4be0c073e4996b83424 +PODFILE CHECKSUM: 463ed7d39926c127d8197fe925fd7d05125f647b COCOAPODS: 1.12.1 From 3d8c016d34629b3dcca79484abb50cdcc9d6cf51 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 11:42:19 -0400 Subject: [PATCH 174/175] Update strings for localization --- .../Resources/en.lproj/Localizable.strings | 136 +++++++++++------- 1 file changed, 81 insertions(+), 55 deletions(-) diff --git a/WordPress/Resources/en.lproj/Localizable.strings b/WordPress/Resources/en.lproj/Localizable.strings index 863ed3860a00..73a4d4d9dbb8 100644 --- a/WordPress/Resources/en.lproj/Localizable.strings +++ b/WordPress/Resources/en.lproj/Localizable.strings @@ -230,11 +230,8 @@ /* translators: accessibility text for blocks with invalid content. %d: localized block title */ "%s block. This block has invalid content" = "%s block. This block has invalid content"; -/* translators: %s: name of the reusable block */ -"%s converted to regular block" = "%s converted to regular block"; - -/* translators: %s: name of the reusable block */ -"%s converted to regular blocks" = "%s converted to regular blocks"; +/* translators: %s: name of the synced block */ +"%s detached" = "%s detached"; /* translators: %s: embed block variant's label e.g: \"Twitter\". */ "%s embed block previews are coming soon" = "%s embed block previews are coming soon"; @@ -770,10 +767,10 @@ "Alt Text" = "Alt Text"; /* No comment provided by engineer. */ -"Alternatively, you can detach and edit these blocks separately by tapping “Convert to regular blocks”." = "Alternatively, you can detach and edit these blocks separately by tapping “Convert to regular blocks”."; +"Alternatively, you can detach and edit these blocks separately by tapping “Detach patterns”." = "Alternatively, you can detach and edit these blocks separately by tapping “Detach patterns”."; /* No comment provided by engineer. */ -"Alternatively, you can detach and edit this block separately by tapping “Convert to regular block”." = "Alternatively, you can detach and edit this block separately by tapping “Convert to regular block”."; +"Alternatively, you can detach and edit this block separately by tapping “Detach pattern”." = "Alternatively, you can detach and edit this block separately by tapping “Detach pattern”."; /* Instruction text to explain to help users type their password instead of using magic link login option. */ "Alternatively, you may enter the password for this account." = "Alternatively, you may enter the password for this account."; @@ -1173,9 +1170,6 @@ /* All Time Stats 'Best views ever' label */ "Best views ever" = "Best views ever"; -/* Text for related post cell preview */ -"Big iPhone/iPad Update Now Available" = "Big iPhone/iPad Update Now Available"; - /* Notice that a page without content has been created */ "Blank page created" = "Blank page created"; @@ -1188,6 +1182,12 @@ /* Title displayed when there are no Blaze campaigns to display. */ "blaze.campaigns.empty.title" = "You have no campaigns"; +/* Text displayed when there is a failure loading Blaze campaigns. */ +"blaze.campaigns.errorMessage" = "There was an error loading campaigns."; + +/* Title for the view when there's an error loading Blaze campiagns. */ +"blaze.campaigns.errorTitle" = "Oops"; + /* Displayed while Blaze campaigns are being loaded. */ "blaze.campaigns.loading.title" = "Loading campaigns..."; @@ -1240,10 +1240,10 @@ "blazeCampaign.status.completed" = "Completed"; /* Short status description */ -"blazeCampaign.status.created" = "Created"; +"blazeCampaign.status.inmoderation" = "In Moderation"; /* Short status description */ -"blazeCampaign.status.inmoderation" = "In Moderation"; +"blazeCampaign.status.processing" = "Processing"; /* Short status description */ "blazeCampaign.status.rejected" = "Rejected"; @@ -2518,6 +2518,9 @@ marketing activities based on your consent and our legitimate interest."; /* Title for the Pages dashboard card. */ "dashboardCard.Pages.title" = "Pages"; +/* Title for the View stats button in the More menu */ +"dashboardCard.stats.viewStats" = "View stats"; + /* Title for a threat that includes the number of database rows affected */ "Database %1$d threats" = "Database %1$d threats"; @@ -3066,10 +3069,10 @@ marketing activities based on your consent and our legitimate interest."; "Edit video" = "Edit video"; /* translators: %s: name of the host app (e.g. WordPress) */ -"Editing reusable blocks is not yet supported on %s for Android" = "Editing reusable blocks is not yet supported on %s for Android"; +"Editing synced patterns is not yet supported on %s for Android" = "Editing synced patterns is not yet supported on %s for Android"; /* translators: %s: name of the host app (e.g. WordPress) */ -"Editing reusable blocks is not yet supported on %s for iOS" = "Editing reusable blocks is not yet supported on %s for iOS"; +"Editing synced patterns is not yet supported on %s for iOS" = "Editing synced patterns is not yet supported on %s for iOS"; /* Editing GIF alert message. */ "Editing this GIF will remove its animation." = "Editing this GIF will remove its animation."; @@ -3490,6 +3493,9 @@ marketing activities based on your consent and our legitimate interest."; /* Option to select the Fastmail app when logging in with magic links */ "Fastmail" = "Fastmail"; +/* Title of screen the displays the details of an advertisement campaign. */ +"feature.blaze.campaignDetails.title" = "Campaign Details"; + /* Name of a feature that allows the user to promote their posts. */ "feature.blaze.title" = "Blaze"; @@ -3752,6 +3758,12 @@ marketing activities based on your consent and our legitimate interest."; Title for the general section in site settings screen */ "General" = "General"; +/* A generic error message for a footer view in a list with pagination */ +"general.pagingFooterView.errorMessage" = "An error occurred"; + +/* A footer retry button */ +"general.pagingFooterView.retry" = "Retry"; + /* Title. A call to action to generate a new invite link. */ "Generate new link" = "Generate new link"; @@ -4182,15 +4194,6 @@ marketing activities based on your consent and our legitimate interest."; /* Footer for the Serve images from our servers setting */ "Improve your site's speed by only loading images visible on the screen. New images will load just before they scroll into view. This prevents viewers from having to download all the images on a page all at once, even ones they can't see." = "Improve your site's speed by only loading images visible on the screen. New images will load just before they scroll into view. This prevents viewers from having to download all the images on a page all at once, even ones they can't see."; -/* Text for related post cell preview */ -"in \"Apps\"" = "in \"Apps\""; - -/* Text for related post cell preview */ -"in \"Mobile\"" = "in \"Mobile\""; - -/* Text for related post cell preview */ -"in \"Upgrade\"" = "in \"Upgrade\""; - /* Explain what is the purpose of the tagline */ "In a few words, explain what this site is about." = "In a few words, explain what this site is about."; @@ -5531,15 +5534,15 @@ Please install the %3$@ to use the app with this site."; /* Title for the card displaying draft posts. */ "my-sites.drafts.card.title" = "Work on a draft post"; -/* The part in the title that should be highlighted. */ -"my-sites.drafts.card.title.hint" = "draft post"; +/* Title for the View all drafts button in the More menu */ +"my-sites.drafts.card.viewAllDrafts" = "View all drafts"; + +/* Title for the View all scheduled drafts button in the More menu */ +"my-sites.scheduled.card.viewAllScheduledPosts" = "View all scheduled posts"; /* Title for the card displaying today's stats. */ "my-sites.stats.card.title" = "Today's Stats"; -/* The part of the title that needs to be emphasized */ -"my-sites.stats.card.title.hint" = "Stats"; - /* Accessibility label for the Email text field. Header for a comment author's name, shown when editing a comment. Name text field placeholder @@ -6074,7 +6077,6 @@ Please install the %3$@ to use the app with this site."; /* An informal exclaimation that means `something went wrong`. Title for the view when there's an error loading a comment. Title for the view when there's an error loading Activity Log - Title for the view when there's an error loading Blaze campiagns. Title for the view when there's an error loading blogging prompts. Title for the view when there's an error loading scan status Title for the view when there's an error loading the history @@ -6718,6 +6720,15 @@ Please install the %3$@ to use the app with this site."; /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "Twitter Auto-Sharing Is No Longer Available"; +/* Title for the button to subscribe to Jetpack Social on the remaining shares view */ +"postsettings.social.remainingshares.subscribe" = "Subscribe now to share more"; + +/* Beginning text of the remaining social shares a user has left. %1$d is their current remaining shares. %2$d is their share limit. This text is combined with ' in the next 30 days' if there is no warning displayed. */ +"postsettings.social.remainingshares.text.format" = "%1$d/%2$d social shares remaining"; + +/* The second half of the remaining social shares a user has. This is only displayed when there is no social limit warning. */ +"postsettings.social.remainingshares.text.part" = " in the next 30 days"; + /* Subtitle for placeholder in Tenor picker. `The company name 'Tenor' should always be written as it is. */ "Powered by Tenor" = "Powered by Tenor"; @@ -6734,7 +6745,6 @@ Please install the %3$@ to use the app with this site."; "Preparing..." = "Preparing..."; /* Displays the Post Preview Interface - Section title for related posts section preview Title for button to preview a selected layout Title for screen to preview a selected homepage design. Title for screen to preview a static content. */ @@ -7073,13 +7083,50 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Displayed in the Notifications Tab as a message, when the Unread Filter shows no notifications */ "Reignite the conversation: write a new post." = "Reignite the conversation: write a new post."; -/* Label for Related Post header preview - Label for selecting the related posts options - Title for screen that allows configuration of your blog/site related posts settings. */ +/* Label for selecting the related posts options */ "Related Posts" = "Related Posts"; /* Information of what related post are and how they are presented */ -"Related Posts displays relevant content from your site below your posts" = "Related Posts displays relevant content from your site below your posts"; +"relatedPostsSettings.optionsFooter" = "Related Posts displays relevant content from your site below your posts"; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview1.details" = "in \"Mobile\""; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview1.title" = "Big iPhone/iPad Update Now Available"; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview2.details" = "in \"Apps\""; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview2.title" = "The WordPress for Android App Gets a Big Facelift"; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview3.details" = "in \"Upgrade\""; + +/* Text for related post cell preview */ +"relatedPostsSettings.preview3.title" = "Upgrade Focus: VideoPress For Weddings"; + +/* Section title for related posts section preview */ +"relatedPostsSettings.previewsHeaders" = "Preview"; + +/* Label for Related Post header preview */ +"relatedPostsSettings.relatedPostsHeader" = "Related Posts"; + +/* Message to show when setting save failed */ +"relatedPostsSettings.settingsUpdateFailed" = "Settings update failed"; + +/* Label for configuration switch to show/hide the header for the related posts section */ +"relatedPostsSettings.showHeader" = "Show Header"; + +/* Label for configuration switch to enable/disable related posts */ +"relatedPostsSettings.showRelatedPosts" = "Show Related Posts"; + +/* Label for configuration switch to show/hide images thumbnail for the related posts */ +"relatedPostsSettings.showThumbnail" = "Show Images"; + +/* Title for screen that allows configuration of your blog/site related posts settings. */ +"relatedPostsSettings.title" = "Related Posts"; /* Button title on the blogging prompt's feature introduction view to set a reminder. */ "Remind me" = "Remind me"; @@ -7328,9 +7375,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Share extension error dialog cancel button text */ "Return to post" = "Return to post"; -/* No comment provided by engineer. */ -"Reusable" = "Reusable"; - /* Cancels a pending Email Change */ "Revert Pending Change" = "Revert Pending Change"; @@ -7865,12 +7909,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Share extension error dialog title. */ "Sharing Error" = "Sharing Error"; -/* Label for configuration switch to show/hide the header for the related posts section */ -"Show Header" = "Show Header"; - -/* Label for configuration switch to show/hide images thumbnail for the related posts */ -"Show Images" = "Show Images"; - /* Title for the `show like button` setting */ "Show Like button" = "Show Like button"; @@ -7890,9 +7928,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Title for the `show reblog button` setting */ "Show Reblog button" = "Show Reblog button"; -/* Label for configuration switch to enable/disable related posts */ -"Show Related Posts" = "Show Related Posts"; - /* Alert title picking theme type to browse */ "Show themes:" = "Show themes:"; @@ -8923,9 +8958,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Message shown when a video failed to load while trying to add it to the Media library. */ "The video could not be added to the Media Library." = "The video could not be added to the Media Library."; -/* Text for related post cell preview */ -"The WordPress for Android App Gets a Big Facelift" = "The WordPress for Android App Gets a Big Facelift"; - /* Example post title used in the login prologue screens. This is a post about football fans. */ "The World's Best Fans" = "The World's Best Fans"; @@ -9019,9 +9051,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Text displayed when there is a failure loading the activity feed */ "There was an error loading activities" = "There was an error loading activities"; -/* Text displayed when there is a failure loading Blaze campaigns. */ -"There was an error loading campaigns." = "There was an error loading campaigns."; - /* Text displayed when there is a failure loading the plan list */ "There was an error loading plans" = "There was an error loading plans"; @@ -9759,9 +9788,6 @@ Example: given a notice format "Following %@" and empty site name, this will be /* Text displayed in HUD while a draft or scheduled post is being updated. */ "Updating..." = "Updating..."; -/* Text for related post cell preview */ -"Upgrade Focus: VideoPress For Weddings" = "Upgrade Focus: VideoPress For Weddings"; - /* No comment provided by engineer. */ "Upgrade your plan to upload audio" = "Upgrade your plan to upload audio"; From 928de0faf1ff8a8e94d64ca18e7925232e815e32 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Mon, 10 Jul 2023 12:45:47 -0400 Subject: [PATCH 175/175] Remove Jetpack-only release notes from WordPress 22.8 release notes --- WordPress/Resources/release_notes.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/Resources/release_notes.txt b/WordPress/Resources/release_notes.txt index 42816e59d62b..5622d406fc18 100644 --- a/WordPress/Resources/release_notes.txt +++ b/WordPress/Resources/release_notes.txt @@ -3,6 +3,5 @@ * [**] [internal] Make sure synced posts are saved before calling completion block. [#20960] * [**] [internal] Fix observing Quick Start notifications. [#20997] * [**] [internal] Fixed an issue that was causing a memory leak in the domain selection flow. [#20813] -* [*] [Jetpack-only] Block editor: Rename "Reusable blocks" to "Synced patterns", aligning with the web editor. [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5885] * [**] [internal] Block editor: Fix a crash related to Reanimated when closing the editor [https://github.com/wordpress-mobile/gutenberg-mobile/pull/5938]