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 23, 2022
1 parent 26adfea commit c36ae8c
Show file tree
Hide file tree
Showing 14 changed files with 873 additions and 18 deletions.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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}"
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading

0 comments on commit c36ae8c

Please sign in to comment.