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 31, 2022
1 parent 588c2c4 commit f92636c
Show file tree
Hide file tree
Showing 14 changed files with 875 additions and 18 deletions.
Expand Up @@ -13,7 +13,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.selector.findTabOrCustomTab
Expand Down Expand Up @@ -47,6 +46,9 @@ 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.AddressDelegate
import mozilla.components.feature.prompts.address.AddressPicker
import mozilla.components.feature.prompts.address.DefaultAddressDelegate
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 @@ -133,6 +135,7 @@ internal const val FRAGMENT_TAG = "mozac_feature_prompt_dialog"
* the select credit card prompt.
* @property onSelectCreditCard A callback invoked when a user selects a credit card from the
* select credit card prompt.
* @property addressDelegate Delegate for address picker.
* @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 @@ -155,6 +158,7 @@ class PromptFeature private constructor(
private val creditCardPickerView: SelectablePromptView<CreditCardEntry>? = null,
private val onManageCreditCards: () -> Unit = {},
private val onSelectCreditCard: () -> Unit = {},
private val addressDelegate: AddressDelegate = DefaultAddressDelegate(),
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : LifecycleAwareFeature,
PermissionsFeature,
Expand Down Expand Up @@ -195,6 +199,7 @@ class PromptFeature private constructor(
creditCardPickerView: SelectablePromptView<CreditCardEntry>? = null,
onManageCreditCards: () -> Unit = {},
onSelectCreditCard: () -> Unit = {},
addressDelegate: AddressDelegate = DefaultAddressDelegate(),
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
container = PromptContainer.Activity(activity),
Expand All @@ -213,7 +218,8 @@ class PromptFeature private constructor(
onManageLogins = onManageLogins,
creditCardPickerView = creditCardPickerView,
onManageCreditCards = onManageCreditCards,
onSelectCreditCard = onSelectCreditCard
onSelectCreditCard = onSelectCreditCard,
addressDelegate = addressDelegate
)

constructor(
Expand All @@ -233,6 +239,7 @@ class PromptFeature private constructor(
creditCardPickerView: SelectablePromptView<CreditCardEntry>? = null,
onManageCreditCards: () -> Unit = {},
onSelectCreditCard: () -> Unit = {},
addressDelegate: AddressDelegate = DefaultAddressDelegate(),
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
container = PromptContainer.Fragment(fragment),
Expand All @@ -251,7 +258,8 @@ class PromptFeature private constructor(
onManageLogins = onManageLogins,
creditCardPickerView = creditCardPickerView,
onManageCreditCards = onManageCreditCards,
onSelectCreditCard = onSelectCreditCard
onSelectCreditCard = onSelectCreditCard,
addressDelegate = addressDelegate
)

private val filePicker = FilePicker(container, store, customTabId, onNeedToRequestPermissions)
Expand All @@ -272,6 +280,19 @@ class PromptFeature private constructor(
)
}

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

override val onNeedToRequestPermissions
get() = filePicker.onNeedToRequestPermissions

Expand All @@ -293,16 +314,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 @@ -429,15 +463,16 @@ class PromptFeature private constructor(
creditCardPicker?.handleSelectCreditCardRequest(promptRequest)
}
}
is SelectAddress ->
if (isAddressAutofillEnabled() && promptRequest.addresses.isNotEmpty()) {
// Address picker will be implemented in #12061
}
is SelectLoginPrompt -> {
if (promptRequest.logins.isNotEmpty()) {
loginPicker?.handleSelectLoginRequest(promptRequest)
}
}
is SelectAddress -> {
if (isAddressAutofillEnabled() && promptRequest.addresses.isNotEmpty()) {
addressPicker?.handleSelectAddressRequest(promptRequest)
}
}
else -> handleDialogsRequest(promptRequest, session)
}
}
Expand Down Expand Up @@ -851,8 +886,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 @@ -884,6 +919,15 @@ class PromptFeature private constructor(
}
}

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

return result
}

Expand Down
@@ -0,0 +1,74 @@
/* 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}"
@@ -0,0 +1,33 @@
/* 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 mozilla.components.concept.storage.Address
import mozilla.components.feature.prompts.concept.SelectablePromptView

/**
* Delegate for address picker
*/
interface AddressDelegate {
/**
* The [SelectablePromptView] used for [AddressPicker] to display a
* selectable prompt list of address options.
*/
val addressPickerView: SelectablePromptView<Address>?

/**
* Callback invoked when the user clicks "Manage addresses" from
* select address prompt.
*/
val onManageAddresses: () -> Unit
}

/**
* Default implementation for address picker delegate
*/
class DefaultAddressDelegate(
override val addressPickerView: SelectablePromptView<Address>? = null,
override val onManageAddresses: () -> Unit = {}
) : AddressDelegate
@@ -0,0 +1,85 @@
/* 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 mozilla.components.browser.state.action.ContentAction
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 onManageAddresses Callback invoked when user clicks on "Manage adresses" button from
* 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 onManageAddresses: () -> Unit = {},
private var sessionId: String? = null
) : SelectablePromptView.Listener<Address> {

init {
addressSelectBar.listener = this
}

/**
* 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)
}

/**
* 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()
sessionId?.let {
store.dispatch(ContentAction.ConsumePromptRequestAction(it, promptRequest))
}
return
}

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

override fun onOptionSelect(option: Address) {
store.consumePromptFrom<PromptRequest.SelectAddress>(sessionId) {
it.onConfirm(option)
}

addressSelectBar.hidePrompt()
}

override fun onManageOptions() {
onManageAddresses.invoke()
dismissSelectAddressRequest()
}
}

0 comments on commit f92636c

Please sign in to comment.