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-1893 && MBL-1877: Separate rewards from project query #2173

Merged
merged 14 commits into from
Nov 26, 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
20 changes: 1 addition & 19 deletions app/src/main/graphql/fragments.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -104,27 +104,9 @@ fragment fullProject on Project {
prelaunchActivated
...tagsCreative
...tagsDiscovery
rewards {
rewards { # query within project the bare minimum for rewards
nodes {
... reward
allowedAddons {
pageInfo {
startCursor
}
}
items {
... rewardItems
}
... on Reward {
simpleShippingRulesExpanded {
cost
country
estimatedMax
estimatedMin
locationId
locationName
}
}
}
}
risks
Expand Down
27 changes: 27 additions & 0 deletions app/src/main/graphql/project.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@ query FetchProject($slug: String!) {
}
}

query FetchProjectRewards($slug: String!) {
project(slug: $slug) {
minPledge
rewards {
nodes {
... reward
allowedAddons {
pageInfo {
startCursor
}
}
items {
... rewardItems
}
simpleShippingRulesExpanded {
cost
country
estimatedMax
estimatedMin
locationId
locationName
}
}
}
}
}

query ProjectCreatorDetails($slug: String!) {
project(slug: $slug) {
creator {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fun PledgeData.shippingCostIfShipping(): Double {
var addOnsShippingCost = 0.0
this.addOns()?.map {
if (RewardUtils.shipsWorldwide(it) || RewardUtils.shipsToRestrictedLocations(it)) {
addOnsShippingCost += (it.shippingRules()?.first()?.cost() ?: 0.0) * (it.quantity() ?: 0)
addOnsShippingCost += (it.shippingRules()?.firstOrNull()?.cost() ?: 0.0) * (it.quantity() ?: 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The crash was due to using the reward and addOns from the backing object, where shipping locations information is not available

} else 0.0
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ import java.util.Collections

open class MockApolloClientV2 : ApolloClientTypeV2 {

override fun getRewardsFromProject(slug: String): io.reactivex.Observable<List<Reward>> {
return io.reactivex.Observable.just(emptyList())
}

override fun watchProject(project: Project): io.reactivex.Observable<Project> {
return io.reactivex.Observable.just(project.toBuilder().isStarred(true).build())
}
Expand Down
42 changes: 42 additions & 0 deletions app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import DeletePaymentSourceMutation
import ErroredBackingsQuery
import FetchCategoryQuery
import FetchProjectQuery
import FetchProjectRewardsQuery
import FetchProjectsQuery
import GetBackingQuery
import GetCommentQuery
Expand Down Expand Up @@ -52,6 +53,7 @@ import com.kickstarter.features.pledgedprojectsoverview.data.PledgedProjectsOver
import com.kickstarter.libs.utils.extensions.isNotNull
import com.kickstarter.libs.utils.extensions.toBoolean
import com.kickstarter.libs.utils.extensions.toProjectSort
import com.kickstarter.mock.factories.RewardFactory
import com.kickstarter.models.Backing
import com.kickstarter.models.Category
import com.kickstarter.models.Checkout
Expand Down Expand Up @@ -208,6 +210,7 @@ interface ApolloClientTypeV2 {
fun createOrUpdateBackingAddress(eventInput: CreateOrUpdateBackingAddressData): Observable<Boolean>
fun completeOrder(orderInput: CompleteOrderInput): Observable<CompleteOrderPayload>
fun getPledgedProjectsOverviewPledges(inputData: PledgedProjectsOverviewQueryData): Observable<PledgedProjectsOverviewEnvelope>
fun getRewardsFromProject(slug: String): Observable<List<Reward>>
}

private const val PAGE_SIZE = 25
Expand Down Expand Up @@ -714,6 +717,45 @@ class KSApolloClientV2(val service: ApolloClient, val gson: Gson) : ApolloClient
}.subscribeOn(Schedulers.io())
}

override fun getRewardsFromProject(slug: String): Observable<List<Reward>> {
return Observable.defer {
val ps = PublishSubject.create<List<Reward>>()
val query = FetchProjectRewardsQuery.builder()
.slug(slug)
.build()

this.service.query(query)
.enqueue(object : ApolloCall.Callback<FetchProjectRewardsQuery.Data>() {
override fun onFailure(e: ApolloException) {
ps.onError(e)
}

override fun onResponse(response: Response<FetchProjectRewardsQuery.Data>) {
if (response.hasErrors()) {
ps.onError(Exception(response.errors?.first()?.message))
}
response.data?.let { data ->
val rwList = data.project()?.rewards()?.nodes()?.map {
rewardTransformer(
rewardGr = it.fragments().reward(),
allowedAddons = it.allowedAddons().pageInfo().startCursor()?.isNotEmpty() ?: false,
rewardItems = complexRewardItemsTransformer(it.items()?.fragments()?.rewardItems()),
simpleShippingRules = it.simpleShippingRulesExpanded()
)
} ?: emptyList()
// - API does not provide the Reward no reward, we need to add it first
val minPledge = data.project()?.minPledge()?.toDouble() ?: 1.0
val modifiedRewards = rwList.toMutableList()
modifiedRewards.add(0, RewardFactory.noReward().toBuilder().minimum(minPledge).build())
Copy link
Contributor Author

@Arkariang Arkariang Nov 19, 2024

Choose a reason for hiding this comment

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

Adding reward no reward to the list of rewards. It is not provided by the API, but the minimum value to be pledged is configured in a project level.

ps.onNext(modifiedRewards.toList())
}
ps.onComplete()
}
})
return@defer ps
}
}

private fun getAddOnsFromProject(addOnsGr: GetProjectAddOnsQuery.AddOns): List<Reward> {
return addOnsGr.nodes()?.map { node ->
val shippingRulesGr =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ fun environmentalCommitmentTransformer(envCommit: fragment.EnvironmentalCommitme
fun rewardTransformer(
rewardGr: fragment.Reward,
shippingRulesExpanded: List<fragment.ShippingRule> = emptyList(),
simpleShippingRules: List<FullProject.SimpleShippingRulesExpanded> = emptyList(),
simpleShippingRules: List<FetchProjectRewardsQuery.SimpleShippingRulesExpanded> = emptyList(),
allowedAddons: Boolean = false,
rewardItems: List<RewardsItem> = emptyList(),
addOnItems: List<RewardsItem> = emptyList()
Expand Down Expand Up @@ -198,7 +198,7 @@ fun rewardTransformer(
.build()
}

fun simpleShippingRuleTransformer(simpleShippingRules: FullProject.SimpleShippingRulesExpanded): ShippingRule {
fun simpleShippingRuleTransformer(simpleShippingRules: FetchProjectRewardsQuery.SimpleShippingRulesExpanded): ShippingRule {
val id = decodeRelayId(simpleShippingRules.locationId()) ?: -1
val country = simpleShippingRules.country() ?: ""
val displayName = simpleShippingRules.locationName()
Expand Down Expand Up @@ -327,12 +327,8 @@ fun projectTransformer(projectFragment: FullProject?): Project {
val minPledge = projectFragment?.minPledge()?.toDouble() ?: 1.0
val rewards =
projectFragment?.rewards()?.nodes()?.map {
val shippingRules = it.simpleShippingRulesExpanded()
rewardTransformer(
it.fragments().reward(),
simpleShippingRules = shippingRules,
allowedAddons = it.allowedAddons().pageInfo().startCursor()?.isNotEmpty() ?: false,
rewardItems = complexRewardItemsTransformer(it.items()?.fragments()?.rewardItems())
it.fragments().reward()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ fun RewardCarouselScreen(
id = R.string.Back_it_because_you_believe_in_it
),
onRewardSelectClicked = { onRewardSelected(reward) },
isCTAButtonVisible = project.isAllowedToPledge()
isCTAButtonVisible = project.isAllowedToPledge(),
yourSelectionIsVisible = project.backing()?.isBacked(reward) ?: false,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Blue pill was not visible when the baked reward is a "Reward no reward"

)
} else {
KSRewardCard(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ class AddOnsViewModel(val environment: Environment, bundle: Bundle? = null) : Vi
bonusAmount = b.amount()
} else {
backedAddOns = b.addOns() ?: emptyList()
currentUserReward = b.reward() ?: currentUserReward
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was causing the issue mentioned here

bonusAmount = b.bonusAmount()
}
}
Expand Down Expand Up @@ -229,10 +228,14 @@ class AddOnsViewModel(val environment: Environment, bundle: Bundle? = null) : Vi
holder[it.id()] = it
}

// Take the backed AddOns, update with the backed AddOn information which will contain the backed quantity
backedAddOns.map {
holder[it.id()] = it
currentSelection[it.id()] = it.quantity() ?: 0
// Take the backed AddOns, update with matching addOn ID with the quantity information
backedAddOns.map { backedAddOn ->
val aux = holder[backedAddOn.id()]
if (aux != null) {
val updated = aux.toBuilder().quantity(backedAddOn.quantity()).build()
holder[backedAddOn.id()] = updated
Copy link
Contributor Author

@Arkariang Arkariang Nov 19, 2024

Choose a reason for hiding this comment

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

before we were using the information from the baked object, now we update the "quantity" field alone to not loose the "shipping rules" field

}
currentSelection[backedAddOn.id()] = backedAddOn.quantity() ?: 0
}

return holder.values.toList()
Expand Down Expand Up @@ -278,7 +281,7 @@ class AddOnsViewModel(val environment: Environment, bundle: Bundle? = null) : Vi
val selectedAddOns = mutableListOf<Reward>()
addOns.forEach {
val amount = currentSelection[it.id()]
if (amount != null) {
if (amount != null && amount > 0) {
selectedAddOns.add(it.toBuilder().quantity(amount).build())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ class CrowdfundCheckoutViewModel(val environment: Environment, bundle: Bundle? =
fun provideBundle(arguments: Bundle?) {
val pData = arguments?.getParcelable(ArgumentsKey.PLEDGE_PLEDGE_DATA) as PledgeData?
pledgeReason = arguments?.getSerializable(ArgumentsKey.PLEDGE_PLEDGE_REASON) as PledgeReason?
val flowContext = pledgeReason?.let { PledgeFlowContext.forPledgeReason(it) }

if (pData != null) {
pledgeData = pData
Expand All @@ -158,12 +157,11 @@ class CrowdfundCheckoutViewModel(val environment: Environment, bundle: Bundle? =
sharedPreferences
)

when (flowContext) {
PledgeFlowContext.NEW_PLEDGE,
PledgeFlowContext.CHANGE_REWARD -> getPledgeInfoFrom(pData)
PledgeFlowContext.MANAGE_REWARD,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"Manage_Reward" is used for both updating selected reward and changing reward, it describes the fact of having to present to the user RewardsCarousel not granular enough.
Exchanged to use PledgeReason instead of PledgeFlowContext as it's uniquely identified.

PledgeFlowContext.FIX_ERRORED_PLEDGE
-> {
when (pledgeReason) {
PledgeReason.PLEDGE,
PledgeReason.UPDATE_REWARD -> getPledgeInfoFrom(pData)
PledgeReason.UPDATE_PAYMENT,
PledgeReason.FIX_PLEDGE -> {
backing?.let { getPledgeInfoFrom(it) }
}
else -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,6 @@ interface ProjectPageViewModel {
.addToDisposable(disposables)

this.updatePledgeData
.distinctUntilChanged()
.subscribe {
this.showUpdatePledge.onNext(it)
}.addToDisposable(disposables)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,25 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx2.asFlow

data class RewardSelectionUIState(
val selectedReward: Reward = Reward.builder().build(),
val initialRewardIndex: Int = 0,
val project: ProjectData = ProjectData.builder().build(),
val project: ProjectData = ProjectData.builder().build()
)

class RewardsSelectionViewModel(private val environment: Environment, private var shippingRulesUseCase: GetShippingRulesUseCase? = null) : ViewModel() {

private val analytics = requireNotNull(environment.analytics())
private val apolloClient = requireNotNull(environment.apolloClientV2())
private val currentConfig = requireNotNull(environment.currentConfigV2()?.observable())

private lateinit var currentProjectData: ProjectData
private var pReason: PledgeReason? = null
Expand Down Expand Up @@ -83,26 +87,30 @@ class RewardsSelectionViewModel(private val environment: Environment, private va
indexOfBackedReward = indexOfBackedReward(project = projectData.project())
pReason = when {
previousUserBacking == null && projectData.project().isInPostCampaignPledgingPhase() == true -> PledgeReason.LATE_PLEDGE
previousUserBacking != null -> PledgeReason.UPDATE_PLEDGE
previousUserBacking != null -> PledgeReason.UPDATE_REWARD
previousUserBacking == null && projectData.project().isInPostCampaignPledgingPhase() == false -> PledgeReason.PLEDGE
else -> PledgeReason.PLEDGE
}

val project = projectData.project()
viewModelScope.launch {
emitCurrentState()
environment.currentConfigV2()?.observable()?.asFlow()?.collectLatest {
if (shippingRulesUseCase == null) {
shippingRulesUseCase = GetShippingRulesUseCase(
projectData.project(),
it,
viewModelScope,
Dispatchers.IO
)
apolloClient.getRewardsFromProject(project.slug() ?: "")
.asFlow()
.combine(currentConfig.asFlow()) { rewardsList, config ->
if (shippingRulesUseCase == null) {
shippingRulesUseCase = GetShippingRulesUseCase(
project = projectData.project(),
config = config,
projectRewards = rewardsList,
viewModelScope,
Dispatchers.IO
)
}
shippingRulesUseCase?.invoke()
emitShippingUIState()
}
shippingRulesUseCase?.invoke()

emitShippingUIState()
}
.catch { }
.collect()
}
}

Expand Down Expand Up @@ -209,7 +217,7 @@ class RewardsSelectionViewModel(private val environment: Environment, private va
fun shouldShowAlert(): Boolean {
val prevRw = previousUserBacking?.reward()
prevRw?.let {
if (pReason == PledgeReason.UPDATE_PLEDGE) {
if (pReason == PledgeReason.UPDATE_REWARD) {
return !previousUserBacking?.addOns().isNullOrEmpty() && prevRw.id() != newUserReward.id()
}
}
Expand Down
Loading