diff --git a/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt b/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt index 6fd7c77ee..eac2efeb8 100644 --- a/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt +++ b/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt @@ -41,6 +41,7 @@ private val ICON_SIZE = 24.dp * @param label The label in the list item. * @param modifier [Modifier] to be applied to the layout. * @param description An optional description text below the label. + * @param maxDescriptionLines An optional maximum number of lines for the description text to span. * @param onClick Called when the user clicks on the item. * @param iconPainter [Painter] used to display a [ListItemIcon] after the list item. * @param iconDescription Content description of the icon. @@ -51,6 +52,7 @@ fun TextListItem( label: String, modifier: Modifier = Modifier, description: String? = null, + maxDescriptionLines: Int = 1, onClick: (() -> Unit)? = null, iconPainter: Painter? = null, iconDescription: String? = null, @@ -60,6 +62,7 @@ fun TextListItem( label = label, modifier = modifier, description = description, + maxDescriptionLines = maxDescriptionLines, onClick = onClick, afterListAction = { iconPainter?.let { @@ -172,6 +175,7 @@ fun IconListItem( * @param label The label in the list item. * @param modifier [Modifier] to be applied to the layout. * @param description An optional description text below the label. + * @param maxDescriptionLines An optional maximum number of lines for the description text to span. * @param onClick Called when the user clicks on the item. * @param beforeListAction Optional Composable for adding UI before the list item. * @param afterListAction Optional Composable for adding UI to the end of the list item. @@ -181,6 +185,7 @@ private fun ListItem( label: String, modifier: Modifier = Modifier, description: String? = null, + maxDescriptionLines: Int = 1, onClick: (() -> Unit)? = null, beforeListAction: @Composable RowScope.() -> Unit = {}, afterListAction: @Composable RowScope.() -> Unit = {}, @@ -211,7 +216,7 @@ private fun ListItem( SecondaryText( text = description, fontSize = 14.sp, - maxLines = 1, + maxLines = maxDescriptionLines, ) } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/address/ext/Address.kt b/app/src/main/java/org/mozilla/fenix/settings/address/ext/Address.kt new file mode 100644 index 000000000..fa25db556 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/address/ext/Address.kt @@ -0,0 +1,38 @@ +/* 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.ext + +import androidx.annotation.VisibleForTesting +import mozilla.components.concept.storage.Address + +/** + * Generate a label item text for an [Address]. The combination of names is based on desktop code + * found here: + * https://searchfox.org/mozilla-central/rev/d989c65584ded72c2de85cb40bede7ac2f176387/toolkit/components/formautofill/FormAutofillNameUtils.jsm#400 + */ +fun Address.getFullName(): String = listOf(givenName, additionalName, familyName) + .filter { it.isNotEmpty() } + .joinToString(" ") + +/** + * Generate a description item text for an [Address]. The element ordering is based on the + * priorities defined by the desktop code found here: + * https://searchfox.org/mozilla-central/rev/d989c65584ded72c2de85cb40bede7ac2f176387/toolkit/components/formautofill/FormAutofillUtils.jsm#323 + */ +fun Address.getAddressLabel(): String = listOf( + streetAddress.toOneLineAddress(), + addressLevel3, + addressLevel2, + organization, + addressLevel1, + country, + postalCode, + tel, + email +).filter { it.isNotEmpty() }.joinToString(", ") + +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +internal fun String.toOneLineAddress(): String = + this.split("\n").joinToString(separator = " ") { it.trim() } diff --git a/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressList.kt b/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressList.kt index 6fa8cdb6b..38d868562 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressList.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressList.kt @@ -19,6 +19,8 @@ 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.settings.address.ext.getAddressLabel +import org.mozilla.fenix.settings.address.ext.getFullName import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.Theme @@ -38,9 +40,10 @@ fun AddressList( LazyColumn { items(addresses) { address -> TextListItem( - label = address.givenName + " " + address.familyName, + label = address.getFullName(), modifier = Modifier.padding(start = 56.dp), - description = address.streetAddress, + description = address.getAddressLabel(), + maxDescriptionLines = 2, onClick = { onAddressClick(address) }, ) } diff --git a/app/src/test/java/org/mozilla/fenix/settings/address/ext/AddressTest.kt b/app/src/test/java/org/mozilla/fenix/settings/address/ext/AddressTest.kt new file mode 100644 index 000000000..c5c52b845 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/settings/address/ext/AddressTest.kt @@ -0,0 +1,143 @@ +/* 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.ext + +import mozilla.components.concept.storage.Address +import org.junit.Assert.assertEquals +import org.junit.Test + +class AddressTest { + @Test + fun `WHEN all names are populated THEN label includes all names`() { + val addr = generateAddress() + + val label = addr.getFullName() + + assertEquals("${addr.givenName} ${addr.additionalName} ${addr.familyName}", label) + } + + @Test + fun `WHEN middle name is missing THEN label is given and family combined`() { + val addr = generateAddress(additionalName = "") + + val label = addr.getFullName() + + assertEquals("${addr.givenName} ${addr.familyName}", label) + } + + @Test + fun `WHEN only family and middle name are available THEN label is middle and family combined`() { + val addr = generateAddress(givenName = "") + + val label = addr.getFullName() + + assertEquals("${addr.additionalName} ${addr.familyName}", label) + } + + @Test + fun `WHEN only family name is available THEN label is family name`() { + val addr = generateAddress(givenName = "", additionalName = "") + + val label = addr.getFullName() + + assertEquals(addr.familyName, label) + } + + @Test + fun `WHEN all properties are present THEN all properties present in description`() { + val addr = generateAddress() + + val description = addr.getAddressLabel() + + val expected = "${addr.streetAddress}, ${addr.addressLevel3}, ${addr.addressLevel2}, " + + "${addr.organization}, ${addr.addressLevel1}, ${addr.country}, " + + "${addr.postalCode}, ${addr.tel}, ${addr.email}" + + assertEquals(expected, description) + } + + @Test + fun `WHEN any properties are missing THEN description includes only present`() { + val addr = generateAddress( + addressLevel3 = "", + organization = "", + email = "", + ) + + val description = addr.getAddressLabel() + + val expected = "${addr.streetAddress}, ${addr.addressLevel2}, ${addr.addressLevel1}, " + + "${addr.country}, ${addr.postalCode}, ${addr.tel}" + assertEquals(expected, description) + } + + @Test + fun `WHEN everything is missing THEN description is empty`() { + val addr = generateAddress( + givenName = "", + additionalName = "", + familyName = "", + organization = "", + streetAddress = "", + addressLevel3 = "", + addressLevel2 = "", + addressLevel1 = "", + postalCode = "", + country = "", + tel = "", + email = "" + ) + + val description = addr.getAddressLabel() + + assertEquals("", description) + } + + @Test + fun `GIVEN multiline street address THEN joined as single line`() { + val streetAddress = """ + line1 + line2 + line3 + """.trimIndent() + + val result = streetAddress.toOneLineAddress() + + assertEquals("line1 line2 line3", result) + } + + private fun generateAddress( + givenName: String = "Firefox", + additionalName: String = "The", + familyName: String = "Browser", + organization: String = "Mozilla", + streetAddress: String = "street", + addressLevel3: String = "3", + addressLevel2: String = "2", + addressLevel1: String = "1", + postalCode: String = "code", + country: String = "country", + tel: String = "tel", + email: String = "email", + ) = Address( + guid = "", + givenName = givenName, + additionalName = additionalName, + familyName = familyName, + organization = organization, + streetAddress = streetAddress, + addressLevel3 = addressLevel3, + addressLevel2 = addressLevel2, + addressLevel1 = addressLevel1, + postalCode = postalCode, + country = country, + tel = tel, + email = email, + timeCreated = 1, + timeLastUsed = 1, + timeLastModified = 1, + timesUsed = 1, + ) +}