Skip to content

Commit

Permalink
For mozilla-mobile#12061 - Add support to display AddressPicker
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandru2909 committed May 11, 2022
1 parent ea42318 commit 1c1e3ba
Show file tree
Hide file tree
Showing 8 changed files with 465 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ import mozilla.components.concept.engine.prompt.PromptRequest.Share
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.TextPrompt
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection
import mozilla.components.concept.storage.Address
import mozilla.components.concept.storage.CreditCardEntry
import mozilla.components.concept.storage.CreditCardValidationDelegate
import mozilla.components.concept.storage.Login
import mozilla.components.concept.storage.LoginEntry
import mozilla.components.concept.storage.LoginValidationDelegate
import mozilla.components.feature.prompts.address.AddressPicker
import mozilla.components.feature.prompts.concept.SelectablePromptView
import mozilla.components.feature.prompts.creditcard.CreditCardPicker
import mozilla.components.feature.prompts.creditcard.CreditCardSaveDialogFragment
Expand Down Expand Up @@ -151,6 +153,8 @@ class PromptFeature private constructor(
private val creditCardPickerView: SelectablePromptView<CreditCardEntry>? = null,
private val onManageCreditCards: () -> Unit = {},
private val onSelectCreditCard: () -> Unit = {},
private val addressPickerView: SelectablePromptView<Address>? = null,
private val onSelectAddress: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : LifecycleAwareFeature,
PermissionsFeature,
Expand Down Expand Up @@ -190,6 +194,8 @@ class PromptFeature private constructor(
creditCardPickerView: SelectablePromptView<CreditCardEntry>? = null,
onManageCreditCards: () -> Unit = {},
onSelectCreditCard: () -> Unit = {},
addressPickerView: SelectablePromptView<Address>? = null,
onSelectAddress: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
container = PromptContainer.Activity(activity),
Expand All @@ -207,7 +213,9 @@ class PromptFeature private constructor(
onManageLogins = onManageLogins,
creditCardPickerView = creditCardPickerView,
onManageCreditCards = onManageCreditCards,
onSelectCreditCard = onSelectCreditCard
onSelectCreditCard = onSelectCreditCard,
addressPickerView = addressPickerView,
onSelectAddress = onSelectAddress
)

constructor(
Expand All @@ -226,6 +234,8 @@ class PromptFeature private constructor(
creditCardPickerView: SelectablePromptView<CreditCardEntry>? = null,
onManageCreditCards: () -> Unit = {},
onSelectCreditCard: () -> Unit = {},
addressPickerView: SelectablePromptView<Address>? = null,
onSelectAddress: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
container = PromptContainer.Fragment(fragment),
Expand All @@ -243,7 +253,9 @@ class PromptFeature private constructor(
onManageLogins = onManageLogins,
creditCardPickerView = creditCardPickerView,
onManageCreditCards = onManageCreditCards,
onSelectCreditCard = onSelectCreditCard
onSelectCreditCard = onSelectCreditCard,
addressPickerView = addressPickerView,
onSelectAddress = onSelectAddress
)

private val filePicker = FilePicker(container, store, customTabId, onNeedToRequestPermissions)
Expand All @@ -264,6 +276,17 @@ class PromptFeature private constructor(
)
}

@VisibleForTesting(otherwise = PRIVATE)
internal var addressPicker =
addressPickerView?.let {
AddressPicker(
store = store,
addressSelectBar = it,
selectAddressCallback = onSelectAddress,
sessionId = customTabId
)
}

override val onNeedToRequestPermissions
get() = filePicker.onNeedToRequestPermissions

Expand All @@ -285,16 +308,29 @@ class PromptFeature private constructor(
if (content.promptRequests.lastOrNull() != 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 SaveLoginPrompt) {
(activePrompt?.get() as? SaveLoginDialogFragment)?.dismissAllowingStateLoss()
} else if (activePromptRequest is SaveCreditCard) {
(activePrompt?.get() as? CreditCardSaveDialogFragment)?.dismissAllowingStateLoss()
} else if (activePromptRequest is SelectCreditCard) {
creditCardPicker?.dismissSelectCreditCardRequest(
activePromptRequest as SelectCreditCard
)
when (activePromptRequest) {
is SelectLoginPrompt -> {
loginPicker?.dismissCurrentLoginSelect(activePromptRequest as SelectLoginPrompt)
}
is SaveLoginPrompt -> {
(activePrompt?.get() as? SaveLoginDialogFragment)?.dismissAllowingStateLoss()
}
is SaveCreditCard -> {
(activePrompt?.get() as? CreditCardSaveDialogFragment)?.dismissAllowingStateLoss()
}
is SelectCreditCard -> {
creditCardPicker?.dismissSelectCreditCardRequest(
activePromptRequest as SelectCreditCard
)
}
is SelectAddress -> {
addressPicker?.dismissSelectAddressRequest(
activePromptRequest as SelectAddress
)
}
else -> {
// no-op
}
}

onPromptRequested(state)
Expand Down Expand Up @@ -426,6 +462,11 @@ class PromptFeature private constructor(
loginPicker?.handleSelectLoginRequest(promptRequest)
}
}
is SelectAddress -> {
if (promptRequest.addresses.isNotEmpty()) {
addressPicker?.handleSelectAddressRequest(promptRequest)
}
}
else -> handleDialogsRequest(promptRequest, session)
}
}
Expand Down Expand Up @@ -839,8 +880,8 @@ class PromptFeature private constructor(
is SelectLoginPrompt,
is SelectCreditCard,
is SaveCreditCard,
is SelectAddress,
is Share -> true
is SelectAddress -> false
is Alert, is TextPrompt, is Confirm, is Repost, is Popup -> promptAbuserDetector.shouldShowMoreDialogs
}
}
Expand Down Expand Up @@ -872,6 +913,15 @@ class PromptFeature private constructor(
}
}

(activePromptRequest as? SelectAddress)?.let { selectAddressPrompt ->
addressPicker?.let { addressPicker ->
if (addressPickerView?.asView()?.isVisible == true) {
addressPicker.dismissSelectAddressRequest(selectAddressPrompt)
result = true
}
}
}

return result
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* 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.address

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.concept.storage.Address
import mozilla.components.feature.prompts.R

@VisibleForTesting
internal object AddressDiffCallback : DiffUtil.ItemCallback<Address>() {
override fun areItemsTheSame(oldItem: Address, newItem: Address) =
oldItem.guid == newItem.guid

override fun areContentsTheSame(oldItem: Address, newItem: Address) =
oldItem == newItem
}

/**
* RecyclerView adapter for displaying address items.
*/
internal class AddressAdapter(
private val onAddressSelected: (Address) -> Unit
) : ListAdapter<Address, AddressViewHolder>(AddressDiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddressViewHolder {
val view = LayoutInflater
.from(parent.context)
.inflate(R.layout.mozac_feature_prompts_address_list_item, parent, false)
return AddressViewHolder(view, onAddressSelected)
}

override fun onBindViewHolder(holder: AddressViewHolder, position: Int) {
holder.bind(getItem(position))
}
}

/**
* View holder for a address item.
*/
@VisibleForTesting
internal class AddressViewHolder(
itemView: View,
private val onAddressSelected: (Address) -> Unit
) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
@VisibleForTesting
lateinit var address: Address

init {
itemView.setOnClickListener(this)
}

fun bind(address: Address) {
this.address = address
itemView.findViewById<TextView>(R.id.address_name)?.text = address.displayFormat()
}

override fun onClick(v: View?) {
onAddressSelected(address)
}
}

/**
* Format the address details to be displayed to the user.
*/
fun Address.displayFormat(): String = "${this.streetAddress}, ${this.addressLevel2}, ${this.addressLevel1}, ${this.postalCode}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* 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.address

import androidx.annotation.VisibleForTesting
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.storage.Address
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 address picker.
*
* @property store The [BrowserStore] this feature should subscribe to.
* @property addressSelectBar The [SelectablePromptView] view into which the select address
* prompt will be inflated.
* @property selectAddressCallback A callback invoked when a user selects an address option
* from the select address prompt
* @property sessionId The session ID which requested the prompt.
*/
class AddressPicker(
private val store: BrowserStore,
private val addressSelectBar: SelectablePromptView<Address>,
private val selectAddressCallback: () -> Unit = {},
private var sessionId: String? = null
) : SelectablePromptView.Listener<Address> {

init {
addressSelectBar.listener = this
}

// The selected address option to confirm.
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var selectedAddress: Address? = null

override fun onManageOptions() {
dismissSelectAddressRequest()
}

override fun onOptionSelect(option: Address) {
selectedAddress = option
addressSelectBar.hidePrompt()
selectAddressCallback.invoke()
}

/**
* Dismisses the active [PromptRequest.SelectAddress] request.
*
* @param promptRequest The current active [PromptRequest.SelectAddress] or null
* otherwise.
*/
@Suppress("TooGenericExceptionCaught")
fun dismissSelectAddressRequest(promptRequest: PromptRequest.SelectAddress? = null) {
addressSelectBar.hidePrompt()

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

store.consumePromptFrom<PromptRequest.SelectAddress>(sessionId) {
it.onDismiss()
}
} catch (e: RuntimeException) {
Logger.error("Can't dismiss select address prompt", e)
}
}

/**
* Shows the select address prompt in response to the [PromptRequest] event.
*
* @param request The [PromptRequest] containing the the address request data to be shown.
*/
internal fun handleSelectAddressRequest(request: PromptRequest.SelectAddress) {
addressSelectBar.showPrompt(request.addresses)
}
}
Loading

0 comments on commit 1c1e3ba

Please sign in to comment.