[fenix] For https://github.com/mozilla-mobile/fenix/issues/24850 - Display an address list for managing existing addresses
parent
3bc3ca9610
commit
0782aba4b0
@ -0,0 +1,82 @@
|
|||||||
|
/* 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 org.mozilla.fenix.settings.address
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mozilla.components.lib.state.ext.observeAsComposableState
|
||||||
|
import org.mozilla.fenix.components.StoreProvider
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.settings.address.controller.DefaultAddressManagementController
|
||||||
|
import org.mozilla.fenix.settings.address.interactor.AddressManagementInteractor
|
||||||
|
import org.mozilla.fenix.settings.address.interactor.DefaultAddressManagementInteractor
|
||||||
|
import org.mozilla.fenix.settings.address.view.AddressList
|
||||||
|
import org.mozilla.fenix.settings.autofill.AutofillAction
|
||||||
|
import org.mozilla.fenix.settings.autofill.AutofillFragmentState
|
||||||
|
import org.mozilla.fenix.settings.autofill.AutofillFragmentStore
|
||||||
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a list of saved addresses.
|
||||||
|
*/
|
||||||
|
class AddressManagementFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var store: AutofillFragmentStore
|
||||||
|
private lateinit var interactor: AddressManagementInteractor
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
store = StoreProvider.get(this) {
|
||||||
|
AutofillFragmentStore(AutofillFragmentState())
|
||||||
|
}
|
||||||
|
|
||||||
|
interactor = DefaultAddressManagementInteractor(
|
||||||
|
controller = DefaultAddressManagementController(
|
||||||
|
navController = findNavController()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
loadAddresses()
|
||||||
|
|
||||||
|
return ComposeView(requireContext()).apply {
|
||||||
|
setContent {
|
||||||
|
FirefoxTheme {
|
||||||
|
val addresses = store.observeAsComposableState { state -> state.addresses }
|
||||||
|
|
||||||
|
AddressList(
|
||||||
|
addresses = addresses.value ?: emptyList(),
|
||||||
|
onAddressClick = interactor::onSelectAddress,
|
||||||
|
onAddAddressButtonClick = interactor::onAddAddressButtonClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all the addresses from the autofill storage and updates the
|
||||||
|
* [AutofillFragmentStore] with the list of addresses.
|
||||||
|
*/
|
||||||
|
private fun loadAddresses() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val addresses = requireContext().components.core.autofillStorage.getAllAddresses()
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
store.dispatch(AutofillAction.UpdateAddresses(addresses))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/* 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 org.mozilla.fenix.settings.address.controller
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import mozilla.components.concept.storage.Address
|
||||||
|
import org.mozilla.fenix.settings.address.AddressManagementFragment
|
||||||
|
import org.mozilla.fenix.settings.address.AddressManagementFragmentDirections
|
||||||
|
import org.mozilla.fenix.settings.address.interactor.AddressManagementInteractor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AddressManagementFragment] controller. An interface that handles the view manipulation of
|
||||||
|
* the address manager triggered by the interactor.
|
||||||
|
*/
|
||||||
|
interface AddressManagementController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [AddressManagementInteractor.onSelectAddress]
|
||||||
|
*/
|
||||||
|
fun handleAddressClicked(address: Address)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [AddressManagementInteractor.onAddAddressClick]
|
||||||
|
*/
|
||||||
|
fun handleAddAddressButtonClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of [AddressManagementController].
|
||||||
|
*
|
||||||
|
* @param navController [NavController] used for navigation.
|
||||||
|
*/
|
||||||
|
class DefaultAddressManagementController(
|
||||||
|
private val navController: NavController
|
||||||
|
) : AddressManagementController {
|
||||||
|
|
||||||
|
override fun handleAddressClicked(address: Address) {
|
||||||
|
navigateToAddressEditor()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleAddAddressButtonClicked() {
|
||||||
|
navigateToAddressEditor()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToAddressEditor() {
|
||||||
|
navController.navigate(
|
||||||
|
AddressManagementFragmentDirections
|
||||||
|
.actionAddressManagementFragmentToAddressEditorFragment()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/* 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 org.mozilla.fenix.settings.address.interactor
|
||||||
|
|
||||||
|
import mozilla.components.concept.storage.Address
|
||||||
|
import org.mozilla.fenix.settings.address.controller.AddressManagementController
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for the address management interactor.
|
||||||
|
*/
|
||||||
|
interface AddressManagementInteractor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the address editor to edit the selected address. Called when a user
|
||||||
|
* taps on an address item.
|
||||||
|
*
|
||||||
|
* @param address The selected [Address] to edit.
|
||||||
|
*/
|
||||||
|
fun onSelectAddress(address: Address)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the address editor to add a new address. Called when a user
|
||||||
|
* taps on 'Add address' button.
|
||||||
|
*/
|
||||||
|
fun onAddAddressButtonClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of [AddressManagementInteractor].
|
||||||
|
*
|
||||||
|
* @param controller An instance of [AddressManagementController] which will be delegated for
|
||||||
|
* all user interactions.
|
||||||
|
*/
|
||||||
|
class DefaultAddressManagementInteractor(
|
||||||
|
private val controller: AddressManagementController
|
||||||
|
) : AddressManagementInteractor {
|
||||||
|
|
||||||
|
override fun onSelectAddress(address: Address) {
|
||||||
|
controller.handleAddressClicked(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAddAddressButtonClick() {
|
||||||
|
controller.handleAddAddressButtonClicked()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/* 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 org.mozilla.fenix.settings.address.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import mozilla.components.concept.storage.Address
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.compose.list.IconListItem
|
||||||
|
import org.mozilla.fenix.compose.list.TextListItem
|
||||||
|
import org.mozilla.fenix.theme.FirefoxTheme
|
||||||
|
import org.mozilla.fenix.theme.Theme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of addresses.
|
||||||
|
*
|
||||||
|
* @param addresses A list of [Address] to display.
|
||||||
|
* @param onAddressClick Invoked when the user clicks on an address.
|
||||||
|
* @param onAddAddressButtonClick Invoked when the user clicks on the "Add address" button.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun AddressList(
|
||||||
|
addresses: List<Address>,
|
||||||
|
onAddressClick: (Address) -> Unit,
|
||||||
|
onAddAddressButtonClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
LazyColumn {
|
||||||
|
items(addresses) { address ->
|
||||||
|
TextListItem(
|
||||||
|
label = address.givenName + " " + address.familyName,
|
||||||
|
modifier = Modifier.padding(start = 56.dp),
|
||||||
|
description = address.streetAddress,
|
||||||
|
onClick = { onAddressClick(address) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
IconListItem(
|
||||||
|
label = stringResource(R.string.preferences_addresses_add_address),
|
||||||
|
beforeIconPainter = painterResource(R.drawable.ic_new),
|
||||||
|
onClick = onAddAddressButtonClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun AddressListPreview() {
|
||||||
|
FirefoxTheme(theme = Theme.getTheme(isPrivate = false)) {
|
||||||
|
Box(Modifier.background(FirefoxTheme.colors.layer2)) {
|
||||||
|
AddressList(
|
||||||
|
addresses = listOf(
|
||||||
|
Address(
|
||||||
|
guid = "1",
|
||||||
|
givenName = "Banana",
|
||||||
|
additionalName = "",
|
||||||
|
familyName = "Apple",
|
||||||
|
organization = "Mozilla",
|
||||||
|
streetAddress = "123 Sesame Street",
|
||||||
|
addressLevel3 = "",
|
||||||
|
addressLevel2 = "",
|
||||||
|
addressLevel1 = "",
|
||||||
|
postalCode = "90210",
|
||||||
|
country = "US",
|
||||||
|
tel = "+1 519 555-5555",
|
||||||
|
email = "foo@bar.com",
|
||||||
|
timeCreated = 0L,
|
||||||
|
timeLastUsed = 0L,
|
||||||
|
timeLastModified = 0L,
|
||||||
|
timesUsed = 0L
|
||||||
|
)
|
||||||
|
),
|
||||||
|
onAddressClick = {},
|
||||||
|
onAddAddressButtonClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/* 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 org.mozilla.fenix.settings.address.controller
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.spyk
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.concept.storage.Address
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.settings.address.AddressManagementFragmentDirections
|
||||||
|
|
||||||
|
class DefaultAddressManagementControllerTest {
|
||||||
|
|
||||||
|
private val navController: NavController = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private lateinit var controller: AddressManagementController
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
controller = spyk(
|
||||||
|
DefaultAddressManagementController(
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN an address is selected THEN navigate to the address editor`() {
|
||||||
|
val address: Address = mockk(relaxed = true)
|
||||||
|
|
||||||
|
controller.handleAddressClicked(address)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
navController.navigate(
|
||||||
|
AddressManagementFragmentDirections
|
||||||
|
.actionAddressManagementFragmentToAddressEditorFragment()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN the add address button is clicked THEN navigate to the address editor`() {
|
||||||
|
controller.handleAddAddressButtonClicked()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
navController.navigate(
|
||||||
|
AddressManagementFragmentDirections
|
||||||
|
.actionAddressManagementFragmentToAddressEditorFragment()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/* 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 org.mozilla.fenix.settings.address.interactor
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.concept.storage.Address
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.settings.address.controller.AddressManagementController
|
||||||
|
|
||||||
|
class DefaultAddressManagementInteractorTest {
|
||||||
|
|
||||||
|
private val controller: AddressManagementController = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private lateinit var interactor: AddressManagementInteractor
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
interactor = DefaultAddressManagementInteractor(controller)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN an address is selected THEN forward to controller handler`() {
|
||||||
|
val address: Address = mockk(relaxed = true)
|
||||||
|
|
||||||
|
interactor.onSelectAddress(address)
|
||||||
|
|
||||||
|
verify { controller.handleAddressClicked(address) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN add address button is clicked THEN forward to controller handler`() {
|
||||||
|
interactor.onAddAddressButtonClick()
|
||||||
|
|
||||||
|
verify { controller.handleAddAddressButtonClicked() }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue