Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -13,14 +13,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.wordpress.android.Constants.TYPE_DOMAINS_PRODUCT
import org.wordpress.android.R
import org.wordpress.android.fluxc.Dispatcher
import org.wordpress.android.fluxc.model.products.Product
import org.wordpress.android.fluxc.network.rest.wpcom.site.DomainSuggestionResponse
import org.wordpress.android.fluxc.store.ProductsStore
import org.wordpress.android.fluxc.store.SiteStore.OnSuggestedDomains
import org.wordpress.android.fluxc.store.SiteStore.SuggestDomainErrorType
import org.wordpress.android.models.networkresource.ListState
import org.wordpress.android.models.networkresource.ListState.Error
import org.wordpress.android.models.networkresource.ListState.Loading
Expand All @@ -41,14 +34,15 @@ import org.wordpress.android.ui.sitecreation.misc.SiteCreationSearchInputUiState
import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker
import org.wordpress.android.ui.sitecreation.usecases.FETCH_DOMAINS_VENDOR_DOT
import org.wordpress.android.ui.sitecreation.usecases.FETCH_DOMAINS_VENDOR_MOBILE
import org.wordpress.android.ui.sitecreation.usecases.FetchDomainsResult
import org.wordpress.android.ui.sitecreation.usecases.FetchDomainsUseCase
import org.wordpress.android.ui.utils.UiString
import org.wordpress.android.ui.utils.UiString.UiStringRes
import org.wordpress.android.ui.utils.UiString.UiStringText
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.NetworkUtilsWrapper
import org.wordpress.android.util.config.PlansInSiteCreationFeatureConfig
import org.wordpress.android.viewmodel.SingleLiveEvent
import uniffi.wp_api.DomainSuggestion
import javax.inject.Inject
import javax.inject.Named
import kotlin.coroutines.CoroutineContext
Expand All @@ -60,10 +54,8 @@ private const val ERROR_CONTEXT = "domains"
@HiltViewModel
class SiteCreationDomainsViewModel @Inject constructor(
private val networkUtils: NetworkUtilsWrapper,
private val dispatcher: Dispatcher,
private val domainSanitizer: SiteCreationDomainSanitizer,
private val fetchDomainsUseCase: FetchDomainsUseCase,
private val productsStore: ProductsStore,
private val plansInSiteCreationFeatureConfig: PlansInSiteCreationFeatureConfig,
private val tracker: SiteCreationTracker,
@Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher,
Expand All @@ -78,7 +70,6 @@ class SiteCreationDomainsViewModel @Inject constructor(
private val _uiState: MutableLiveData<DomainsUiState> = MutableLiveData()
val uiState: LiveData<DomainsUiState> = _uiState

private var products: Map<Int?, Product> = mapOf()
private var currentQuery: DomainSuggestionsQuery? = null
private var listState: ListState<DomainModel> = ListState.Init()
private var selectedDomain by Delegates.observable<DomainModel?>(null) { _, old, new ->
Expand All @@ -96,36 +87,11 @@ class SiteCreationDomainsViewModel @Inject constructor(
private val _onHelpClicked = SingleLiveEvent<Unit?>()
val onHelpClicked: LiveData<Unit?> = _onHelpClicked

init {
dispatcher.register(fetchDomainsUseCase)
}

override fun onCleared() {
super.onCleared()
dispatcher.unregister(fetchDomainsUseCase)
}

fun start() {
if (isStarted) return
isStarted = true
tracker.trackDomainsAccessed()
resetUiState()
if (plansInSiteCreationFeatureConfig.isEnabled()) fetchAndCacheProducts()
}

private fun fetchAndCacheProducts() {
launch {
val result = productsStore.fetchProducts(TYPE_DOMAINS_PRODUCT)
when {
result.isError -> {
AppLog.e(AppLog.T.DOMAIN_REGISTRATION, "Error while fetching domain products: ${result.error}")
}

else -> {
products = result.products.orEmpty().associateBy { it.productId }
}
}
}
}

fun onCreateSiteBtnClicked() {
Expand Down Expand Up @@ -159,10 +125,12 @@ class SiteCreationDomainsViewModel @Inject constructor(
updateUiStateToContent(query, Loading(Ready(emptyList()), false))
fetchDomainsJob = launch {
delay(THROTTLE_DELAY)
val onSuggestedDomains: OnSuggestedDomains = fetchDomainsByPurchasingFeatureConfig(query.value)
val onlyWordpressCom = !plansInSiteCreationFeatureConfig.isEnabled()
val vendor = if (onlyWordpressCom) FETCH_DOMAINS_VENDOR_DOT else FETCH_DOMAINS_VENDOR_MOBILE
val result = fetchDomainsUseCase.fetchDomains(query.value, vendor, onlyWordpressCom)

withContext(mainDispatcher) {
onDomainsFetched(query, onSuggestedDomains)
onDomainsFetched(query, result)
}
}
} else {
Expand All @@ -174,55 +142,52 @@ class SiteCreationDomainsViewModel @Inject constructor(
}
}

private suspend fun fetchDomainsByPurchasingFeatureConfig(query: String): OnSuggestedDomains {
val onlyWordpressCom = !plansInSiteCreationFeatureConfig.isEnabled()
val vendor = if (onlyWordpressCom) FETCH_DOMAINS_VENDOR_DOT else FETCH_DOMAINS_VENDOR_MOBILE

return fetchDomainsUseCase.fetchDomains(query, vendor, onlyWordpressCom)
}
private fun onDomainsFetched(query: DomainSuggestionsQuery, result: FetchDomainsResult) {
when (result) {
is FetchDomainsResult.Error -> {
tracker.trackErrorShown(ERROR_CONTEXT, result.type, result.message)
updateUiStateToContent(
query,
Error(
listState,
errorMessageResId = R.string.site_creation_fetch_suggestions_error_unknown
)
)
}
FetchDomainsResult.InvalidQuery -> {
val emptyListMessage = UiStringRes(
R.string.new_site_creation_empty_domain_list_message_invalid_query
)
updateUiStateToContent(query, Success(emptyList()), emptyListMessage)
}
is FetchDomainsResult.Success -> {
val domains = result.suggestions.map(::parseSuggestion)
.partition { it.domainName.startsWith("${query.value}.") }
.toList().flatten()

private fun onDomainsFetched(query: DomainSuggestionsQuery, event: OnSuggestedDomains) {
// We want to treat `INVALID_QUERY` as if it's an empty result, so we'll ignore it
if (event.isError && event.error.type != SuggestDomainErrorType.INVALID_QUERY) {
tracker.trackErrorShown(
ERROR_CONTEXT,
event.error.type.toString(),
event.error.message
)
updateUiStateToContent(
query,
Error(
listState,
errorMessageResId = R.string.site_creation_fetch_suggestions_error_unknown
val emptyListMessage = UiStringRes(
R.string.new_site_creation_empty_domain_list_message
)
)
} else {
/**
* We would like to show the domains that matches the current query at the top. For this, we split the
* domains into two, one part for the domain names that start with the current query plus `.` and the
* other part for the others. We then combine them back again into a single list.
*/
val domains = event.suggestions.map(::parseSuggestion)
.partition { it.domainName.startsWith("${query.value}.") }
.toList().flatten()

val isInvalidQuery = event.isError && event.error.type == SuggestDomainErrorType.INVALID_QUERY
val emptyListMessage = UiStringRes(
if (isInvalidQuery) R.string.new_site_creation_empty_domain_list_message_invalid_query
else R.string.new_site_creation_empty_domain_list_message
)

updateUiStateToContent(query, Success(domains), emptyListMessage)
updateUiStateToContent(query, Success(domains), emptyListMessage)
}
}
}

private fun parseSuggestion(response: DomainSuggestionResponse): DomainModel = with(response) {
DomainModel(
domainName = domain_name,
isFree = is_free,
cost = cost.orEmpty(),
productId = product_id,
supportsPrivacy = supports_privacy,
private fun parseSuggestion(suggestion: DomainSuggestion): DomainModel = when (suggestion) {
is DomainSuggestion.Free -> DomainModel(
domainName = suggestion.v1.domainName,
isFree = true,
cost = suggestion.v1.cost,
productId = 0,
supportsPrivacy = false,
)
is DomainSuggestion.Paid -> DomainModel(
domainName = suggestion.v1.domainName,
isFree = false,
cost = suggestion.v1.cost,
productId = suggestion.v1.productId.toInt(),
supportsPrivacy = suggestion.v1.supportsPrivacy,
)
}

Expand Down Expand Up @@ -514,11 +479,6 @@ private fun createSearchInputUiState(
R.color.purple_50,
UiStringRes(R.string.site_creation_domain_tag_best_alternative),
)

object Sale : Tag(
R.color.yellow_50,
UiStringRes(R.string.site_creation_domain_tag_sale)
)
}

sealed class Cost(val title: UiString) {
Expand All @@ -530,13 +490,6 @@ private fun createSearchInputUiState(
val strikeoutTitle = UiStringText(titleCost)
val subtitle = UiStringRes(R.string.site_creation_domain_free_with_annual_plan)
}

data class OnSale(private val titleCost: String, private val strikeoutTitleCost: String) : Cost(
UiStringText(titleCost)
) {
val strikeoutTitle = UiStringText(strikeoutTitleCost)
val subtitle = UiStringRes(R.string.site_creation_domain_cost_sale)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import org.wordpress.android.ui.sitecreation.domains.SiteCreationDomainsViewMode
import org.wordpress.android.ui.sitecreation.domains.SiteCreationDomainsViewModel.ListItemUiState.New.DomainUiState.Cost
import org.wordpress.android.ui.sitecreation.domains.SiteCreationDomainsViewModel.ListItemUiState.New.DomainUiState.Tag.BestAlternative
import org.wordpress.android.ui.sitecreation.domains.SiteCreationDomainsViewModel.ListItemUiState.New.DomainUiState.Tag.Recommended
import org.wordpress.android.ui.sitecreation.domains.SiteCreationDomainsViewModel.ListItemUiState.New.DomainUiState.Tag.Sale
import org.wordpress.android.ui.sitecreation.domains.SiteCreationDomainsViewModel.ListItemUiState.New.DomainUiState.Tag.Unavailable

private val HighlightBgColor @Composable get() = colorScheme.primary.copy(0.1f)
Expand Down Expand Up @@ -107,14 +106,7 @@ fun DomainItem(uiState: DomainUiState): Unit = with(uiState) {
}
}
if (tags.none { it is Unavailable }) {
if (cost is Cost.OnSale) {
SalePrice(
cost.strikeoutTitle.asString() to cost.title.asString(),
cost.subtitle.asString(),
modifier = Modifier.padding(start = Margin.ExtraLarge.value)
)
}
else if (cost is Cost.Paid) {
if (cost is Cost.Paid) {
Plan(
cost.strikeoutTitle.asString() to cost.title.asString(),
modifier = Modifier.padding(start = Margin.ExtraLarge.value)
Expand Down Expand Up @@ -151,36 +143,6 @@ private fun Price(text: String, modifier: Modifier = Modifier) {
)
}

@Composable
private fun SalePrice(title: Pair<String, String>, subtitle: String, modifier: Modifier = Modifier) {
Column(
modifier,
horizontalAlignment = Alignment.End,
) {
title.let { (strikethroughText, normalText) ->
Row(verticalAlignment = Alignment.Bottom) {
Text(
strikethroughText,
color = SecondaryTextColor,
fontSize = SecondaryFontSize,
textDecoration = TextDecoration.LineThrough,
modifier = Modifier.padding(end = 4.dp)
)
Text(
normalText,
color = colorScheme.primary,
fontSize = PrimaryFontSize,
)
}
}
Text(
subtitle,
color = colorScheme.primary,
fontSize = SecondaryFontSize,
)
}
}

@Composable
private fun Plan(title: Pair<String, String>, modifier: Modifier = Modifier) {
Column(
Expand Down Expand Up @@ -213,7 +175,6 @@ private fun DomainItemPreview() {
},
cost = when {
it % 3 == 0 -> Cost.Paid("$${it * 5}")
it in 1..2 -> Cost.OnSale("$${it * 2}", "$${it * 3}")
else -> Cost.Free
},
tags = listOfNotNull(
Expand All @@ -223,7 +184,6 @@ private fun DomainItemPreview() {
2 -> BestAlternative
else -> null
},
if (it in 1..2) Sale else null,
),
isSelected = it == 5,
onClick = {}
Expand Down
Loading
Loading