Skip to content

Commit

Permalink
MBL-1326 bonus support input (#2030)
Browse files Browse the repository at this point in the history
* Functionality minus currency symbol and styling

* Move currency symbol out of TextField into its own Text

* Wrap currency symbol and amount into a stylized right aligned row

* Separate currency symbol start and end

* Cursor brush

* Remove logs

* Fix lint

* Address feedback and stylize max pledge amount

* Remove unused util method

* Fix lint

* Fix max input amount error messaging

* Fix relationship between initialBonusSupport and totalBonusSupport

* Address feedback to move to util methods

* Couple of tests

* Oops lint

* Change variable name

---------

Co-authored-by: Isabel Martin <[email protected]>
Co-authored-by: mtgriego <[email protected]>
  • Loading branch information
3 people authored May 9, 2024
1 parent 4974fd2 commit 96dc1aa
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 53 deletions.
13 changes: 13 additions & 0 deletions app/src/main/java/com/kickstarter/libs/utils/RewardUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,17 @@ object RewardUtils {
else -> floor(seconds / 60.0 / 60.0 / 24.0).toInt()
}
}

/**
* Returns the finalBonusSupportAmount as either the initialBonusSupport OR the addedBonusSupport,
* depending on if the user chose to change the bonus amount from the original initialBonusAmount.
*
* The initialBonusSupport is the bonus amount the user initially sees upon landing on the Confirm
* Pledge Details screen. Most of the time initialBonusSupport will be 0, but for the case of No
* Reward, the initialBonusSupport is 1. If the user inputs a bonus amount, we use that as
* the finalBonusSupportAmount, otherwise we use the initialBonusSupport.
*/
fun getFinalBonusSupportAmount(addedBonusSupport: Double, initialBonusSupport: Double): Double {
return if (addedBonusSupport > 0) addedBonusSupport else initialBonusSupport
}
}
40 changes: 40 additions & 0 deletions app/src/main/java/com/kickstarter/libs/utils/RewardViewUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.text.style.RelativeSizeSpan
import android.util.Pair
import androidx.annotation.StringRes
import com.kickstarter.R
import com.kickstarter.libs.Environment
import com.kickstarter.libs.KSCurrency
import com.kickstarter.libs.KSString
import com.kickstarter.libs.models.Country
Expand Down Expand Up @@ -99,4 +100,43 @@ object RewardViewUtils {
)
return spannable
}

/**
* Returns the string for the error message a user receives when their inputted bonus amount causes
* the total pledge amount to exceed the max pledge amount:
*
* Enter an amount less than $X.
*
* where X is calculated as maxPledgeAmount - rewardAmount
*/
fun getMaxInputString(
context: Context,
selectedReward: Reward?,
maxPledgeAmount: Double,
totalAmount: Double,
totalBonusSupport: Double,
currencySymbolStartAndEnd: kotlin.Pair<String?, String?>,
environment: Environment?
): String {

// rewardAmount + totalBonusSupport = totalAmount
// totalAmount must be <= maxPledgeAmount

val maxInputAmount = if (selectedReward != null && RewardUtils.isNoReward(selectedReward)) {
maxPledgeAmount
} else {
val rewardAmount = totalAmount - totalBonusSupport
maxPledgeAmount - rewardAmount
}
val maxInputAmountWithCurrency =
(currencySymbolStartAndEnd.first ?: "") +
if (maxInputAmount % 1.0 == 0.0) maxInputAmount.toInt().toString()
else maxInputAmount.toString() + (currencySymbolStartAndEnd.second ?: "")

return environment?.ksString()?.format(
context.getString(R.string.Enter_an_amount_less_than_max_pledge), // TODO: MBL-1416 Copy should say less than or equal to
"max_pledge",
maxInputAmountWithCurrency
) ?: ""
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ class ProjectPageActivity :
val rewardsAndAddOns = confirmUiState.rewardsAndAddOns
val shippingAmount = confirmUiState.shippingAmount
val initialBonusAmount = confirmUiState.initialBonusSupportAmount
val totalBonusSupportAmount = confirmUiState.totalBonusSupportAmount
val totalBonusSupportAmount = confirmUiState.finalBonusSupportAmount
val maxPledgeAmount = confirmUiState.maxPledgeAmount
val minStepAmount = confirmUiState.minStepAmount
val confirmDetailsIsLoading = confirmUiState.isLoading
Expand Down Expand Up @@ -684,6 +684,9 @@ class ProjectPageActivity :
userEmail = userEmail,
onBonusSupportMinusClicked = { confirmDetailsViewModel.decrementBonusSupport() },
onBonusSupportPlusClicked = { confirmDetailsViewModel.incrementBonusSupport() },
onBonusSupportInputted = { input ->
confirmDetailsViewModel.inputBonusSupport(input)
},
selectedAddOnsMap = selectedAddOnsMap,
onPledgeCtaClicked = { selectedCard ->
selectedCard?.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -14,21 +15,31 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.integerResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
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.ProjectViewUtils
import com.kickstarter.libs.utils.RewardViewUtils
import com.kickstarter.libs.utils.extensions.isNotNull
import com.kickstarter.libs.utils.extensions.parseToDouble
import com.kickstarter.models.Project
import com.kickstarter.models.Reward
import com.kickstarter.models.ShippingRule
Expand Down Expand Up @@ -64,7 +75,8 @@ private fun ConfirmPledgeDetailsScreenPreviewNoRewards() {
minPledgeStep = 1.0,
onShippingRuleSelected = {},
onBonusSupportMinusClicked = {},
onBonusSupportPlusClicked = {}
onBonusSupportPlusClicked = {},
onBonusSupportInputted = {}
)
}
}
Expand All @@ -90,7 +102,8 @@ private fun ConfirmPledgeDetailsScreenPreviewNoRewardsWarning() {
minPledgeStep = 1.0,
onShippingRuleSelected = {},
onBonusSupportMinusClicked = {},
onBonusSupportPlusClicked = {}
onBonusSupportPlusClicked = {},
onBonusSupportInputted = {}
)
}
}
Expand Down Expand Up @@ -121,7 +134,8 @@ private fun ConfirmPledgeDetailsScreenPreviewNoAddOnsOrBonusSupport() {
countryList = listOf(ShippingRule.builder().build()),
onShippingRuleSelected = {},
onBonusSupportMinusClicked = {},
onBonusSupportPlusClicked = {}
onBonusSupportPlusClicked = {},
onBonusSupportInputted = {}
)
}
}
Expand Down Expand Up @@ -151,7 +165,8 @@ private fun ConfirmPledgeDetailsScreenPreviewAddOnsOnly() {
minPledgeStep = 1.0,
onShippingRuleSelected = {},
onBonusSupportMinusClicked = {},
onBonusSupportPlusClicked = {}
onBonusSupportPlusClicked = {},
onBonusSupportInputted = {}
)
}
}
Expand Down Expand Up @@ -182,7 +197,8 @@ private fun ConfirmPledgeDetailsScreenPreviewBonusSupportOnly() {
countryList = listOf(ShippingRule.builder().build()),
onShippingRuleSelected = {},
onBonusSupportMinusClicked = {},
onBonusSupportPlusClicked = {}
onBonusSupportPlusClicked = {},
onBonusSupportInputted = {}
)
}
}
Expand Down Expand Up @@ -212,7 +228,8 @@ private fun ConfirmPledgeDetailsScreenPreviewAddOnsAndBonusSupport() {
minPledgeStep = 1.0,
onShippingRuleSelected = {},
onBonusSupportMinusClicked = {},
onBonusSupportPlusClicked = {}
onBonusSupportPlusClicked = {},
onBonusSupportInputted = {}
)
}
}
Expand All @@ -238,7 +255,8 @@ fun ConfirmPledgeDetailsScreen(
minPledgeStep: Double,
isLoading: Boolean = false,
onBonusSupportPlusClicked: () -> Unit,
onBonusSupportMinusClicked: () -> Unit
onBonusSupportMinusClicked: () -> Unit,
onBonusSupportInputted: (input: Double) -> Unit
) {
val interactionSource = remember {
MutableInteractionSource()
Expand Down Expand Up @@ -293,6 +311,21 @@ fun ConfirmPledgeDetailsScreen(
).toString()
} ?: ""

// Currency symbol, which can be positioned at start or end of amount depending on country
val currencySymbolStartAndEnd = environment?.ksCurrency()?.let {
val symbolAndStart = ProjectViewUtils.currencySymbolAndPosition(
project,
it
)
val symbol = symbolAndStart.first
val symbolAtStart = symbolAndStart.second
if (symbolAtStart) {
Pair(symbol.toString(), null)
} else {
Pair(null, symbol.toString())
}
} ?: Pair(null, null)

val deliveryDateString = if (selectedReward?.estimatedDeliveryOn().isNotNull()) {
DateTimeUtils.estimatedDeliveryOn(
requireNotNull(
Expand All @@ -301,11 +334,7 @@ fun ConfirmPledgeDetailsScreen(
)
} else ""

val maxPledgeString = environment?.ksString()?.format(
stringResource(R.string.Enter_an_amount_less_than_max_pledge),
"max_pledge",
maxPledgeAmount.toString()
) ?: ""
val maxInputString = RewardViewUtils.getMaxInputString(LocalContext.current, selectedReward, maxPledgeAmount, totalAmount, totalBonusSupport, currencySymbolStartAndEnd, environment)

Box(
modifier = Modifier.fillMaxSize(),
Expand Down Expand Up @@ -428,20 +457,28 @@ fun ConfirmPledgeDetailsScreen(
item {
BonusSupportContainer(
isForNoRewardPledge = rewardsList.isEmpty(),
initialValue = initialBonusSupportString,
totalBonusAmount = totalBonusSupportString,
initialBonusSupport = initialBonusSupport,
totalBonusSupport = totalBonusSupport,
currencySymbolAtStart = currencySymbolStartAndEnd.first,
currencySymbolAtEnd = currencySymbolStartAndEnd.second,
canAddMore = totalAmount + minPledgeStep <= maxPledgeAmount,
onBonusSupportPlusClicked = onBonusSupportPlusClicked,
onBonusSupportMinusClicked = onBonusSupportMinusClicked
onBonusSupportMinusClicked = onBonusSupportMinusClicked,
onBonusSupportInputted = onBonusSupportInputted
)

if (totalAmount >= maxPledgeAmount) {
Spacer(modifier = Modifier.height(dimensions.paddingXSmall))

if (totalAmount > maxPledgeAmount) {
Text(
text = maxPledgeString,
style = typography.headline,
color = colors.textAccentRedBold
text = maxInputString,
textAlign = TextAlign.Right,
style = typography.footnoteMedium,
color = colors.textAccentRed,
modifier = Modifier
.fillMaxWidth()
.padding(
start = dimensions.paddingMedium,
end = dimensions.paddingMedium,
)
)
}
}
Expand Down Expand Up @@ -518,12 +555,17 @@ fun ConfirmPledgeDetailsScreen(
@Composable
fun BonusSupportContainer(
isForNoRewardPledge: Boolean,
initialValue: String,
totalBonusAmount: String,
initialBonusSupport: Double,
totalBonusSupport: Double,
currencySymbolAtStart: String?,
currencySymbolAtEnd: String?,
canAddMore: Boolean,
onBonusSupportPlusClicked: () -> Unit,
onBonusSupportMinusClicked: () -> Unit
onBonusSupportMinusClicked: () -> Unit,
onBonusSupportInputted: (input: Double) -> Unit
) {
val bonusAmountMaxDigits = integerResource(R.integer.max_length)

Column(
modifier = Modifier.padding(all = dimensions.paddingMedium)
) {
Expand All @@ -543,6 +585,7 @@ fun BonusSupportContainer(
style = typography.body2,
color = colors.textSecondary
)
Spacer(modifier = Modifier.height(dimensions.paddingSmall))
}

Row(
Expand All @@ -553,7 +596,7 @@ fun BonusSupportContainer(
onPlusClicked = onBonusSupportPlusClicked,
isPlusEnabled = canAddMore,
onMinusClicked = onBonusSupportMinusClicked,
isMinusEnabled = initialValue != totalBonusAmount,
isMinusEnabled = initialBonusSupport != totalBonusSupport,
enabledButtonBackgroundColor = colors.kds_white
)

Expand All @@ -565,22 +608,44 @@ fun BonusSupportContainer(
Spacer(modifier = Modifier.width(dimensions.paddingMediumSmall))
}

Text(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(
color = colors.kds_white,
shape = shapes.small
)
.padding(
start = dimensions.paddingMediumSmall,
top = dimensions.paddingMediumSmall,
bottom = dimensions.paddingMediumSmall,
end = dimensions.paddingMediumSmall
start = dimensions.paddingXSmall,
top = dimensions.paddingXSmall,
bottom = dimensions.paddingXSmall,
end = dimensions.paddingXSmall
),
text = totalBonusAmount,
style = typography.headline,
color = colors.textAccentGreen
)

) {
Text(
text = currencySymbolAtStart ?: "",
color = colors.textAccentGreen
)
BasicTextField(
modifier = Modifier.width(IntrinsicSize.Min),
value = if (totalBonusSupport % 1.0 == 0.0) totalBonusSupport.toInt().toString() else totalBonusSupport.toString(),
onValueChange = {
if (it.length <= bonusAmountMaxDigits) onBonusSupportInputted(it.parseToDouble())
},
textStyle = typography.title1.copy(color = colors.textAccentGreen),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
cursorBrush = SolidColor(colors.iconSubtle)

)
Text(
text = currencySymbolAtEnd ?: "",
color = colors.textAccentGreen
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ private fun ProjectPledgeButtonAndContainerPreview() {
selectedRewardAndAddOnList = listOf(),
onBonusSupportMinusClicked = {},
onBonusSupportPlusClicked = {},
onBonusSupportInputted = {},
storedCards = listOf(),
userEmail = "[email protected]",
onPledgeCtaClicked = {},
Expand Down Expand Up @@ -154,6 +155,7 @@ fun ProjectPledgeButtonAndFragmentContainer(
selectedRewardAndAddOnList: List<Reward>,
onBonusSupportPlusClicked: () -> Unit,
onBonusSupportMinusClicked: () -> Unit,
onBonusSupportInputted: (input: Double) -> Unit,
storedCards: List<StoredCard>,
userEmail: String,
onPledgeCtaClicked: (selectedCard: StoredCard?) -> Unit,
Expand Down Expand Up @@ -312,6 +314,7 @@ fun ProjectPledgeButtonAndFragmentContainer(
},
onBonusSupportPlusClicked = onBonusSupportPlusClicked,
onBonusSupportMinusClicked = onBonusSupportMinusClicked,
onBonusSupportInputted = onBonusSupportInputted,
isLoading = isLoading,
)
}
Expand Down
Loading

0 comments on commit 96dc1aa

Please sign in to comment.