Skip to content

Commit a3367ba

Browse files
authored
Merge pull request #7602 from woocommerce/feature/webview-store-creation
Store creation using a webview
2 parents da7e83e + 6627d58 commit a3367ba

File tree

9 files changed

+144
-9
lines changed

9 files changed

+144
-9
lines changed

WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ enum class AnalyticsEvent(val siteless: Boolean = false) {
131131
SITE_PICKER_SITE_DISCOVERY(siteless = true),
132132
SITE_PICKER_JETPACK_TIMEOUT_ERROR_SHOWN(siteless = true),
133133
SITE_PICKER_JETPACK_TIMEOUT_CONTACT_SUPPORT_CLICKED(siteless = true),
134+
SITE_PICKER_CREATE_SITE_TAPPED(siteless = true),
135+
LOGIN_WOOCOMMERCE_SITE_CREATED(siteless = true),
134136

135137
// -- Dashboard
136138
DASHBOARD_PULLED_TO_REFRESH,

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/common/wpcomwebview/WPComWebViewFragment.kt

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import androidx.navigation.fragment.navArgs
1010
import com.woocommerce.android.R
1111
import com.woocommerce.android.databinding.FragmentWpcomWebviewBinding
1212
import com.woocommerce.android.extensions.navigateBackWithNotice
13+
import com.woocommerce.android.extensions.navigateBackWithResult
1314
import com.woocommerce.android.ui.base.BaseFragment
1415
import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewFragment.UrlComparisonMode.EQUALITY
1516
import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewFragment.UrlComparisonMode.PARTIAL
1617
import com.woocommerce.android.ui.main.MainActivity.Companion.BackPressListener
18+
import com.woocommerce.android.util.WooLog
19+
import com.woocommerce.android.util.WooLog.T
1720
import dagger.hilt.android.AndroidEntryPoint
1821
import org.wordpress.android.fluxc.network.UserAgent
1922
import javax.inject.Inject
@@ -28,11 +31,16 @@ import javax.inject.Inject
2831
class WPComWebViewFragment : BaseFragment(R.layout.fragment_wpcom_webview), UrlInterceptor, BackPressListener {
2932
companion object {
3033
const val WEBVIEW_RESULT = "webview-result"
34+
const val WEBVIEW_RESULT_WITH_URL = "webview-result-with-url"
3135
const val WEBVIEW_DISMISSED = "webview-dismissed"
36+
const val WEBVIEW_STORE_CHECKOUT_STRING = "checkout/thank-you/"
37+
const val WEBVIEW_STORE_URL_KEY = "store-url-key"
3238
}
3339

3440
private val webViewClient by lazy { WPComWebViewClient(this) }
3541
private val navArgs: WPComWebViewFragmentArgs by navArgs()
42+
private var siteUrls = ArrayList<String>()
43+
private var canNavigate = true
3644

3745
@Inject lateinit var wpcomWebViewAuthenticator: WPComWebViewAuthenticator
3846

@@ -52,20 +60,47 @@ class WPComWebViewFragment : BaseFragment(R.layout.fragment_wpcom_webview), UrlI
5260
}
5361
}
5462
this.settings.javaScriptEnabled = true
63+
this.settings.domStorageEnabled = true
5564
settings.userAgentString = userAgent.userAgent
5665
}
5766

67+
siteUrls = savedInstanceState?.getStringArrayList(WEBVIEW_STORE_URL_KEY) ?: siteUrls
68+
5869
wpcomWebViewAuthenticator.authenticateAndLoadUrl(binding.webView, navArgs.urlToLoad)
5970
}
6071

72+
override fun onSaveInstanceState(outState: Bundle) {
73+
outState.putStringArrayList(WEBVIEW_STORE_URL_KEY, siteUrls)
74+
super.onSaveInstanceState(outState)
75+
}
76+
6177
override fun onLoadUrl(url: String) {
6278
fun String.matchesUrl(url: String) = when (navArgs.urlComparisonMode) {
6379
PARTIAL -> url.contains(this, ignoreCase = true)
6480
EQUALITY -> equals(url, ignoreCase = true)
6581
}
6682

67-
if (isAdded && navArgs.urlToTriggerExit?.matchesUrl(url) == true) {
68-
navigateBackWithNotice(WEBVIEW_RESULT)
83+
extractSiteUrl(url)
84+
85+
if (isAdded && navArgs.urlToTriggerExit?.matchesUrl(url) == true && canNavigate) {
86+
canNavigate = false
87+
if (siteUrls.isEmpty()) {
88+
navigateBackWithNotice(WEBVIEW_RESULT)
89+
} else {
90+
navigateBackWithResult(WEBVIEW_RESULT_WITH_URL, siteUrls)
91+
WooLog.d(T.LOGIN, "Site creation URLs: ${siteUrls.joinToString()}")
92+
}
93+
}
94+
}
95+
96+
private fun extractSiteUrl(url: String) {
97+
"$WEBVIEW_STORE_CHECKOUT_STRING.+/".toRegex().find(url)?.range?.let { range ->
98+
val start = range.first + WEBVIEW_STORE_CHECKOUT_STRING.length
99+
val end = range.last
100+
val siteUrl = url.substring(start, end)
101+
if (!siteUrls.contains(siteUrl)) {
102+
siteUrls.add(siteUrl)
103+
}
69104
}
70105
}
71106

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/LoginPrologueFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ open class LoginPrologueFragment(@LayoutRes layout: Int) : Fragment(layout) {
5656
unifiedLoginTracker.track(Flow.PROLOGUE, Step.PROLOGUE)
5757
}
5858

59-
binding.buttonGetStarted.isVisible = FeatureFlag.STORE_CREATION_FLOW.isEnabled()
59+
binding.buttonGetStarted.isVisible = FeatureFlag.ACCOUNT_CREATION_FLOW.isEnabled()
6060
binding.buttonGetStarted.setOnClickListener {
6161
findNavController().navigate(LoginPrologueFragmentDirections.actionLoginPrologueFragmentToSignupFragment())
6262
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerState
4242
import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerState.WooNotFoundState
4343
import com.woocommerce.android.ui.sitepicker.sitediscovery.SitePickerSiteDiscoveryFragment
4444
import com.woocommerce.android.util.ChromeCustomTabUtils
45+
import com.woocommerce.android.util.FeatureFlag
4546
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
4647
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Logout
4748
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog
@@ -92,6 +93,11 @@ class SitePickerFragment : BaseFragment(R.layout.fragment_site_picker), LoginEma
9293
binding.loginEpilogueButtonBar.buttonSecondary.setOnClickListener {
9394
viewModel.onTryAnotherAccountButtonClick()
9495
}
96+
97+
binding.loginEpilogueButtonBar.buttonTertiary.isVisible = FeatureFlag.STORE_CREATION_WEBVIEW_FLOW.isEnabled()
98+
binding.loginEpilogueButtonBar.buttonTertiary.setOnClickListener {
99+
viewModel.onCreateSiteButtonClick()
100+
}
95101
}
96102

97103
@Suppress("LongMethod", "ComplexMethod")
@@ -176,6 +182,13 @@ class SitePickerFragment : BaseFragment(R.layout.fragment_site_picker), LoginEma
176182
AnalyticsTracker.track(AnalyticsEvent.LOGIN_WOOCOMMERCE_SETUP_COMPLETED)
177183
viewModel.onWooInstalled()
178184
}
185+
handleResult<List<String>>(WPComWebViewFragment.WEBVIEW_RESULT_WITH_URL) { urls ->
186+
AnalyticsTracker.track(
187+
AnalyticsEvent.LOGIN_WOOCOMMERCE_SITE_CREATED,
188+
mapOf(AnalyticsTracker.KEY_URL to urls.joinToString())
189+
)
190+
viewModel.onSiteCreated(urls)
191+
}
179192
handleNotice(WPComWebViewFragment.WEBVIEW_DISMISSED) {
180193
AnalyticsTracker.track(AnalyticsEvent.LOGIN_WOOCOMMERCE_SETUP_DISMISSED)
181194
}
@@ -275,7 +288,8 @@ class SitePickerFragment : BaseFragment(R.layout.fragment_site_picker), LoginEma
275288
findNavController().navigate(
276289
NavGraphMainDirections.actionGlobalWPComWebViewFragment(
277290
urlToLoad = event.url,
278-
urlToTriggerExit = event.validationUrl
291+
urlToTriggerExit = event.validationUrl,
292+
title = event.title
279293
)
280294
)
281295
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.N
2828
import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.WooSiteUiModel
2929
import com.woocommerce.android.util.FeatureFlag
3030
import com.woocommerce.android.util.WooLog
31+
import com.woocommerce.android.util.WooLog.T
3132
import com.woocommerce.android.viewmodel.LiveDataDelegate
3233
import com.woocommerce.android.viewmodel.MultiLiveEvent
3334
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
@@ -38,8 +39,10 @@ import com.woocommerce.android.viewmodel.ResourceProvider
3839
import com.woocommerce.android.viewmodel.ScopedViewModel
3940
import com.woocommerce.android.viewmodel.navArgs
4041
import dagger.hilt.android.lifecycle.HiltViewModel
42+
import kotlinx.coroutines.Dispatchers
4143
import kotlinx.coroutines.delay
4244
import kotlinx.coroutines.launch
45+
import kotlinx.coroutines.withContext
4346
import kotlinx.parcelize.Parcelize
4447
import org.wordpress.android.fluxc.model.SiteModel
4548
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType
@@ -67,6 +70,9 @@ class SitePickerViewModel @Inject constructor(
6770
companion object {
6871
private const val WOOCOMMERCE_INSTALLATION_URL = "https://wordpress.com/plugins/woocommerce/"
6972
private const val WOOCOMMERCE_INSTALLATION_DONE_URL = "marketplace/thank-you/woocommerce"
73+
private const val WOOCOMMERCE_STORE_CREATION_URL = "https://woocommerce.com/start"
74+
private const val WOOCOMMERCE_STORE_CREATION_DONE_URL = "calypso/images/wpcom-ecommerce"
75+
private const val WOOCOMMERCE_STORE_CREATION_RETRY_INTERVAL = 2000L
7076
}
7177

7278
private val navArgs: SitePickerFragmentArgs by savedState.navArgs()
@@ -78,7 +84,7 @@ class SitePickerViewModel @Inject constructor(
7884
val sites: LiveData<List<SitesListItem>> = _sites
7985

8086
private var loginSiteAddress: String?
81-
get() = savedState.get("key") ?: appPrefsWrapper.getLoginSiteAddress()
87+
get() = savedState["key"] ?: appPrefsWrapper.getLoginSiteAddress()
8288
set(value) = savedState.set("key", value)
8389

8490
val shouldShowToolbar: Boolean
@@ -112,11 +118,12 @@ class SitePickerViewModel @Inject constructor(
112118
}
113119
}
114120

115-
private suspend fun fetchSitesFromApi(showSkeleton: Boolean) {
121+
private suspend fun fetchSitesFromApi(showSkeleton: Boolean, delayTime: Long = 0) {
116122
sitePickerViewState = sitePickerViewState.copy(
117123
isSkeletonViewVisible = showSkeleton
118124
)
119125

126+
delay(delayTime)
120127
val startTime = System.currentTimeMillis()
121128
val result = repository.fetchWooCommerceSites()
122129
val duration = System.currentTimeMillis() - startTime
@@ -406,6 +413,17 @@ class SitePickerViewModel @Inject constructor(
406413
triggerEvent(SitePickerEvent.NavigateToSiteAddressEvent)
407414
}
408415

416+
fun onCreateSiteButtonClick() {
417+
analyticsTrackerWrapper.track(AnalyticsEvent.SITE_PICKER_CREATE_SITE_TAPPED)
418+
triggerEvent(
419+
NavigateToWPComWebView(
420+
url = WOOCOMMERCE_STORE_CREATION_URL,
421+
validationUrl = WOOCOMMERCE_STORE_CREATION_DONE_URL,
422+
title = resourceProvider.getString(string.create_new_store)
423+
)
424+
)
425+
}
426+
409427
fun onTryAnotherAccountButtonClick() {
410428
trackLoginEvent(clickEvent = UnifiedLoginTracker.Click.TRY_ANOTHER_ACCOUNT)
411429
launch {
@@ -530,6 +548,42 @@ class SitePickerViewModel @Inject constructor(
530548
}
531549
}
532550

551+
fun onSiteCreated(urls: List<String>) {
552+
launch {
553+
sitePickerViewState = sitePickerViewState.copy(
554+
isSkeletonViewVisible = false, isProgressDiaLogVisible = true
555+
)
556+
557+
var newSite: SiteModel? = null
558+
while (newSite?.hasWooCommerce != true) {
559+
newSite = urls.firstNotNullOfOrNull { url -> repository.getSiteBySiteUrl(url) }
560+
if (newSite != null && newSite.hasWooCommerce) {
561+
onSiteSelected(newSite)
562+
onContinueButtonClick(true)
563+
WooLog.d(T.LOGIN, "Found new site: ${newSite.url}")
564+
} else {
565+
WooLog.d(T.LOGIN, "New site not found, retrying...")
566+
val result = fetchSitesAfterCreation()
567+
if (result.isFailure) {
568+
triggerEvent(ShowSnackbar(string.site_picker_error))
569+
break
570+
} else {
571+
displaySites(result.getOrDefault(emptyList()))
572+
}
573+
}
574+
}
575+
}
576+
}
577+
578+
private suspend fun fetchSitesAfterCreation(): Result<List<SiteModel>> {
579+
val result = withContext(Dispatchers.Default) {
580+
delay(WOOCOMMERCE_STORE_CREATION_RETRY_INTERVAL)
581+
repository.fetchWooCommerceSites()
582+
}
583+
return if (result.isError) Result.failure(Exception(result.error.message))
584+
else Result.success(result.model ?: emptyList())
585+
}
586+
533587
fun onWooInstalled() {
534588
suspend fun fetchSite(site: SiteModel, retries: Int = 0): Result<SiteModel> {
535589
delay(retries * TimeUnit.SECONDS.toMillis(2))
@@ -660,7 +714,11 @@ class SitePickerViewModel @Inject constructor(
660714
object NavigateToNewToWooEvent : SitePickerEvent()
661715
object NavigateToSiteAddressEvent : SitePickerEvent()
662716
data class NavigateToHelpFragmentEvent(val origin: HelpActivity.Origin) : SitePickerEvent()
663-
data class NavigateToWPComWebView(val url: String, val validationUrl: String) : SitePickerEvent()
717+
data class NavigateToWPComWebView(
718+
val url: String,
719+
val validationUrl: String,
720+
val title: String? = null
721+
) : SitePickerEvent()
664722
data class NavigateToAccountMismatchScreen(
665723
val primaryButton: AccountMismatchPrimaryButton,
666724
val siteUrl: String,

WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ enum class FeatureFlag {
1414
WC_SHIPPING_BANNER,
1515
UNIFIED_ORDER_EDITING,
1616
ORDER_CREATION_CUSTOMER_SEARCH,
17-
STORE_CREATION_FLOW;
17+
STORE_CREATION_WEBVIEW_FLOW,
18+
ACCOUNT_CREATION_FLOW;
1819

1920
fun isEnabled(context: Context? = null): Boolean {
2021
return when (this) {
@@ -28,7 +29,8 @@ enum class FeatureFlag {
2829
ANALYTICS_HUB,
2930
MORE_MENU_INBOX,
3031
WC_SHIPPING_BANNER,
31-
STORE_CREATION_FLOW -> PackageUtils.isDebugBuild()
32+
ACCOUNT_CREATION_FLOW -> PackageUtils.isDebugBuild()
33+
STORE_CREATION_WEBVIEW_FLOW -> PackageUtils.isDebugBuild()
3234
}
3335
}
3436
}

WooCommerce/src/main/res/layout-land/view_login_epilogue_button_bar.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,16 @@
3232
android:text="@string/login_view_connected_stores"
3333
android:textAllCaps="false"
3434
tools:visibility="visible"/>
35+
36+
<com.google.android.material.button.MaterialButton
37+
android:id="@+id/button_tertiary"
38+
style="@style/Woo.Button.Outlined"
39+
android:layout_width="0dp"
40+
android:layout_height="wrap_content"
41+
android:layout_weight="1"
42+
android:layout_marginStart="@dimen/minor_100"
43+
android:layout_marginEnd="@dimen/minor_00"
44+
android:text="@string/create_new_store"
45+
android:textAllCaps="false"
46+
tools:visibility="visible"/>
3547
</LinearLayout>

WooCommerce/src/main/res/layout/view_login_epilogue_button_bar.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@
2929
android:text="@string/login_view_connected_stores"
3030
android:textAllCaps="false"
3131
tools:visibility="visible"/>
32+
33+
<com.google.android.material.button.MaterialButton
34+
android:id="@+id/button_tertiary"
35+
style="@style/Woo.Button.Outlined"
36+
android:layout_width="match_parent"
37+
android:layout_height="wrap_content"
38+
android:text="@string/create_new_store"
39+
android:textAllCaps="false"
40+
tools:visibility="visible"/>
3241
</LinearLayout>

WooCommerce/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2646,4 +2646,7 @@
26462646
<string name="stats_today_widget_description">WooCommerce Stats Today</string>
26472647
<string name="stats_widget_error_no_data">Couldn\'t load data</string>
26482648
<string name="stats_widget_last_updated_message">As of %1$s</string>
2649+
2650+
<!-- Store creation -->
2651+
<string name="create_new_store">Create a new store</string>
26492652
</resources>

0 commit comments

Comments
 (0)