Skip to content

Commit

Permalink
Issue mozilla-mobile#10216: Refactor LoginPickerView to a more generi…
Browse files Browse the repository at this point in the history
…c view SelectablePromptView
  • Loading branch information
gabrielluong committed May 5, 2021
1 parent 1cf5f96 commit c5fda3b
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import mozilla.components.feature.prompts.dialog.TimePickerDialogFragment
import mozilla.components.feature.prompts.file.FilePicker
import mozilla.components.feature.prompts.login.LoginExceptions
import mozilla.components.feature.prompts.login.LoginPicker
import mozilla.components.feature.prompts.login.LoginPickerView
import mozilla.components.feature.prompts.concept.SelectablePromptView
import mozilla.components.feature.prompts.share.DefaultShareDelegate
import mozilla.components.feature.prompts.share.ShareDelegate
import mozilla.components.lib.state.ext.flowScoped
Expand Down Expand Up @@ -107,7 +107,8 @@ internal const val FRAGMENT_TAG = "mozac_feature_prompt_dialog"
* 'save login'prompts will not be shown.
* @property loginExceptionStorage An implementation of [LoginExceptions] that saves and checks origins
* the user does not want to see a save login dialog for.
* @property loginPickerView The [LoginPickerView] used for [LoginPicker] to display select login options.
* @property loginPickerView The [SelectablePromptView] used for [LoginPicker] to display a
* selectable prompt list of login options.
* @property onManageLogins A callback invoked when a user selects "manage logins" from the
* select login prompt.
* @property onNeedToRequestPermissions A callback invoked when permissions
Expand All @@ -124,7 +125,7 @@ class PromptFeature private constructor(
override val loginValidationDelegate: LoginValidationDelegate? = null,
private val isSaveLoginEnabled: () -> Boolean = { false },
override val loginExceptionStorage: LoginExceptions? = null,
private val loginPickerView: LoginPickerView? = null,
private val loginPickerView: SelectablePromptView<Login>? = null,
private val onManageLogins: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : LifecycleAwareFeature, PermissionsFeature, Prompter, ActivityResultHandler,
Expand All @@ -151,7 +152,7 @@ class PromptFeature private constructor(
loginValidationDelegate: LoginValidationDelegate? = null,
isSaveLoginEnabled: () -> Boolean = { false },
loginExceptionStorage: LoginExceptions? = null,
loginPickerView: LoginPickerView? = null,
loginPickerView: SelectablePromptView<Login>? = null,
onManageLogins: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
Expand All @@ -177,7 +178,7 @@ class PromptFeature private constructor(
loginValidationDelegate: LoginValidationDelegate? = null,
isSaveLoginEnabled: () -> Boolean = { false },
loginExceptionStorage: LoginExceptions? = null,
loginPickerView: LoginPickerView? = null,
loginPickerView: SelectablePromptView<Login>? = null,
onManageLogins: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
Expand All @@ -201,7 +202,7 @@ class PromptFeature private constructor(
store: BrowserStore,
customTabId: String? = null,
fragmentManager: FragmentManager,
loginPickerView: LoginPickerView? = null,
loginPickerView: SelectablePromptView<Login>? = null,
onManageLogins: () -> Unit = {},
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* 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.concept

import android.view.View

/**
* An interface for views that can display an option selection prompt.
*/
interface SelectablePromptView<T> {

var listener: Listener<T>?

/**
* Shows an option selection prompt with the provided options.
*
* @param options A list of options to display in the prompt.
*/
fun showPrompt(options: List<T>)

/**
* Hides the option selection prompt.
*/
fun hidePrompt()

/**
* Casts this [SelectablePromptView] interface to an Android [View] object.
*/
fun asView(): View = (this as View)

/**
* Tries to inflate the view if needed.
*
* See: https://github.com/mozilla-mobile/android-components/issues/5491
*
* @return true if the inflation was completed, false if the view was already inflated.
*/
fun tryInflate(): Boolean

/**
* Interface to allow a class to listen to the option selection prompt events.
*/
interface Listener<in T> {
/**
* Called when an user selects an options from the prompt.
*
* @param option The selected option.
*/
fun onOptionSelect(option: T)

/**
* Called when the user invokes the option to manage the list of options.
*/
fun onManageOptions()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,45 @@ package mozilla.components.feature.prompts.login
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.storage.Login
import mozilla.components.feature.prompts.concept.SelectablePromptView
import mozilla.components.feature.prompts.consumePromptFrom
import mozilla.components.support.base.log.logger.Logger

/**
* The [LoginPicker] displays a list of possible logins in a [LoginPickerView] for a site after
* The [LoginPicker] displays a list of possible logins in a [SelectablePromptView] for a site after
* receiving a [PromptRequest.SelectLoginPrompt] when a user clicks into a login field and we have
* matching logins. It allows the user to select which one of these logins they would like to fill,
* or select an option to manage their logins.
*
* @property store The [BrowserStore] this feature should subscribe to.
* @property loginSelectBar The [LoginPickerView] view into which the select login "prompt" will be inflated.
* @property loginSelectBar The [SelectablePromptView] view into which the select login "prompt" will be inflated.
* @property manageLoginsCallback A callback invoked when a user selects "manage logins" from the
* select login prompt.
* @property sessionId This is the id of the session which requested the prompt.
*/
internal class LoginPicker(
private val store: BrowserStore,
private val loginSelectBar: LoginPickerView,
private val loginSelectBar: SelectablePromptView<Login>,
private val manageLoginsCallback: () -> Unit = {},
private var sessionId: String? = null
) : LoginPickerView.Listener {
) : SelectablePromptView.Listener<Login> {

init {
loginSelectBar.listener = this
}

internal fun handleSelectLoginRequest(request: PromptRequest.SelectLoginPrompt) {
loginSelectBar.showPicker(request.logins)
loginSelectBar.showPrompt(request.logins)
}

override fun onLoginSelected(login: Login) {
override fun onOptionSelect(option: Login) {
store.consumePromptFrom(sessionId) {
if (it is PromptRequest.SelectLoginPrompt) it.onConfirm(login)
if (it is PromptRequest.SelectLoginPrompt) it.onConfirm(option)
}
loginSelectBar.hidePicker()
loginSelectBar.hidePrompt()
}

override fun onManageLogins() {
override fun onManageOptions() {
manageLoginsCallback.invoke()
dismissCurrentLoginSelect()
}
Expand All @@ -58,6 +59,6 @@ internal class LoginPicker(
} catch (e: RuntimeException) {
Logger.error("Can't dismiss this login select prompt", e)
}
loginSelectBar.hidePicker()
loginSelectBar.hidePrompt()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.concept.storage.Login
import mozilla.components.feature.prompts.R
import mozilla.components.feature.prompts.concept.SelectablePromptView
import mozilla.components.support.ktx.android.view.hideKeyboard

/**
* A customizable multiple login selection bar implementing [LoginPickerView].
* A customizable multiple login selection bar implementing [SelectablePromptView].
*/
class LoginSelectBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), LoginPickerView {
) : ConstraintLayout(context, attrs, defStyleAttr), SelectablePromptView<Login> {

var headerTextStyle: Int? = null

Expand All @@ -48,16 +49,16 @@ class LoginSelectBar @JvmOverloads constructor(
}
}

override var listener: LoginPickerView.Listener? = null
override var listener: SelectablePromptView.Listener<Login>? = null

override fun showPicker(list: List<Login>) {
override fun showPrompt(options: List<Login>) {
tryInflate().also {
listAdapter.submitList(list)
listAdapter.submitList(options)
loginPickerView?.isVisible = true
}
}

override fun hidePicker() {
override fun hidePrompt() {
this.isVisible = false
loginsList?.isVisible = false
listAdapter.submitList(mutableListOf())
Expand All @@ -76,7 +77,7 @@ class LoginSelectBar @JvmOverloads constructor(
private var expandArrowHead: AppCompatImageView? = null

private var listAdapter = BasicLoginAdapter {
listener?.onLoginSelected(it)
listener?.onOptionSelect(it)
}

override fun tryInflate(): Boolean {
Expand All @@ -93,7 +94,7 @@ class LoginSelectBar @JvmOverloads constructor(
private fun bindViews() {
manageLoginsButton = findViewById<AppCompatTextView>(R.id.manage_logins).apply {
setOnClickListener {
listener?.onManageLogins()
listener?.onManageOptions()
}
}
loginsList = findViewById(R.id.logins_list)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import mozilla.components.feature.prompts.dialog.PromptDialogFragment
import mozilla.components.feature.prompts.dialog.SaveLoginDialogFragment
import mozilla.components.feature.prompts.file.FilePicker.Companion.FILE_PICKER_ACTIVITY_REQUEST_CODE
import mozilla.components.feature.prompts.login.LoginPicker
import mozilla.components.feature.prompts.login.LoginPickerView
import mozilla.components.feature.prompts.concept.SelectablePromptView
import mozilla.components.feature.prompts.share.ShareDelegate
import mozilla.components.support.test.any
import mozilla.components.support.test.eq
Expand Down Expand Up @@ -273,7 +273,7 @@ class PromptFeatureTest {
@Test
fun `GIVEN loginPickerView is visible WHEN dismissLoginSelectPrompt THEN dismissCurrentLoginSelect called and true returned`() {
// given
val loginPickerView: LoginPickerView = mock()
val loginPickerView: SelectablePromptView<Login> = mock()
val feature = spy(
PromptFeature(
mock<Activity>(),
Expand All @@ -299,7 +299,7 @@ class PromptFeatureTest {
@Test
fun `GIVEN loginPickerView is not visible WHEN dismissLoginSelectPrompt THEN dismissCurrentLoginSelect called and false returned`() {
// given
val loginPickerView: LoginPickerView = mock()
val loginPickerView: SelectablePromptView<Login> = mock()
val feature = spy(
PromptFeature(
mock<Activity>(),
Expand All @@ -324,7 +324,7 @@ class PromptFeatureTest {
@Test
fun `GIVEN PromptFeature WHEN onBackPressed THEN dismissLoginSelectPrompt is called`() {
// given
val loginPickerView: LoginPickerView = mock()
val loginPickerView: SelectablePromptView<Login> = mock()
val feature = spy(
PromptFeature(
mock<Activity>(),
Expand All @@ -349,7 +349,7 @@ class PromptFeatureTest {

@Test
fun `Calling dismissLoginSelectPrompt should dismiss the login picker if the login prompt is active`() {
val loginPickerView: LoginPickerView = mock()
val loginPickerView: SelectablePromptView<Login> = mock()
val feature = spy(
PromptFeature(
mock<Activity>(),
Expand Down Expand Up @@ -1096,7 +1096,7 @@ class PromptFeatureTest {

@Test
fun `When page is refreshed login dialog is dismissed`() {
val loginPickerView: LoginPickerView = mock()
val loginPickerView: SelectablePromptView<Login> = mock()
val feature =
PromptFeature(
activity = mock(), store = store, fragmentManager = fragmentManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ class LoginPickerTest {
whenever(state.customTabs).thenReturn(listOf(customTab))
loginPicker = LoginPicker(store, loginSelectBar, onManageLogins, customTab.id)
loginPicker.handleSelectLoginRequest(request)
verify(loginSelectBar).showPicker(request.logins)
verify(loginSelectBar).showPrompt(request.logins)
}

@Test
fun `LoginPicker shows the login select bar on a selected tab`() {
prepareSelectedSession(request)
loginPicker = LoginPicker(store, loginSelectBar, onManageLogins)
loginPicker.handleSelectLoginRequest(request)
verify(loginSelectBar).showPicker(request.logins)
verify(loginSelectBar).showPrompt(request.logins)
}

@Test
Expand All @@ -79,10 +79,10 @@ class LoginPickerTest {

loginPicker.handleSelectLoginRequest(request)

loginPicker.onLoginSelected(login)
loginPicker.onOptionSelect(login)

assertEquals(confirmedLogin, login)
verify(loginSelectBar).hidePicker()
verify(loginSelectBar).hidePrompt()
}

@Test
Expand All @@ -95,11 +95,11 @@ class LoginPickerTest {

loginPicker.handleSelectLoginRequest(request)

loginPicker.onManageLogins()
loginPicker.onManageOptions()

assertTrue(manageLoginsCalled)
assertTrue(onDismissWasCalled)
verify(loginSelectBar).hidePicker()
verify(loginSelectBar).hidePrompt()
}

private fun prepareSelectedSession(request: PromptRequest? = null): TabSessionState {
Expand Down
Loading

0 comments on commit c5fda3b

Please sign in to comment.