diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt index ca0cabccab8e..b61fdf423088 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt @@ -131,6 +131,8 @@ enum class AnalyticsEvent(val siteless: Boolean = false) { SITE_PICKER_SITE_DISCOVERY(siteless = true), SITE_PICKER_JETPACK_TIMEOUT_ERROR_SHOWN(siteless = true), SITE_PICKER_JETPACK_TIMEOUT_CONTACT_SUPPORT_CLICKED(siteless = true), + SITE_PICKER_CREATE_SITE_TAPPED(siteless = true), + LOGIN_WOOCOMMERCE_SITE_CREATED(siteless = true), // -- Dashboard DASHBOARD_PULLED_TO_REFRESH, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/common/wpcomwebview/WPComWebViewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/common/wpcomwebview/WPComWebViewFragment.kt index 2f6bf61b1f45..067364c7b3d1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/common/wpcomwebview/WPComWebViewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/common/wpcomwebview/WPComWebViewFragment.kt @@ -10,10 +10,13 @@ import androidx.navigation.fragment.navArgs import com.woocommerce.android.R import com.woocommerce.android.databinding.FragmentWpcomWebviewBinding import com.woocommerce.android.extensions.navigateBackWithNotice +import com.woocommerce.android.extensions.navigateBackWithResult import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewFragment.UrlComparisonMode.EQUALITY import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewFragment.UrlComparisonMode.PARTIAL import com.woocommerce.android.ui.main.MainActivity.Companion.BackPressListener +import com.woocommerce.android.util.WooLog +import com.woocommerce.android.util.WooLog.T import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.fluxc.network.UserAgent import javax.inject.Inject @@ -28,11 +31,16 @@ import javax.inject.Inject class WPComWebViewFragment : BaseFragment(R.layout.fragment_wpcom_webview), UrlInterceptor, BackPressListener { companion object { const val WEBVIEW_RESULT = "webview-result" + const val WEBVIEW_RESULT_WITH_URL = "webview-result-with-url" const val WEBVIEW_DISMISSED = "webview-dismissed" + const val WEBVIEW_STORE_CHECKOUT_STRING = "checkout/thank-you/" + const val WEBVIEW_STORE_URL_KEY = "store-url-key" } private val webViewClient by lazy { WPComWebViewClient(this) } private val navArgs: WPComWebViewFragmentArgs by navArgs() + private var siteUrls = ArrayList() + private var canNavigate = true @Inject lateinit var wpcomWebViewAuthenticator: WPComWebViewAuthenticator @@ -52,20 +60,47 @@ class WPComWebViewFragment : BaseFragment(R.layout.fragment_wpcom_webview), UrlI } } this.settings.javaScriptEnabled = true + this.settings.domStorageEnabled = true settings.userAgentString = userAgent.userAgent } + siteUrls = savedInstanceState?.getStringArrayList(WEBVIEW_STORE_URL_KEY) ?: siteUrls + wpcomWebViewAuthenticator.authenticateAndLoadUrl(binding.webView, navArgs.urlToLoad) } + override fun onSaveInstanceState(outState: Bundle) { + outState.putStringArrayList(WEBVIEW_STORE_URL_KEY, siteUrls) + super.onSaveInstanceState(outState) + } + override fun onLoadUrl(url: String) { fun String.matchesUrl(url: String) = when (navArgs.urlComparisonMode) { PARTIAL -> url.contains(this, ignoreCase = true) EQUALITY -> equals(url, ignoreCase = true) } - if (isAdded && navArgs.urlToTriggerExit?.matchesUrl(url) == true) { - navigateBackWithNotice(WEBVIEW_RESULT) + extractSiteUrl(url) + + if (isAdded && navArgs.urlToTriggerExit?.matchesUrl(url) == true && canNavigate) { + canNavigate = false + if (siteUrls.isEmpty()) { + navigateBackWithNotice(WEBVIEW_RESULT) + } else { + navigateBackWithResult(WEBVIEW_RESULT_WITH_URL, siteUrls) + WooLog.d(T.LOGIN, "Site creation URLs: ${siteUrls.joinToString()}") + } + } + } + + private fun extractSiteUrl(url: String) { + "$WEBVIEW_STORE_CHECKOUT_STRING.+/".toRegex().find(url)?.range?.let { range -> + val start = range.first + WEBVIEW_STORE_CHECKOUT_STRING.length + val end = range.last + val siteUrl = url.substring(start, end) + if (!siteUrls.contains(siteUrl)) { + siteUrls.add(siteUrl) + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/LoginPrologueFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/LoginPrologueFragment.kt index 67b3b2bad01d..3b9b685eb653 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/LoginPrologueFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/LoginPrologueFragment.kt @@ -56,7 +56,7 @@ open class LoginPrologueFragment(@LayoutRes layout: Int) : Fragment(layout) { unifiedLoginTracker.track(Flow.PROLOGUE, Step.PROLOGUE) } - binding.buttonGetStarted.isVisible = FeatureFlag.STORE_CREATION_FLOW.isEnabled() + binding.buttonGetStarted.isVisible = FeatureFlag.ACCOUNT_CREATION_FLOW.isEnabled() binding.buttonGetStarted.setOnClickListener { findNavController().navigate(LoginPrologueFragmentDirections.actionLoginPrologueFragmentToSignupFragment()) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt index bfdddbca43da..1064401fb798 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt @@ -42,6 +42,7 @@ import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerState import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerState.WooNotFoundState import com.woocommerce.android.ui.sitepicker.sitediscovery.SitePickerSiteDiscoveryFragment import com.woocommerce.android.util.ChromeCustomTabUtils +import com.woocommerce.android.util.FeatureFlag import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Logout import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog @@ -92,6 +93,11 @@ class SitePickerFragment : BaseFragment(R.layout.fragment_site_picker), LoginEma binding.loginEpilogueButtonBar.buttonSecondary.setOnClickListener { viewModel.onTryAnotherAccountButtonClick() } + + binding.loginEpilogueButtonBar.buttonTertiary.isVisible = FeatureFlag.STORE_CREATION_WEBVIEW_FLOW.isEnabled() + binding.loginEpilogueButtonBar.buttonTertiary.setOnClickListener { + viewModel.onCreateSiteButtonClick() + } } @Suppress("LongMethod", "ComplexMethod") @@ -176,6 +182,13 @@ class SitePickerFragment : BaseFragment(R.layout.fragment_site_picker), LoginEma AnalyticsTracker.track(AnalyticsEvent.LOGIN_WOOCOMMERCE_SETUP_COMPLETED) viewModel.onWooInstalled() } + handleResult>(WPComWebViewFragment.WEBVIEW_RESULT_WITH_URL) { urls -> + AnalyticsTracker.track( + AnalyticsEvent.LOGIN_WOOCOMMERCE_SITE_CREATED, + mapOf(AnalyticsTracker.KEY_URL to urls.joinToString()) + ) + viewModel.onSiteCreated(urls) + } handleNotice(WPComWebViewFragment.WEBVIEW_DISMISSED) { AnalyticsTracker.track(AnalyticsEvent.LOGIN_WOOCOMMERCE_SETUP_DISMISSED) } @@ -275,7 +288,8 @@ class SitePickerFragment : BaseFragment(R.layout.fragment_site_picker), LoginEma findNavController().navigate( NavGraphMainDirections.actionGlobalWPComWebViewFragment( urlToLoad = event.url, - urlToTriggerExit = event.validationUrl + urlToTriggerExit = event.validationUrl, + title = event.title ) ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt index 483034704e22..226f797b6760 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt @@ -28,6 +28,7 @@ import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.N import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.WooSiteUiModel import com.woocommerce.android.util.FeatureFlag import com.woocommerce.android.util.WooLog +import com.woocommerce.android.util.WooLog.T import com.woocommerce.android.viewmodel.LiveDataDelegate import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit @@ -38,8 +39,10 @@ import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType @@ -67,6 +70,9 @@ class SitePickerViewModel @Inject constructor( companion object { private const val WOOCOMMERCE_INSTALLATION_URL = "https://wordpress.com/plugins/woocommerce/" private const val WOOCOMMERCE_INSTALLATION_DONE_URL = "marketplace/thank-you/woocommerce" + private const val WOOCOMMERCE_STORE_CREATION_URL = "https://woocommerce.com/start" + private const val WOOCOMMERCE_STORE_CREATION_DONE_URL = "calypso/images/wpcom-ecommerce" + private const val WOOCOMMERCE_STORE_CREATION_RETRY_INTERVAL = 2000L } private val navArgs: SitePickerFragmentArgs by savedState.navArgs() @@ -78,7 +84,7 @@ class SitePickerViewModel @Inject constructor( val sites: LiveData> = _sites private var loginSiteAddress: String? - get() = savedState.get("key") ?: appPrefsWrapper.getLoginSiteAddress() + get() = savedState["key"] ?: appPrefsWrapper.getLoginSiteAddress() set(value) = savedState.set("key", value) val shouldShowToolbar: Boolean @@ -112,11 +118,12 @@ class SitePickerViewModel @Inject constructor( } } - private suspend fun fetchSitesFromApi(showSkeleton: Boolean) { + private suspend fun fetchSitesFromApi(showSkeleton: Boolean, delayTime: Long = 0) { sitePickerViewState = sitePickerViewState.copy( isSkeletonViewVisible = showSkeleton ) + delay(delayTime) val startTime = System.currentTimeMillis() val result = repository.fetchWooCommerceSites() val duration = System.currentTimeMillis() - startTime @@ -406,6 +413,17 @@ class SitePickerViewModel @Inject constructor( triggerEvent(SitePickerEvent.NavigateToSiteAddressEvent) } + fun onCreateSiteButtonClick() { + analyticsTrackerWrapper.track(AnalyticsEvent.SITE_PICKER_CREATE_SITE_TAPPED) + triggerEvent( + NavigateToWPComWebView( + url = WOOCOMMERCE_STORE_CREATION_URL, + validationUrl = WOOCOMMERCE_STORE_CREATION_DONE_URL, + title = resourceProvider.getString(string.create_new_store) + ) + ) + } + fun onTryAnotherAccountButtonClick() { trackLoginEvent(clickEvent = UnifiedLoginTracker.Click.TRY_ANOTHER_ACCOUNT) launch { @@ -530,6 +548,42 @@ class SitePickerViewModel @Inject constructor( } } + fun onSiteCreated(urls: List) { + launch { + sitePickerViewState = sitePickerViewState.copy( + isSkeletonViewVisible = false, isProgressDiaLogVisible = true + ) + + var newSite: SiteModel? = null + while (newSite?.hasWooCommerce != true) { + newSite = urls.firstNotNullOfOrNull { url -> repository.getSiteBySiteUrl(url) } + if (newSite != null && newSite.hasWooCommerce) { + onSiteSelected(newSite) + onContinueButtonClick(true) + WooLog.d(T.LOGIN, "Found new site: ${newSite.url}") + } else { + WooLog.d(T.LOGIN, "New site not found, retrying...") + val result = fetchSitesAfterCreation() + if (result.isFailure) { + triggerEvent(ShowSnackbar(string.site_picker_error)) + break + } else { + displaySites(result.getOrDefault(emptyList())) + } + } + } + } + } + + private suspend fun fetchSitesAfterCreation(): Result> { + val result = withContext(Dispatchers.Default) { + delay(WOOCOMMERCE_STORE_CREATION_RETRY_INTERVAL) + repository.fetchWooCommerceSites() + } + return if (result.isError) Result.failure(Exception(result.error.message)) + else Result.success(result.model ?: emptyList()) + } + fun onWooInstalled() { suspend fun fetchSite(site: SiteModel, retries: Int = 0): Result { delay(retries * TimeUnit.SECONDS.toMillis(2)) @@ -660,7 +714,11 @@ class SitePickerViewModel @Inject constructor( object NavigateToNewToWooEvent : SitePickerEvent() object NavigateToSiteAddressEvent : SitePickerEvent() data class NavigateToHelpFragmentEvent(val origin: HelpActivity.Origin) : SitePickerEvent() - data class NavigateToWPComWebView(val url: String, val validationUrl: String) : SitePickerEvent() + data class NavigateToWPComWebView( + val url: String, + val validationUrl: String, + val title: String? = null + ) : SitePickerEvent() data class NavigateToAccountMismatchScreen( val primaryButton: AccountMismatchPrimaryButton, val siteUrl: String, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt index 8abd743066aa..62c89d0ecd95 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt @@ -14,7 +14,8 @@ enum class FeatureFlag { WC_SHIPPING_BANNER, UNIFIED_ORDER_EDITING, ORDER_CREATION_CUSTOMER_SEARCH, - STORE_CREATION_FLOW; + STORE_CREATION_WEBVIEW_FLOW, + ACCOUNT_CREATION_FLOW; fun isEnabled(context: Context? = null): Boolean { return when (this) { @@ -28,7 +29,8 @@ enum class FeatureFlag { ANALYTICS_HUB, MORE_MENU_INBOX, WC_SHIPPING_BANNER, - STORE_CREATION_FLOW -> PackageUtils.isDebugBuild() + ACCOUNT_CREATION_FLOW -> PackageUtils.isDebugBuild() + STORE_CREATION_WEBVIEW_FLOW -> PackageUtils.isDebugBuild() } } } diff --git a/WooCommerce/src/main/res/layout-land/view_login_epilogue_button_bar.xml b/WooCommerce/src/main/res/layout-land/view_login_epilogue_button_bar.xml index 7b76e59ea254..99698653f968 100644 --- a/WooCommerce/src/main/res/layout-land/view_login_epilogue_button_bar.xml +++ b/WooCommerce/src/main/res/layout-land/view_login_epilogue_button_bar.xml @@ -32,4 +32,16 @@ android:text="@string/login_view_connected_stores" android:textAllCaps="false" tools:visibility="visible"/> + + diff --git a/WooCommerce/src/main/res/layout/view_login_epilogue_button_bar.xml b/WooCommerce/src/main/res/layout/view_login_epilogue_button_bar.xml index c9ab959fbf11..b96a20ab148b 100644 --- a/WooCommerce/src/main/res/layout/view_login_epilogue_button_bar.xml +++ b/WooCommerce/src/main/res/layout/view_login_epilogue_button_bar.xml @@ -29,4 +29,13 @@ android:text="@string/login_view_connected_stores" android:textAllCaps="false" tools:visibility="visible"/> + + diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 6df88123b53f..995cbce266a7 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -2646,4 +2646,7 @@ WooCommerce Stats Today Couldn\'t load data As of %1$s + + + Create a new store