Skip to content

Commit

Permalink
Issue mozilla-mobile#9457: Add support to display a credit card picke…
Browse files Browse the repository at this point in the history
…r to handle a SelectCreditCard request
  • Loading branch information
gabrielluong committed May 11, 2021
1 parent 8d04bb4 commit 077b1bb
Show file tree
Hide file tree
Showing 11 changed files with 830 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ class PromptRequestTest {
var onConfirmCalled = false
var confirmedCreditCard: CreditCard? = null

val selectCreditCardPrompt = SelectCreditCard(
val selectCreditCardRequest = SelectCreditCard(
creditCards = listOf(creditCard),
onDismiss = {
onDismissCalled = true
Expand All @@ -284,9 +284,9 @@ class PromptRequestTest {
}
)

assertEquals(selectCreditCardPrompt.creditCards, listOf(creditCard))
assertEquals(selectCreditCardRequest.creditCards, listOf(creditCard))

selectCreditCardPrompt.onConfirm(creditCard)
selectCreditCardRequest.onConfirm(creditCard)

assertTrue(onConfirmCalled)
assertFalse(onDismissCalled)
Expand All @@ -295,7 +295,7 @@ class PromptRequestTest {
onConfirmCalled = false
confirmedCreditCard = null

selectCreditCardPrompt.onDismiss()
selectCreditCardRequest.onDismiss()

assertTrue(onDismissCalled)
assertFalse(onConfirmCalled)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.CreditCard
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.Alert
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication
Expand All @@ -44,6 +45,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection
import mozilla.components.concept.storage.Login
import mozilla.components.concept.storage.LoginValidationDelegate
import mozilla.components.feature.prompts.concept.SelectablePromptView
import mozilla.components.feature.prompts.creditcard.CreditCardPicker
import mozilla.components.feature.prompts.dialog.AlertDialogFragment
import mozilla.components.feature.prompts.dialog.AuthenticationDialogFragment
import mozilla.components.feature.prompts.dialog.ChoiceDialogFragment
Expand Down Expand Up @@ -112,6 +114,10 @@ internal const val FRAGMENT_TAG = "mozac_feature_prompt_dialog"
* selectable prompt list of login options.
* @property onManageLogins A callback invoked when a user selects "manage logins" from the
* select login prompt.
* @property creditCardPickerView The [SelectablePromptView] used for [CreditCardPicker] to display
* a selectable prompt list of credit card options.
* @property onManageCreditCards A callback invoked when a user selects "Manage credit cards" from
* the select credit card prompt.
* @property onNeedToRequestPermissions A callback invoked when permissions
* need to be requested before a prompt (e.g. a file picker) can be displayed.
* Once the request is completed, [onPermissionsResult] needs to be invoked.
Expand All @@ -128,6 +134,8 @@ class PromptFeature private constructor(
override val loginExceptionStorage: LoginExceptions? = null,
private val loginPickerView: SelectablePromptView<Login>? = null,
private val onManageLogins: () -> Unit = {},
private val creditCardPickerView: SelectablePromptView<CreditCard>? = null,
private val onManageCreditCards: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : LifecycleAwareFeature, PermissionsFeature, Prompter, ActivityResultHandler,
UserInteractionHandler {
Expand Down Expand Up @@ -155,6 +163,8 @@ class PromptFeature private constructor(
loginExceptionStorage: LoginExceptions? = null,
loginPickerView: SelectablePromptView<Login>? = null,
onManageLogins: () -> Unit = {},
creditCardPickerView: SelectablePromptView<CreditCard>? = null,
onManageCreditCards: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
container = PromptContainer.Activity(activity),
Expand All @@ -167,7 +177,9 @@ class PromptFeature private constructor(
loginExceptionStorage = loginExceptionStorage,
onNeedToRequestPermissions = onNeedToRequestPermissions,
loginPickerView = loginPickerView,
onManageLogins = onManageLogins
onManageLogins = onManageLogins,
creditCardPickerView = creditCardPickerView,
onManageCreditCards = onManageCreditCards
)

constructor(
Expand All @@ -181,6 +193,8 @@ class PromptFeature private constructor(
loginExceptionStorage: LoginExceptions? = null,
loginPickerView: SelectablePromptView<Login>? = null,
onManageLogins: () -> Unit = {},
creditCardPickerView: SelectablePromptView<CreditCard>? = null,
onManageCreditCards: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
container = PromptContainer.Fragment(fragment),
Expand All @@ -193,7 +207,9 @@ class PromptFeature private constructor(
loginExceptionStorage = loginExceptionStorage,
onNeedToRequestPermissions = onNeedToRequestPermissions,
loginPickerView = loginPickerView,
onManageLogins = onManageLogins
onManageLogins = onManageLogins,
creditCardPickerView = creditCardPickerView,
onManageCreditCards = onManageCreditCards
)

@Deprecated("Pass only activity or fragment instead")
Expand All @@ -205,6 +221,8 @@ class PromptFeature private constructor(
fragmentManager: FragmentManager,
loginPickerView: SelectablePromptView<Login>? = null,
onManageLogins: () -> Unit = {},
creditCardPickerView: SelectablePromptView<CreditCard>? = null,
onManageCreditCards: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
container = activity?.let { PromptContainer.Activity(it) }
Expand All @@ -220,7 +238,9 @@ class PromptFeature private constructor(
loginValidationDelegate = null,
onNeedToRequestPermissions = onNeedToRequestPermissions,
loginPickerView = loginPickerView,
onManageLogins = onManageLogins
onManageLogins = onManageLogins,
creditCardPickerView = creditCardPickerView,
onManageCreditCards = onManageCreditCards
)

private val filePicker = FilePicker(container, store, customTabId, onNeedToRequestPermissions)
Expand All @@ -229,6 +249,10 @@ class PromptFeature private constructor(
internal var loginPicker =
loginPickerView?.let { LoginPicker(store, it, onManageLogins, customTabId) }

@VisibleForTesting(otherwise = PRIVATE)
internal var creditCardPicker =
creditCardPickerView?.let { CreditCardPicker(store, it, onManageCreditCards, customTabId) }

override val onNeedToRequestPermissions
get() = filePicker.onNeedToRequestPermissions

Expand All @@ -248,15 +272,23 @@ class PromptFeature private constructor(
.collect { state ->
state?.content?.let {
if (it.promptRequest != activePromptRequest) {
// Dismiss any active select login or credit card prompt if it does
// not match the current prompt request for the session.
if (activePromptRequest is SelectLoginPrompt) {
loginPicker?.dismissCurrentLoginSelect(activePromptRequest as SelectLoginPrompt)
} else if (activePromptRequest is SelectCreditCard) {
creditCardPicker?.dismissSelectCreditCardRequest(
activePromptRequest as SelectCreditCard
)
}

onPromptRequested(state)
} else if (!it.loading) {
promptAbuserDetector.resetJSAlertAbuseState()
} else if (it.loading) {
dismissLoginSelectPrompt()
dismissSelectPrompts()
}

activePromptRequest = it.promptRequest
}
}
Expand All @@ -270,7 +302,7 @@ class PromptFeature private constructor(
state.findTabOrCustomTabOrSelectedTab(customTabId)?.content?.url
)
}.collect {
dismissLoginSelectPrompt()
dismissSelectPrompts()

val prompt = activePrompt?.get()
if (prompt?.shouldDismissOnLoad() == true) {
Expand All @@ -294,11 +326,11 @@ class PromptFeature private constructor(
dismissPromptScope?.cancel()

// Dismisses the logins prompt so that it can appear on another tab
dismissLoginSelectPrompt()
dismissSelectPrompts()
}

override fun onBackPressed(): Boolean {
return dismissLoginSelectPrompt()
return dismissSelectPrompts()
}

/**
Expand Down Expand Up @@ -336,11 +368,14 @@ class PromptFeature private constructor(
when (promptRequest) {
is File -> filePicker.handleFileRequest(promptRequest)
is Share -> handleShareRequest(promptRequest, session)
is SelectCreditCard -> {
if (promptRequest.creditCards.isNotEmpty()) {
creditCardPicker?.handleSelectCreditCardRequest(promptRequest)
}
}
is SelectLoginPrompt -> {
if (promptRequest.logins.isNotEmpty()) {
loginPicker?.handleSelectLoginRequest(
promptRequest
)
loginPicker?.handleSelectLoginRequest(promptRequest)
}
}
else -> handleDialogsRequest(promptRequest, session)
Expand Down Expand Up @@ -533,7 +568,6 @@ class PromptFeature private constructor(
}

is TimeSelection -> {

val selectionType = when (promptRequest.type) {
TimeSelection.Type.DATE -> TimePickerDialogFragment.SELECTION_TYPE_DATE
TimeSelection.Type.DATE_AND_TIME -> TimePickerDialogFragment.SELECTION_TYPE_DATE_AND_TIME
Expand Down Expand Up @@ -584,7 +618,6 @@ class PromptFeature private constructor(
)

is Popup -> {

val title = container.getString(R.string.mozac_feature_prompts_popup_dialog_title)
val positiveLabel = container.getString(R.string.mozac_feature_prompts_allow)
val negativeLabel = container.getString(R.string.mozac_feature_prompts_deny)
Expand All @@ -599,7 +632,6 @@ class PromptFeature private constructor(
)
}
is BeforeUnload -> {

val title =
container.getString(R.string.mozac_feature_prompt_before_unload_dialog_title)
val body =
Expand Down Expand Up @@ -689,27 +721,40 @@ class PromptFeature private constructor(
is BeforeUnload,
is SaveLoginPrompt,
is SelectLoginPrompt,
is SelectCreditCard,
is Share -> true
is SelectCreditCard -> false
is Alert, is TextPrompt, is Confirm, is Repost, is Popup -> promptAbuserDetector.shouldShowMoreDialogs
}
}

/**
* Dismisses the select login prompt if it is active and visible.
* @returns true if dismissCurrentLoginSelect is called otherwise false.
* Dismisses the select prompts if they are active and visible.
*
* @returns true if a select prompt was dismissed, otherwise false.
*/
@VisibleForTesting
fun dismissLoginSelectPrompt(): Boolean {
fun dismissSelectPrompts(): Boolean {
var result = false

(activePromptRequest as? SelectLoginPrompt)?.let { selectLoginPrompt ->
loginPicker?.let { loginPicker ->
if (loginPickerView?.asView()?.isVisible == true) {
loginPicker.dismissCurrentLoginSelect(selectLoginPrompt)
return true
result = true
}
}
}

(activePromptRequest as? SelectCreditCard)?.let { selectCreditCardPrompt ->
creditCardPicker?.let { creditCardPicker ->
if (creditCardPickerView?.asView()?.isVisible == true) {
creditCardPicker.dismissSelectCreditCardRequest(selectCreditCardPrompt)
result = true
}
}
}
return false

return result
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.feature.prompts.creditcard

import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.prompt.CreditCard
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.feature.prompts.concept.SelectablePromptView
import mozilla.components.feature.prompts.consumePromptFrom
import mozilla.components.support.base.log.logger.Logger

/**
* Interactor that implements [SelectablePromptView.Listener] and notifies the feature about actions
* the user performed in the credit card picker.
*
* @property store The [BrowserStore] this feature should subscribe to.
* @property creditCardSelectBar The [SelectablePromptView] view into which the select credit card
* prompt will be inflated.
* @property manageCreditCardsCallback A callback invoked when a user selects "Manage credit cards"
* from the select credit card prompt.
* @property sessionId The session ID which requested the prompt.
*/
class CreditCardPicker(
private val store: BrowserStore,
private val creditCardSelectBar: SelectablePromptView<CreditCard>,
private val manageCreditCardsCallback: () -> Unit = {},
private var sessionId: String? = null
) : SelectablePromptView.Listener<CreditCard> {

init {
creditCardSelectBar.listener = this
}

override fun onManageOptions() {
manageCreditCardsCallback.invoke()
dismissSelectCreditCardRequest()
}

override fun onOptionSelect(option: CreditCard) {
store.consumePromptFrom(sessionId) {
if (it is PromptRequest.SelectCreditCard) it.onConfirm(option)
}

creditCardSelectBar.hidePrompt()
}

/**
* Dismisses the active select credit card request.
*
* @param promptRequest The current active [PromptRequest.SelectCreditCard] or null
* otherwise.
*/
@Suppress("TooGenericExceptionCaught")
fun dismissSelectCreditCardRequest(promptRequest: PromptRequest.SelectCreditCard? = null) {
creditCardSelectBar.hidePrompt()

try {
if (promptRequest != null) {
promptRequest.onDismiss()
return
}

store.consumePromptFrom(sessionId) {
if (it is PromptRequest.SelectCreditCard) it.onDismiss()
}
} catch (e: RuntimeException) {
Logger.error("Can't dismiss this select credit card prompt", e)
}
}

/**
* Shows the select credit card prompt in response to the [PromptRequest] event.
*
* @param request The [PromptRequest] containing the the credit card request data to be shown.
*/
internal fun handleSelectCreditCardRequest(request: PromptRequest.SelectCreditCard) {
creditCardSelectBar.showPrompt(request.creditCards)
}
}
Loading

0 comments on commit 077b1bb

Please sign in to comment.