diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index ec9570c7d385..7abcb978cf40 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -20,6 +20,7 @@ * [**] Block Editor: Media uploads that failed due to lack of internet connectivity automatically retry once a connection is re-established [https://github.com/wordpress-mobile/WordPress-iOS/pull/22238] * [**] Block Editor: Manually retrying a single failed media upload will retry all failed media uploads in a post [https://github.com/wordpress-mobile/WordPress-iOS/pull/22240] * [*] [internal] Drop iOS 14 Support: Remove deprecated UIDocumentPickerViewController API [#22286] +* [**] [internal] Site creation is re-enabled for WordPress users with no sites. [#22415] * [***] [internal] Refactor WP.com sign in API requests [#22421] * [*] Add In-App Feedback Prompt. [#22050] * [**] Disabled the ability of creating new Story posts. [#22453] diff --git a/WordPress/Classes/System/RootViewCoordinator.swift b/WordPress/Classes/System/RootViewCoordinator.swift index 0851fc282aec..7e4baeb8a6d6 100644 --- a/WordPress/Classes/System/RootViewCoordinator.swift +++ b/WordPress/Classes/System/RootViewCoordinator.swift @@ -54,7 +54,8 @@ class RootViewCoordinator { private var featureFlagStore: RemoteFeatureFlagStore private var windowManager: WindowManager? private let wordPressAuthenticator: WordPressAuthenticatorProtocol.Type - + var isSiteCreationActive = false + var isFullScreenOverlayBeingDisplayed = false // MARK: Initializer init(featureFlagStore: RemoteFeatureFlagStore, @@ -153,6 +154,7 @@ class RootViewCoordinator { return } + isFullScreenOverlayBeingDisplayed = true let viewController = BlurredEmptyViewController() windowManager.displayOverlayingWindow(with: viewController) @@ -164,6 +166,7 @@ class RootViewCoordinator { blog: blog, onWillDismiss: { viewController.removeBlurView() + self.isFullScreenOverlayBeingDisplayed = false }, onDidDismiss: { windowManager.clearOverlayingWindow() }) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListViewController+SiteCreation.swift b/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListViewController+SiteCreation.swift index 328a1ad45978..fbbf4ec521ce 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListViewController+SiteCreation.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog List/BlogListViewController+SiteCreation.swift @@ -12,6 +12,7 @@ extension BlogListViewController { guard let wizard = wizardLauncher.ui else { return } + RootViewCoordinator.shared.isSiteCreationActive = true self.present(wizard, animated: true) SiteCreationAnalyticsHelper.trackSiteCreationAccessed(source: source) }) diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteOverlaysCoordinator.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteOverlaysCoordinator.swift index f721a307a223..913b3a334761 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteOverlaysCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteOverlaysCoordinator.swift @@ -28,6 +28,10 @@ final class MySiteOverlaysCoordinator { /// /// - Parameter viewController: The UIViewController instance on which to potentially present an overlay. @MainActor func presentOverlayIfNeeded(in viewController: UIViewController) async { + guard !RootViewCoordinator.shared.isSiteCreationActive && !RootViewCoordinator.shared.isFullScreenOverlayBeingDisplayed else { + return + } + if let complianceCoordinator, await complianceCoordinator.presentIfNeeded() { self.complianceCoordinator = nil } else if let inAppFeedbackCoordinator, inAppFeedbackCoordinator.presentIfNeeded(in: viewController) { diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift index 19f38010535c..5f0c21e29884 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift @@ -130,7 +130,10 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite let configuration = AddNewSiteConfiguration( canCreateWPComSite: viewModel.defaultAccount != nil, canAddSelfHostedSite: AppConfiguration.showAddSelfHostedSiteButton, - launchSiteCreation: { [weak self] in self?.launchSiteCreationFromNoSites() }, + launchSiteCreation: { + [weak self] in self?.launchSiteCreationFromNoSites() + RootViewCoordinator.shared.isSiteCreationActive = true + }, launchLoginForSelfHostedSite: { [weak self] in self?.launchLoginForSelfHostedSite() } ) let noSiteView = NoSitesView( @@ -198,6 +201,7 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite Task { @MainActor in await overlaysCoordinator.presentOverlayIfNeeded(in: self) } + } override func viewDidLayoutSubviews() { @@ -591,6 +595,7 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite guard let wizard = wizardLauncher.ui else { return } + RootViewCoordinator.shared.isSiteCreationActive = true self.present(wizard, animated: true) SiteCreationAnalyticsHelper.trackSiteCreationAccessed(source: source) }) @@ -924,7 +929,7 @@ extension MySiteViewController: BlogDetailsPresentationDelegate { private extension MySiteViewController { @objc func displayOverlayIfNeeded() { - if isViewOnScreen(), !willDisplayPostSignupFlow { + if isViewOnScreen() && !willDisplayPostSignupFlow && !RootViewCoordinator.shared.isSiteCreationActive { let didReloadUI = RootViewCoordinator.shared.reloadUIIfNeeded(blog: self.blog) if !didReloadUI { let phase = JetpackFeaturesRemovalCoordinator.generalPhase() diff --git a/WordPress/Classes/ViewRelated/Blog/QuickStartPromptViewController.xib b/WordPress/Classes/ViewRelated/Blog/QuickStartPromptViewController.xib index 333c82332a46..85af8e0edf75 100644 --- a/WordPress/Classes/ViewRelated/Blog/QuickStartPromptViewController.xib +++ b/WordPress/Classes/ViewRelated/Blog/QuickStartPromptViewController.xib @@ -1,9 +1,9 @@ - + - + @@ -30,7 +30,7 @@ - + diff --git a/WordPress/Classes/ViewRelated/Domains/DomainSelectionViewController.swift b/WordPress/Classes/ViewRelated/Domains/DomainSelectionViewController.swift index 53c2ef1e6021..4de1e41745bb 100644 --- a/WordPress/Classes/ViewRelated/Domains/DomainSelectionViewController.swift +++ b/WordPress/Classes/ViewRelated/Domains/DomainSelectionViewController.swift @@ -281,7 +281,7 @@ final class DomainSelectionViewController: CollapsableHeaderViewController { let type: DomainsServiceRemote.DomainSuggestionType switch domainSelectionType { case .siteCreation: - type = RemoteFeatureFlag.plansInSiteCreation.enabled() ? .freeAndPaid : .wordPressDotComAndDotBlogSubdomains + type = siteCreationType default: if coordinator?.site?.hasBloggerPlan == true { type = .allowlistedTopLevelDomains(["blog"]) @@ -297,6 +297,12 @@ final class DomainSelectionViewController: CollapsableHeaderViewController { } } + private var siteCreationType: DomainsServiceRemote.DomainSuggestionType { + (RemoteFeatureFlag.plansInSiteCreation.enabled() && AppConfiguration.isJetpack) ? + .freeAndPaid : + .wordPressDotComAndDotBlogSubdomains + } + private func handleResult(_ results: Result) { updateIcon(isLoading: false) switch results { diff --git a/WordPress/Classes/ViewRelated/Jetpack/Branding/Coordinator/JetpackFeaturesRemovalCoordinator.swift b/WordPress/Classes/ViewRelated/Jetpack/Branding/Coordinator/JetpackFeaturesRemovalCoordinator.swift index e12579f1c0aa..d5ea6ea20bbc 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Branding/Coordinator/JetpackFeaturesRemovalCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Branding/Coordinator/JetpackFeaturesRemovalCoordinator.swift @@ -129,7 +129,7 @@ class JetpackFeaturesRemovalCoordinator: NSObject { if RemoteFeatureFlag.jetpackFeaturesRemovalPhaseNewUsers.enabled(using: featureFlagStore) || RemoteFeatureFlag.jetpackFeaturesRemovalPhaseFour.enabled(using: featureFlagStore) || RemoteFeatureFlag.jetpackFeaturesRemovalStaticPosters.enabled(using: featureFlagStore) { - return .two + return Self.hasBlog() ? .two : .normal } if RemoteFeatureFlag.jetpackFeaturesRemovalPhaseThree.enabled(using: featureFlagStore) || RemoteFeatureFlag.jetpackFeaturesRemovalPhaseTwo.enabled(using: featureFlagStore) @@ -288,12 +288,16 @@ class JetpackFeaturesRemovalCoordinator: NSObject { presentOverlay(navigationViewController: navigationViewController, in: viewController) } - private static func presentOverlay(navigationViewController: UINavigationController, - in viewController: UIViewController, - fullScreen: Bool = false) { + static func presentOverlay(navigationViewController: UINavigationController, + in viewController: UIViewController, + fullScreen: Bool = false) { let shouldUseFormSheet = WPDeviceIdentification.isiPad() || !fullScreen navigationViewController.modalPresentationStyle = shouldUseFormSheet ? .formSheet : .fullScreen viewController.present(navigationViewController, animated: true) } + + private static func hasBlog() -> Bool { + Blog.count(in: ContextManager.sharedInstance().mainContext) > 0 + } } diff --git a/WordPress/Classes/ViewRelated/NUX/Post Signup Interstitial/PostSignUpInterstitialViewController.swift b/WordPress/Classes/ViewRelated/NUX/Post Signup Interstitial/PostSignUpInterstitialViewController.swift index 2377f5300fe8..13cc298b8f19 100644 --- a/WordPress/Classes/ViewRelated/NUX/Post Signup Interstitial/PostSignUpInterstitialViewController.swift +++ b/WordPress/Classes/ViewRelated/NUX/Post Signup Interstitial/PostSignUpInterstitialViewController.swift @@ -87,8 +87,11 @@ class PostSignUpInterstitialViewController: UIViewController { WPAnalytics.track(.welcomeNoSitesInterstitialButtonTapped, withProperties: ["button": "create_new_site"]) }) let source = "post_signup" + let siteCreationPhase = JetpackFeaturesRemovalCoordinator.siteCreationPhase() + RootViewCoordinator.shared.isSiteCreationActive = true + JetpackFeaturesRemovalCoordinator.presentSiteCreationOverlayIfNeeded(in: self, source: source, onDidDismiss: { - guard JetpackFeaturesRemovalCoordinator.siteCreationPhase() != .two else { + guard siteCreationPhase != .two else { return } diff --git a/WordPress/Classes/ViewRelated/NUX/WordPressAuthenticationManager.swift b/WordPress/Classes/ViewRelated/NUX/WordPressAuthenticationManager.swift index 43222ce37f45..7c75d499ca3d 100644 --- a/WordPress/Classes/ViewRelated/NUX/WordPressAuthenticationManager.swift +++ b/WordPress/Classes/ViewRelated/NUX/WordPressAuthenticationManager.swift @@ -373,6 +373,7 @@ extension WordPressAuthenticationManager: WordPressAuthenticatorDelegate { return } + RootViewCoordinator.shared.isSiteCreationActive = true navigationController.present(wizard, animated: true) SiteCreationAnalyticsHelper.trackSiteCreationAccessed(source: source) }) diff --git a/WordPress/Classes/ViewRelated/Site Creation/Final Assembly/SiteAssemblyWizardContent.swift b/WordPress/Classes/ViewRelated/Site Creation/Final Assembly/SiteAssemblyWizardContent.swift index 7e330eb60fa0..885fe337b52b 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Final Assembly/SiteAssemblyWizardContent.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Final Assembly/SiteAssemblyWizardContent.swift @@ -229,7 +229,7 @@ final class SiteAssemblyWizardContent: UIViewController { if configuration.dismissalActionHandler == nil { configuration.dismissalActionHandler = { [weak self] in - guard let self = self else { + guard let self else { return } self.dismissTapped() @@ -268,6 +268,7 @@ private extension SiteAssemblyWizardContent { func dismissTapped(viaDone: Bool = false, completion: (() -> Void)? = nil) { // TODO : using viaDone, capture analytics event via #10335 + RootViewCoordinator.shared.isSiteCreationActive = false navigationController?.dismiss(animated: true, completion: completion) } @@ -302,13 +303,16 @@ extension SiteAssemblyWizardContent: NUXButtonViewControllerDelegate { return } + RootViewCoordinator.shared.isSiteCreationActive = false + RootViewCoordinator.shared.reloadUIIfNeeded(blog: blog) + dismissTapped(viaDone: true) { [blog, weak self] in RootViewCoordinator.sharedPresenter.showBlogDetails(for: blog) - // present quick start, and mark site title as complete if they already selected one - guard let self = self else { + guard let self = self, AppConfiguration.isJetpack else { return } + let completedSteps: [QuickStartTour] = self.siteCreator.hasSiteTitle ? [QuickStartSiteTitleTour(blog: blog)] : [] self.showQuickStartPrompt(for: blog, completedSteps: completedSteps) } @@ -319,6 +323,11 @@ extension SiteAssemblyWizardContent: NUXButtonViewControllerDelegate { return } + // Disable the prompt for WordPress when the blog has no domains. + guard AppConfiguration.isJetpack || isDashboardEnabled(for: blog) else { + return + } + let rootViewController = RootViewCoordinator.sharedPresenter.rootViewController let quickstartPrompt = QuickStartPromptViewController(blog: blog) quickstartPrompt.onDismiss = { blog, showQuickStart in @@ -328,4 +337,8 @@ extension SiteAssemblyWizardContent: NUXButtonViewControllerDelegate { } rootViewController.present(quickstartPrompt, animated: true) } + + private func isDashboardEnabled(for blog: Blog) -> Bool { + return JetpackFeaturesRemovalCoordinator.jetpackFeaturesEnabled() && blog.isAccessibleThroughWPCom() + } } diff --git a/WordPress/Classes/ViewRelated/Site Creation/Site Intent/SiteIntentViewController.swift b/WordPress/Classes/ViewRelated/Site Creation/Site Intent/SiteIntentViewController.swift index 8f55c0937952..3674585b01d5 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Site Intent/SiteIntentViewController.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Site Intent/SiteIntentViewController.swift @@ -110,6 +110,8 @@ class SiteIntentViewController: CollapsableHeaderViewController { @objc private func closeButtonTapped(_ sender: Any) { SiteCreationAnalyticsHelper.trackSiteIntentCanceled() + RootViewCoordinator.shared.isSiteCreationActive = false + RootViewCoordinator.shared.reloadUIIfNeeded(blog: nil) dismiss(animated: true) } } diff --git a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreationWizardLauncher.swift b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreationWizardLauncher.swift index 2b484d7c2d28..b3e098ee1cdc 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreationWizardLauncher.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreationWizardLauncher.swift @@ -7,7 +7,7 @@ final class SiteCreationWizardLauncher { }() let steps: [SiteCreationStep] = { - if RemoteFeatureFlag.plansInSiteCreation.enabled() { + if RemoteFeatureFlag.plansInSiteCreation.enabled() && AppConfiguration.isJetpack { return [ .intent, .design, diff --git a/WordPress/WordPressTest/JetpackFeaturesRemovalCoordinatorTests.swift b/WordPress/WordPressTest/JetpackFeaturesRemovalCoordinatorTests.swift index 9db30af93c62..826aa875acbc 100644 --- a/WordPress/WordPressTest/JetpackFeaturesRemovalCoordinatorTests.swift +++ b/WordPress/WordPressTest/JetpackFeaturesRemovalCoordinatorTests.swift @@ -8,7 +8,7 @@ final class JetpackFeaturesRemovalCoordinatorTests: CoreDataTestCase { override func setUp() { contextManager.useAsSharedInstance(untilTestFinished: self) mockUserDefaults = InMemoryUserDefaults() - let account = AccountBuilder(contextManager.mainContext).build() + let account = AccountBuilder(contextManager.mainContext).with(username: "test-account-JetpackFeaturesRemovalCoordinatorTests").build() UserSettings.defaultDotComUUID = account.uuid } @@ -250,7 +250,7 @@ final class JetpackFeaturesRemovalCoordinatorTests: CoreDataTestCase { XCTAssertEqual(phase, .one) } - func testSiteCreationPhaseTwo() { + func testSiteCreationPhaseTwo() throws { // Given let store = RemoteFeatureFlagStore(persistenceStore: mockUserDefaults) @@ -258,8 +258,9 @@ final class JetpackFeaturesRemovalCoordinatorTests: CoreDataTestCase { var flags = generateFlags(phaseOne: false, phaseTwo: false, phaseThree: false, phaseFour: true, phaseNewUsers: false, phaseSelfHosted: false) let remote = MockFeatureFlagRemote(flags: flags) store.update(using: remote, waitOn: self) + let blog = BlogBuilder(mainContext).build() + try mainContext.save() var phase = JetpackFeaturesRemovalCoordinator.siteCreationPhase(featureFlagStore: store) - // Then XCTAssertEqual(phase, .two) @@ -273,7 +274,29 @@ final class JetpackFeaturesRemovalCoordinatorTests: CoreDataTestCase { XCTAssertEqual(phase, .two) } - func testSiteCreationPhaseTwoPrecedence() { + func testSiteCreationPhaseNormalWhenUserHasNoBlog() throws { + // Given + let store = RemoteFeatureFlagStore(persistenceStore: mockUserDefaults) + + // When + var flags = generateFlags(phaseOne: false, phaseTwo: false, phaseThree: false, phaseFour: true, phaseNewUsers: false, phaseSelfHosted: false) + let remote = MockFeatureFlagRemote(flags: flags) + store.update(using: remote, waitOn: self) + var phase = JetpackFeaturesRemovalCoordinator.siteCreationPhase(featureFlagStore: store) + // Then + XCTAssertEqual(phase, .normal) + + // When + flags = generateFlags(phaseOne: false, phaseTwo: false, phaseThree: false, phaseFour: false, phaseNewUsers: true, phaseSelfHosted: false) + remote.flags = flags + store.update(using: remote, waitOn: self) + phase = JetpackFeaturesRemovalCoordinator.siteCreationPhase(featureFlagStore: store) + + // Then + XCTAssertEqual(phase, .normal) + } + + func testSiteCreationPhaseTwoPrecedence() throws { // Given let store = RemoteFeatureFlagStore(persistenceStore: mockUserDefaults) @@ -281,6 +304,8 @@ final class JetpackFeaturesRemovalCoordinatorTests: CoreDataTestCase { var flags = generateFlags(phaseOne: true, phaseTwo: true, phaseThree: true, phaseFour: true, phaseNewUsers: false, phaseSelfHosted: false) let remote = MockFeatureFlagRemote(flags: flags) store.update(using: remote, waitOn: self) + let blog = BlogBuilder(mainContext).build() + try mainContext.save() var phase = JetpackFeaturesRemovalCoordinator.siteCreationPhase(featureFlagStore: store) // Then