@@ -102,6 +102,9 @@ final class StorePickerViewController: UIViewController {
102102 }
103103 }
104104
105+ /// Create store button.
106+ @IBOutlet private weak var createStoreButton : FancyAnimatedButton !
107+
105108 /// New To Woo button
106109 ///
107110 @IBOutlet var newToWooButton : UIButton ! {
@@ -157,9 +160,13 @@ final class StorePickerViewController: UIViewController {
157160 self ? . restartAuthentication ( )
158161 }
159162
163+ @Published private var possibleSiteURLsFromStoreCreation : Set < String > = [ ]
164+ private var possibleSiteURLsFromStoreCreationSubscription : AnyCancellable ?
165+
160166 private let appleIDCredentialChecker : AppleIDCredentialCheckerProtocol
161167 private let stores : StoresManager
162168 private let featureFlagService : FeatureFlagService
169+ private let isStoreCreationEnabled : Bool
163170
164171 init ( configuration: StorePickerConfiguration ,
165172 appleIDCredentialChecker: AppleIDCredentialCheckerProtocol = AppleIDCredentialChecker ( ) ,
@@ -170,6 +177,7 @@ final class StorePickerViewController: UIViewController {
170177 self . stores = stores
171178 self . featureFlagService = featureFlagService
172179 self . viewModel = StorePickerViewModel ( configuration: configuration)
180+ self . isStoreCreationEnabled = featureFlagService. isFeatureFlagEnabled ( . storeCreationMVP)
173181 super. init ( nibName: Self . nibName, bundle: nil )
174182 }
175183
@@ -185,8 +193,10 @@ final class StorePickerViewController: UIViewController {
185193 setupMainView ( )
186194 setupAccountHeader ( )
187195 setupTableView ( )
196+ setupCreateStoreButton ( )
188197 refreshResults ( )
189198 observeStateChange ( )
199+ observeSiteURLsFromStoreCreation ( )
190200
191201 switch configuration {
192202 case . login:
@@ -276,6 +286,17 @@ private extension StorePickerViewController {
276286 }
277287 }
278288
289+ func setupCreateStoreButton( ) {
290+ createStoreButton. isHidden = isStoreCreationEnabled == false
291+ createStoreButton. isPrimary = false
292+ createStoreButton. backgroundColor = . clear
293+ createStoreButton. titleFont = StyleManager . actionButtonTitleFont
294+ createStoreButton. setTitle ( Localization . createStore, for: . normal)
295+ createStoreButton. on ( . touchUpInside) { [ weak self] _ in
296+ self ? . createStoreButtonPressed ( )
297+ }
298+ }
299+
279300 func refreshResults( ) {
280301 viewModel. refreshSites ( currentlySelectedSiteID: currentlySelectedSite? . siteID)
281302 viewModel. trackScreenView ( )
@@ -296,6 +317,28 @@ private extension StorePickerViewController {
296317 func presentHelp( ) {
297318 ServiceLocator . authenticationManager. presentSupport ( from: self , screen: . storePicker)
298319 }
320+
321+ func observeSiteURLsFromStoreCreation( ) {
322+ possibleSiteURLsFromStoreCreationSubscription = $possibleSiteURLsFromStoreCreation
323+ . filter { $0. isEmpty == false }
324+ . removeDuplicates ( )
325+ // There are usually three URLs in the webview that return a site URL - two with `*.wordpress.com` and the other the final URL.
326+ . debounce ( for: . seconds( 5 ) , scheduler: DispatchQueue . main)
327+ . asyncMap { [ weak self] possibleSiteURLs -> Site ? in
328+ // Waits for 5 seconds before syncing sites every time.
329+ try await Task . sleep ( nanoseconds: 5_000_000_000 )
330+ return try await self ? . syncSites ( forSiteThatMatchesPossibleURLs: possibleSiteURLs)
331+ }
332+ // Retries 10 times with 5 seconds pause in between to wait for the newly created site to be available as a Jetpack site
333+ // in the WPCOM `/me/sites` response.
334+ . retry ( 10 )
335+ . replaceError ( with: nil )
336+ . receive ( on: DispatchQueue . main)
337+ . sink { [ weak self] site in
338+ guard let self, let site else { return }
339+ self . continueWithSelectedSite ( site: site)
340+ }
341+ }
299342}
300343
301344
@@ -540,7 +583,7 @@ extension StorePickerViewController: UIViewControllerTransitioningDelegate {
540583
541584// MARK: - Action Handlers
542585//
543- extension StorePickerViewController {
586+ private extension StorePickerViewController {
544587
545588 /// Proceeds with the Login Flow.
546589 ///
@@ -583,6 +626,67 @@ extension StorePickerViewController {
583626 @IBAction func secondaryActionWasPressed( ) {
584627 restartAuthentication ( )
585628 }
629+
630+ func createStoreButtonPressed( ) {
631+ // TODO-7879: analytics
632+
633+ let viewModel = StoreCreationWebViewModel { [ weak self] result in
634+ self ? . handleStoreCreationResult ( result)
635+ }
636+ possibleSiteURLsFromStoreCreation = [ ]
637+ let webViewController = AuthenticatedWebViewController ( viewModel: viewModel)
638+ webViewController. addCloseNavigationBarButton ( target: self , action: #selector( handleStoreCreationCloseAction) )
639+ let navigationController = WooNavigationController ( rootViewController: webViewController)
640+ // Disables interactive dismissal of the store creation modal.
641+ navigationController. isModalInPresentation = true
642+ present ( navigationController, animated: true )
643+ }
644+
645+ @objc func handleStoreCreationCloseAction( ) {
646+ // TODO-7879: show a confirmation alert before closing the store creation view
647+ // TODO-7879: analytics
648+ dismiss ( animated: true )
649+ }
650+
651+ func handleStoreCreationResult( _ result: Result < String , Error > ) {
652+ switch result {
653+ case . success( let siteURL) :
654+ // TODO-7879: analytics
655+
656+ // There could be multiple site URLs from the completion URL in the webview, and only one
657+ // of them matches the final site URL from WPCOM `/me/sites` endpoint.
658+ possibleSiteURLsFromStoreCreation. insert ( siteURL)
659+ case . failure( let error) :
660+ // TODO-7879: analytics
661+ DDLogError ( " Store creation error: \( error) " )
662+ }
663+ }
664+
665+ @MainActor
666+ func syncSites( forSiteThatMatchesPossibleURLs possibleURLs: Set < String > ) async throws -> Site {
667+ return try await withCheckedThrowingContinuation { [ weak self] continuation in
668+ viewModel. refreshSites ( currentlySelectedSiteID: nil ) { [ weak self] in
669+ guard let self else { return }
670+ // The newly created site often has `isJetpackThePluginInstalled=false` initially,
671+ // which results in a JCP site.
672+ // In this case, we want to retry sites syncing.
673+ guard let site = self . viewModel. site ( thatMatchesPossibleURLs: possibleURLs) else {
674+ return continuation. resume ( throwing: StoreCreationError . newSiteUnavailable)
675+ }
676+ guard site. isJetpackConnected && site. isJetpackThePluginInstalled else {
677+ return continuation. resume ( throwing: StoreCreationError . newSiteIsNotJetpackSite)
678+ }
679+ continuation. resume ( returning: site)
680+ }
681+ }
682+ }
683+
684+ func continueWithSelectedSite( site: Site ) {
685+ currentlySelectedSite = site
686+ dismiss ( animated: true ) { [ weak self] in
687+ self ? . checkRoleEligibility ( for: site)
688+ }
689+ }
586690}
587691
588692
@@ -759,6 +863,8 @@ private extension StorePickerViewController {
759863 comment: " Button to input a site address in store picker when there are no stores found " )
760864 static let newToWooCommerce = NSLocalizedString ( " New to WooCommerce? " ,
761865 comment: " Title of button on the site picker screen for users who are new to WooCommerce. " )
866+ static let createStore = NSLocalizedString ( " Create a new store " ,
867+ comment: " Button to create a new store from the store picker " )
762868 }
763869}
764870
0 commit comments