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

MBL-1573: Checkout Screen (Reward noReward for crowdfund and late pledges UI) #2104

Merged
merged 6 commits into from
Aug 19, 2024
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
5 changes: 5 additions & 0 deletions app/src/main/graphql/fragments.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ fragment reward on Reward {
startsAt
endsAt
rewardType
allowedAddons {
nodes {
id
}
}
localReceiptLocation {
... location
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.kickstarter.features.pledgedprojectsoverview.data.PledgedProjectsOver
import com.kickstarter.features.pledgedprojectsoverview.data.PledgedProjectsOverviewQueryData
import com.kickstarter.features.pledgedprojectsoverview.ui.PPOCardViewType
import com.kickstarter.libs.Permission
import com.kickstarter.libs.utils.extensions.isNotNull
import com.kickstarter.libs.utils.extensions.negate
import com.kickstarter.mock.factories.RewardFactory
import com.kickstarter.models.AiDisclosure
Expand Down Expand Up @@ -713,6 +714,7 @@ fun backingTransformer(backingGr: fragment.Backing?): Backing {
val reward = backingGr?.reward()?.fragments()?.reward()?.let { reward ->
return@let rewardTransformer(
reward,
allowedAddons = reward.allowedAddons().isNotNull(),
rewardItems = complexRewardItemsTransformer(items?.fragments()?.rewardItems())
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import com.kickstarter.R
import com.kickstarter.libs.Environment
import com.kickstarter.libs.KSString
import com.kickstarter.libs.utils.DateTimeUtils
import com.kickstarter.libs.utils.RewardUtils
import com.kickstarter.libs.utils.RewardViewUtils
import com.kickstarter.libs.utils.extensions.acceptedCardType
import com.kickstarter.libs.utils.extensions.hrefUrlFromTranslation
Expand Down Expand Up @@ -457,7 +458,8 @@ fun CheckoutScreen(

Spacer(modifier = Modifier.height(dimensions.paddingMediumSmall))

if (rewardsList.isNotEmpty()) {
val isNoReward = selectedReward?.let { RewardUtils.isNoReward(it) } ?: false
if (!isNoReward) {
ItemizedRewardListContainer(
ksString = ksString,
rewardsList = rewardsList,
Expand All @@ -472,11 +474,12 @@ fun CheckoutScreen(
rewardsHaveShippables = rewardsHaveShippables
)
} else {
// - For noReward, totalAmount = bonusAmount as there is no reward
ItemizedRewardListContainer(
totalAmount = totalAmountString,
totalAmountCurrencyConverted = aboutTotalString,
initialBonusSupport = initialBonusSupportString,
totalBonusSupport = totalBonusSupportString,
totalBonusSupport = totalAmountString,
shippingAmount = shippingAmount,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ fun RewardCarouselScreen(
) { reward ->

val ctaButtonEnabled = when {
RewardUtils.isNoReward(reward) && (project.postCampaignPledgingEnabled() ?: false && project.isInPostCampaignPledgingPhase() ?: false) -> true
RewardUtils.isNoReward(reward) -> true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming now the Rewards carousel screen is being used un both crowdfund and late pledges a Reward no reward is always available. But @leighdouglas @mtgriego let me know if I'm missing any use case here

!reward.hasAddons() && backing?.isBacked(reward) != true -> true
backing?.rewardId() != reward.id() && RewardUtils.isAvailable(
project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.libs.utils.extensions.getEnvironment
import com.kickstarter.libs.utils.extensions.reduce
import com.kickstarter.libs.utils.extensions.selectPledgeFragment
import com.kickstarter.mock.factories.ShippingRuleFactory
import com.kickstarter.ui.activities.compose.projectpage.RewardCarouselScreen
import com.kickstarter.ui.compose.designsystem.KSTheme
import com.kickstarter.ui.compose.designsystem.KickstarterApp
Expand Down Expand Up @@ -95,10 +94,6 @@ class RewardsFragment : Fragment() {
initialValue = ShippingRulesState()
).value

if (rules.selectedShippingRule != ShippingRuleFactory.emptyShippingRule()) {
viewModel.setInitialShippingRule(rules.selectedShippingRule)
}

val rewards = rules.filteredRw
val listState = rememberLazyListState()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ import com.kickstarter.ui.compose.designsystem.KSTheme.colors
import com.kickstarter.ui.compose.designsystem.KSTheme.dimensions
import com.kickstarter.ui.compose.designsystem.KSTheme.typography

@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun ItemizedContainerForNoRewardPreview() {
KSTheme {
Scaffold(
backgroundColor = KSTheme.colors.backgroundAccentGraySubtle
) { padding ->
ItemizedRewardListContainer(
modifier = Modifier.padding(paddingValues = padding),
totalAmount = "US$ 1",
totalAmountCurrencyConverted = "About CA$ 1.38",
initialBonusSupport = "US$ 0",
totalBonusSupport = "US$ 1",
shippingAmount = -1.0,
)
}
}
}

@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import java.util.Locale

class RewardsFragmentViewModel {
Expand Down Expand Up @@ -259,7 +262,14 @@ class RewardsFragmentViewModel {
Dispatchers.IO
)
}
shippingRulesUseCase?.invoke()
shippingRulesUseCase?.let { useCaseState ->
useCaseState.invoke()
useCaseState.getScope().launch(useCaseState.getDispatcher()) {
shippingRulesUseCase?.shippingRulesState?.collectLatest {
selectedShippingRule = it.selectedShippingRule
}
}
}
return@combineLatest Observable.empty<Any>()
}.subscribe().addToDisposable(disposables)
}
Expand Down Expand Up @@ -334,11 +344,6 @@ class RewardsFragmentViewModel {

override fun selectedShippingRule(shippingRule: ShippingRule) {
this.shippingRulesUseCase?.filterBySelectedRule(shippingRule)
this.selectedShippingRule = shippingRule
}

fun setInitialShippingRule(rule: ShippingRule) {
this.selectedShippingRule = rule
}

override fun onCleared() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,22 @@ class AddOnsViewModel(val environment: Environment, bundle: Bundle? = null) : Vi
}

backing = pledgeData?.projectData()?.backing() ?: project.backing()
backing?.let { b ->
// - backed a reward no reward
if (b.reward() == null && b.amount().isNotNull()) {
currentUserReward = RewardFactory.noReward().toBuilder().pledgeAmount(b.amount()).build()
bonusAmount = b.amount()
} else {
currentUserReward = b.reward() ?: currentUserReward
bonusAmount = b.bonusAmount()
backedAddOns = b.addOns() ?: emptyList()

if (pReason == PledgeReason.UPDATE_REWARD && backing?.reward()?.id() != currentUserReward.id()) {
// Do nothing, user is selecting a different reward/addOns ...
} else {
// User is selecting a same reward with AddOns, might just adding/deleting addOns, bonus support ...
backing?.let { b ->
// - backed a reward no reward
if (b.reward() == null && b.amount().isNotNull()) {
currentUserReward =
RewardFactory.noReward().toBuilder().pledgeAmount(b.amount()).build()
bonusAmount = b.amount()
} else {
backedAddOns = b.addOns() ?: emptyList()
currentUserReward = b.reward() ?: currentUserReward
bonusAmount = b.bonusAmount()
}
}
}

Expand Down Expand Up @@ -171,7 +178,7 @@ class AddOnsViewModel(val environment: Environment, bundle: Bundle? = null) : Vi

private fun getAddOns(selectedShippingRule: ShippingRule) {
// - Do not execute call unless reward has addOns
if (currentUserReward.hasAddons() || backing?.addOns().isNotNull()) {
if (currentUserReward.hasAddons()) {
scope.launch(dispatcher) {
apolloClient
.getProjectAddOns(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ class CrowdfundCheckoutViewModel(val environment: Environment, bundle: Bundle? =
bonusAmount = (backing.bonusAmount() ?: 0.0).toDouble()
totalAmount = (backing.amount() ?: 0.0).toDouble()

// - User was backing reward no reward
if (backing.reward() == null) {
bonusAmount = 0.0
}

checkoutData = CheckoutData.builder()
.amount(totalAmount)
.paymentType(CreditCardPaymentType.CREDIT_CARD)
Expand All @@ -217,24 +222,34 @@ class CrowdfundCheckoutViewModel(val environment: Environment, bundle: Bundle? =

private fun getPledgeInfoFrom(pData: PledgeData) {
selectedRewards = pData.rewardsAndAddOnsList()
pledgeData = pData
refTag = RefTagUtils.storedCookieRefTagForProject(
project,
cookieManager,
sharedPreferences
)
if (selectedRewards.isNotEmpty()) {
val isNoReward = RewardUtils.isNoReward(selectedRewards.first())
pledgeData = pData
refTag = RefTagUtils.storedCookieRefTagForProject(
project,
cookieManager,
sharedPreferences
)
shippingRule = pData.shippingRule()

shippingRule = pData.shippingRule()
shippingAmount = pData.shippingCostIfShipping()
bonusAmount = pData.bonusAmount()
totalAmount = pData.checkoutTotalAmount()
if (!isNoReward) {
shippingAmount = pData.shippingCostIfShipping()
bonusAmount = pData.bonusAmount()
totalAmount = pData.checkoutTotalAmount()
}

checkoutData = CheckoutData.builder()
.amount(pData.pledgeAmountTotal())
.paymentType(CreditCardPaymentType.CREDIT_CARD)
.bonusAmount(bonusAmount)
.shippingAmount(pData.shippingCostIfShipping())
.build()
if (isNoReward) {
totalAmount = selectedRewards.first().pledgeAmount() + pData.bonusAmount()
bonusAmount = 0.0
}

checkoutData = CheckoutData.builder()
.amount(pData.pledgeAmountTotal())
.paymentType(CreditCardPaymentType.CREDIT_CARD)
.bonusAmount(bonusAmount)
.shippingAmount(pData.shippingCostIfShipping())
.build()
}
}

fun provideErrorAction(errorAction: (message: String?) -> Unit) {
Expand Down Expand Up @@ -357,10 +372,15 @@ class CrowdfundCheckoutViewModel(val environment: Environment, bundle: Bundle? =
analytics.trackPledgeSubmitCTA(requireNotNull(checkoutData), requireNotNull(pledgeData))
}

val shouldNotSendId = pledgeData?.reward()?.let {
RewardUtils.isDigital(it) || RewardUtils.isNoReward(it) || RewardUtils.isLocalPickup(it)
} ?: true

val locationID = pledgeData?.shippingRule()?.location()?.id()?.toString()
val backingData = selectedPaymentMethod.getBackingData(
proj = project,
amount = pledgeData?.checkoutTotalAmount().toString(),
locationId = pledgeData?.shippingRule()?.location()?.id()?.toString(),
locationId = if (shouldNotSendId) null else locationID,
rewards = RewardUtils.extendAddOns(pledgeData?.rewardsAndAddOnsList() ?: emptyList<Reward>()),
cookieRefTag = refTag
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.kickstarter.libs.Environment
import com.kickstarter.libs.utils.RewardUtils
import com.kickstarter.libs.utils.extensions.checkoutTotalAmount
import com.kickstarter.libs.utils.extensions.isNotNull
import com.kickstarter.libs.utils.extensions.locationId
Expand Down Expand Up @@ -417,8 +418,8 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() {

private fun createCheckout() {
this.pledgeData?.let { pData ->
val locationId = pData.locationId()
val rewards = pData.rewardsAndAddOnsList()
val locationId = if (!RewardUtils.isNoReward(pData.reward())) pData.locationId() else null
val rewards = if (!RewardUtils.isNoReward(pData.reward())) pData.rewardsAndAddOnsList() else emptyList()
val totalPledge = pData.checkoutTotalAmount()

this.pledgeData?.projectData()?.let { projectData ->
Expand All @@ -431,7 +432,7 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() {
CreateCheckoutData(
project = projectData.project(),
amount = totalPledge.toString(),
locationId = locationId.toString(),
locationId = locationId?.toString(),
rewardsIds = rewards,
refTag = projectData.refTagFromIntent()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ class RewardsSelectionViewModel(private val environment: Environment, private va
fun selectedShippingRule(shippingRule: ShippingRule) {
viewModelScope.launch {
shippingRulesUseCase?.filterBySelectedRule(shippingRule)
selectedShippingRule = shippingRule
emitShippingUIState()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,6 @@ class GetShippingRulesUseCase(
val avShipMap = allAvailableRulesForProject
emitCurrentState(isLoading = true)

// - If project backing default shipping Rule is backed shipping rule
if (project.isBacking() && project?.backing()?.locationId().isNotNull()) {
defaultShippingRule = getDefaultShippingRule(
avShipMap,
project
)
}

if (rewardsByShippingType.isNotEmpty()) {
rewardsByShippingType.forEachIndexed { index, reward ->

Expand Down Expand Up @@ -136,8 +128,12 @@ class GetShippingRulesUseCase(
}
}

fun getScope() = this.scope
fun getDispatcher() = this.dispatcher

fun filterBySelectedRule(shippingRule: ShippingRule) {
scope.launch(dispatcher) {
defaultShippingRule = shippingRule
emitCurrentState(isLoading = true)
delay(500) // Added delay due to the filtering happening too fast for the user to perceive the loading state
filterRewardsByLocation(allAvailableRulesForProject, shippingRule, projectRewards)
Expand Down Expand Up @@ -208,6 +204,10 @@ class GetShippingRulesUseCase(
filteredRewards.add(rw)
}

if (RewardUtils.isDigital(rw)) {
filteredRewards.add(rw)
}

// - If shipping is restricted, make sure the reward is able to ship to selected rule
if (RewardUtils.shipsToRestrictedLocations(rw)) {
if (isIsValidRule != null) {
Expand All @@ -231,12 +231,25 @@ class GetShippingRulesUseCase(
shippingRules: MutableMap<Long, ShippingRule>,
project: Project
): ShippingRule =
if (project.isBacking()) ShippingRule.builder()
if (project.isBacking() && project.backing()?.location().isNotNull()) ShippingRule.builder()
.apply {
val backing = project.backing()
val locationId = project.backing()?.locationId() ?: 0L
val locationName = project.backing()?.locationName() ?: ""

this.location(Location.Builder().id(locationId).name(locationName).displayableName(locationName).build())
this.id(locationId)
val reward = backing?.reward()?.let {
if (RewardUtils.shipsToRestrictedLocations(it)) {
val rule = backing?.reward()?.shippingRules()
?.first { it.location()?.id() == locationId }
this.location(rule?.location())
this.id(rule?.id())
this.cost(rule?.cost() ?: 0.0)
}
if (RewardUtils.shipsWorldwide(it)) {
val locationName = project.backing()?.locationName() ?: ""
this.location(Location.Builder().id(locationId).name(locationName).displayableName(locationName).build())
this.cost(it.shippingRules()?.first()?.cost() ?: 0.0)
}
}
}
.build()
else config?.getDefaultLocationFrom(shippingRules.values.toList()) ?: ShippingRule.builder()
Expand Down
Loading