Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plans in site creation: Plan selection screen #19304

Merged
merged 27 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5442558
Update SiteCreationStep.kt
ravishanker Oct 5, 2023
ad2692a
Create SiteCreationPlansUiState.kt
ravishanker Oct 5, 2023
012d532
Create SiteCreationPlansWebViewClient.kt
ravishanker Oct 5, 2023
363aa9a
Update strings.xml
ravishanker Oct 5, 2023
7ca8666
Update SiteCreationMainVM.kt
ravishanker Oct 5, 2023
71969f0
Create SiteCreationPlansFragment.kt
ravishanker Oct 5, 2023
8e68b03
Create SiteCreationPlansViewModel.kt
ravishanker Oct 5, 2023
3f62e76
Update SiteCreationActivity.kt
ravishanker Oct 5, 2023
62c0f59
Add params to request
ravishanker Oct 6, 2023
3b54c20
Merge branch 'trunk' into Plans-in-site-creation-Plan-Selection-Screen
ravishanker Oct 8, 2023
fb4dbd7
Detect plan selection in site creation flow
irfano Oct 12, 2023
68559c3
Update SiteCreationPlansViewModel.kt
ravishanker Oct 13, 2023
44c8e2b
Update SiteCreationPlansWebViewClient.kt
ravishanker Oct 13, 2023
3cf4396
Update DomainRegistrationCheckoutWebViewNavigationDelegate.kt
ravishanker Oct 13, 2023
fc30b40
Update SiteCreationProgressViewModel.kt
ravishanker Oct 13, 2023
b91069b
Remove unused
ravishanker Oct 13, 2023
7b80db6
Update SiteCreationFixtures.kt
ravishanker Oct 13, 2023
69fb18b
Create PlansScreenListener.kt
ravishanker Oct 13, 2023
fac06f3
Update SiteCreationPlansWebViewClient.kt
ravishanker Oct 13, 2023
f336392
Update SiteCreationPlansViewModel.kt
ravishanker Oct 13, 2023
8356847
Update SiteCreationPlansFragment.kt
ravishanker Oct 13, 2023
b92f5a8
Update SiteCreationActivity.kt
ravishanker Oct 13, 2023
a5ac692
Update SiteCreationMainVM.kt
ravishanker Oct 13, 2023
63015e7
Update SiteCreationPlansWebViewClient.kt
ravishanker Oct 14, 2023
b094d59
Update SiteCreationPlansViewModel.kt
ravishanker Oct 14, 2023
782b7b4
Update SiteCreationProgressViewModel.kt
ravishanker Oct 14, 2023
05d9682
handle free plan selection
ravishanker Oct 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ object DomainRegistrationCheckoutWebViewNavigationDelegate {
UrlMatcher(
".*wordpress.com".toRegex(),
listOf(
"/jetpack-app".toRegex(),
"/plans.*?.*".toRegex(),
"/automattic-domain-name-registration-agreement.*".toRegex(),
"/checkout.*".toRegex(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.wordpress.android.ui.sitecreation.SiteCreationResult.Created
import org.wordpress.android.ui.sitecreation.SiteCreationResult.CreatedButNotFetched
import org.wordpress.android.ui.sitecreation.SiteCreationStep.DOMAINS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.INTENTS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.PLANS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.PROGRESS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.SITE_DESIGNS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.SITE_NAME
Expand All @@ -43,6 +44,9 @@ import org.wordpress.android.ui.sitecreation.domains.DomainsScreenListener
import org.wordpress.android.ui.sitecreation.domains.SiteCreationDomainsFragment
import org.wordpress.android.ui.sitecreation.misc.OnHelpClickedListener
import org.wordpress.android.ui.sitecreation.misc.SiteCreationSource
import org.wordpress.android.ui.sitecreation.plans.PlanModel
import org.wordpress.android.ui.sitecreation.plans.PlansScreenListener
import org.wordpress.android.ui.sitecreation.plans.SiteCreationPlansFragment
import org.wordpress.android.ui.sitecreation.previews.SiteCreationPreviewFragment
import org.wordpress.android.ui.sitecreation.previews.SitePreviewViewModel
import org.wordpress.android.ui.sitecreation.progress.SiteCreationProgressFragment
Expand Down Expand Up @@ -70,6 +74,7 @@ class SiteCreationActivity : LocaleAwareActivity(),
IntentsScreenListener,
SiteNameScreenListener,
DomainsScreenListener,
PlansScreenListener,
OnHelpClickedListener,
BasicDialogPositiveClickInterface,
BasicDialogNegativeClickInterface {
Expand Down Expand Up @@ -220,6 +225,10 @@ class SiteCreationActivity : LocaleAwareActivity(),
mainViewModel.onDomainsScreenFinished(domain)
}

override fun onPlanSelected(plan: PlanModel) {
mainViewModel.onPlanSelection(plan)
}

override fun onHelpClicked(origin: Origin) {
ActivityLauncher.viewHelp(this, origin, null, null)
}
Expand All @@ -235,6 +244,7 @@ class SiteCreationActivity : LocaleAwareActivity(),
HomePagePickerFragment.newInstance(target.wizardState.siteIntent)
}
DOMAINS -> SiteCreationDomainsFragment.newInstance(screenTitle)
PLANS -> SiteCreationPlansFragment.newInstance(target.wizardState)
PROGRESS -> SiteCreationProgressFragment.newInstance(target.wizardState)
SITE_PREVIEW -> SiteCreationPreviewFragment.newInstance(screenTitle, target.wizardState)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.wordpress.android.ui.sitecreation.SiteCreationResult.NotCreated
import org.wordpress.android.ui.sitecreation.domains.DomainModel
import org.wordpress.android.ui.sitecreation.misc.SiteCreationSource
import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker
import org.wordpress.android.ui.sitecreation.plans.PlanModel
import org.wordpress.android.ui.sitecreation.usecases.FetchHomePageLayoutsUseCase
import org.wordpress.android.ui.utils.UiString.UiStringRes
import org.wordpress.android.util.AppLog
Expand Down Expand Up @@ -59,6 +60,7 @@ data class SiteCreationState(
val segmentId: Long? = null,
val siteDesign: String? = null,
val domain: DomainModel? = null,
val plan: PlanModel? = null,
val result: SiteCreationResult = NotCreated,
) : WizardState, Parcelable

Expand Down Expand Up @@ -260,13 +262,35 @@ class SiteCreationMainVM @Inject constructor(
siteCreationState = siteCreationState.copy(domain = null)
}
}
if (wizardStep == SiteCreationStep.PLANS) {
siteCreationState.plan?.let {
siteCreationState = siteCreationState.copy(plan = null)
}
}
}

fun onDomainsScreenFinished(domain: DomainModel) {
siteCreationState = siteCreationState.copy(domain = domain)
wizardManager.showNextStep()
}

fun onPlanSelection(plan: PlanModel) {
siteCreationState = siteCreationState.copy(plan = plan)
if (plan.productSlug == "free_plan") {
// if they select a paid domain, then choose a free plan, with free domain on plan selection screen
siteCreationState = siteCreationState.copy(
domain = DomainModel(
domainName = plan.productName.orEmpty(),
isFree = true,
cost = "",
productId = 0,
supportsPrivacy = false
)
)
}
wizardManager.showNextStep()
}

fun screenTitleForWizardStep(step: SiteCreationStep): SiteCreationScreenTitle {
val stepPosition = wizardManager.stepPosition(step)
val stepCount = wizardManager.stepsCount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.wordpress.android.ui.sitecreation

import org.wordpress.android.ui.sitecreation.SiteCreationStep.DOMAINS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.INTENTS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.PLANS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.PROGRESS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.SITE_DESIGNS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.SITE_NAME
Expand All @@ -13,7 +14,7 @@ import javax.inject.Inject
import javax.inject.Singleton

enum class SiteCreationStep : WizardStep {
SITE_DESIGNS, DOMAINS, PROGRESS, SITE_PREVIEW, INTENTS, SITE_NAME;
SITE_DESIGNS, DOMAINS, PLANS, PROGRESS, SITE_PREVIEW, INTENTS, SITE_NAME;
}

@Singleton
Expand All @@ -26,7 +27,7 @@ class SiteCreationStepsProvider @Inject constructor(

fun getSteps(): List<SiteCreationStep> = when {
isSiteNameEnabled -> listOf(INTENTS, SITE_NAME, SITE_DESIGNS, PROGRESS, SITE_PREVIEW)
isIntentsEnabled -> listOf(INTENTS, SITE_DESIGNS, DOMAINS, PROGRESS, SITE_PREVIEW)
else -> listOf(SITE_DESIGNS, DOMAINS, PROGRESS, SITE_PREVIEW)
isIntentsEnabled -> listOf(INTENTS, SITE_DESIGNS, DOMAINS, PLANS, PROGRESS, SITE_PREVIEW)
else -> listOf(SITE_DESIGNS, DOMAINS, PLANS, PROGRESS, SITE_PREVIEW)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.wordpress.android.ui.sitecreation.plans

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

interface PlansScreenListener {
fun onPlanSelected(plan: PlanModel)
}

@Parcelize
data class PlanModel(
val productId: Int?,
val productSlug: String?,
val productName: String?,
val isCurrentPlan: Boolean,
val hasDomainCredit: Boolean
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package org.wordpress.android.ui.sitecreation.plans

import android.annotation.SuppressLint
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.wordpress.android.R
import org.wordpress.android.ui.WPWebViewActivity
import org.wordpress.android.ui.compose.components.MainTopAppBar
import org.wordpress.android.ui.compose.components.NavigationIcons
import org.wordpress.android.ui.compose.theme.AppTheme
import org.wordpress.android.ui.compose.utils.uiStringText
import org.wordpress.android.ui.main.jetpack.migration.compose.state.LoadingState
import org.wordpress.android.ui.sitecreation.SiteCreationActivity.Companion.ARG_STATE
import org.wordpress.android.ui.sitecreation.SiteCreationState
import org.wordpress.android.ui.sitecreation.plans.SiteCreationPlansWebViewClient.SiteCreationPlansWebViewClientListener
import org.wordpress.android.util.extensions.getParcelableCompat

@AndroidEntryPoint
class SiteCreationPlansFragment : Fragment(), SiteCreationPlansWebViewClientListener {
private val viewModel: SiteCreationPlansViewModel by viewModels()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View = ComposeView(requireContext()).apply {
setContent {
AppTheme {
SiteCreationPlansPage(
navigationUp = requireActivity().onBackPressedDispatcher::onBackPressed
)
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

viewModel.start(requireNotNull(requireArguments().getParcelableCompat(ARG_STATE)))
viewModel.actionEvents.onEach(this::handleActionEvents).launchIn(viewLifecycleOwner.lifecycleScope)
}

private fun handleActionEvents(actionEvent: SiteCreationPlansActionEvent) {
when (actionEvent) {
is SiteCreationPlansActionEvent.CreateSite -> {
(requireActivity() as PlansScreenListener).onPlanSelected(actionEvent.planModel)
}
}
}

// SiteCreationWebViewClient
override fun onPlanSelected(uri: Uri) {
viewModel.onPlanSelected(uri)
}

override fun onWebViewPageLoaded() {
viewModel.onUrlLoaded()
}

override fun onWebViewReceivedError() {
viewModel.onWebViewError()
}

@Composable
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
fun SiteCreationPlansPage(
navigationUp: () -> Unit = { },
viewModel: SiteCreationPlansViewModel = viewModel(),
) {
val uiState by viewModel.uiState.collectAsState()
Scaffold(
topBar = {
MainTopAppBar(
title = stringResource(R.string.site_creation_plans_selection_title),
navigationIcon = NavigationIcons.BackIcon,
onNavigationIconClick = navigationUp
)
},
content = { SiteCreationPlansContent(uiState) }
)
}

@SuppressLint("SetJavaScriptEnabled")
@Composable
private fun SiteCreationPlansContent(uiState: SiteCreationPlansUiState) {
when (uiState) {
is SiteCreationPlansUiState.Preparing -> LoadingState()
is SiteCreationPlansUiState.Prepared,
is SiteCreationPlansUiState.Loaded -> SiteCreationPlansWebView(uiState)
is SiteCreationPlansUiState.Error -> SiteCreationPlansError(uiState)
}
}

@Composable
fun SiteCreationPlansError(error: SiteCreationPlansUiState.Error) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(20.dp)
.fillMaxWidth()
.fillMaxHeight(),
) {
Text(
text = uiStringText(uiString = error.title),
style = MaterialTheme.typography.h5,
textAlign = TextAlign.Center
)
Text(
text = uiStringText(uiString = error.description),
style = MaterialTheme.typography.body1,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 8.dp)
)
if (error.button != null) {
Button(
modifier = Modifier.padding(top = 8.dp),
onClick = error.button.click
) {
Text(text = uiStringText(uiString = error.button.text))
}
}
}
}

@SuppressLint("SetJavaScriptEnabled")
@Composable
private fun SiteCreationPlansWebView(uiState: SiteCreationPlansUiState) {
var webView: WebView? by remember { mutableStateOf(null) }

if (uiState is SiteCreationPlansUiState.Prepared) {
val model = uiState.model
LaunchedEffect(true) {
webView = WebView(requireContext()).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY
settings.userAgentString = model.userAgent
settings.javaScriptEnabled = model.enableJavascript
settings.domStorageEnabled = model.enableDomStorage
webViewClient = SiteCreationPlansWebViewClient(this@SiteCreationPlansFragment)
postUrl(WPWebViewActivity.WPCOM_LOGIN_URL, model.addressToLoad.toByteArray())
}
}
}

Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
if (uiState is SiteCreationPlansUiState.Prepared) {
LoadingState()
} else {
webView?.let { theWebView ->
AndroidView(
factory = { theWebView },
modifier = Modifier.fillMaxSize()
)
}
}
}
}

companion object {
const val TAG = "site_creation_plans_fragment_tag"

fun newInstance(siteCreationState: SiteCreationState) = SiteCreationPlansFragment().apply {
arguments = Bundle().apply {
putParcelable(ARG_STATE, siteCreationState)
}
}
}
}
Loading