Skip to content

Commit 1c0880f

Browse files
authored
Improve UI for credential entries in list view (#2108)
<!-- Note: This checklist is a reminder of our shared engineering expectations. The items in Bold are required If your PR involves UI changes: 1. Upload screenshots or screencasts that illustrate the changes before / after 2. Add them under the UI changes section (feel free to add more columns if needed) 3. Make sure these changes are tested in API 23 and API 26 If your PR does not involve UI changes, you can remove the **UI changes** section --> Task/Issue URL: https://app.asana.com/0/0/1202659292537113/f ### Description Improves the look of the individual entries in the credential management list. - Styles the title and subtitle to match designs - Shows the title if it exists, otherwise falls back to showing the domain ### Steps to test this PR - [x] Save a few autofill entries - [x] Visit the credential management screen; verify the style of the title and subtitle look ok - [x] Edit a credential and add a title; return to the listing page - [x] Verify the title is shown instead of the domain - [x] Re-edit the credential to remove the title and return to the listing page - [x] Verify the domain is shown instead of a title
1 parent f1f3efa commit 1c0880f

File tree

8 files changed

+126
-11
lines changed

8 files changed

+126
-11
lines changed

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/AutofillDomainFormatter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ package com.duckduckgo.autofill
1818

1919
import androidx.core.net.toUri
2020
import com.duckduckgo.app.global.baseHost
21-
import com.duckduckgo.di.scopes.FragmentScope
21+
import com.duckduckgo.di.scopes.AppScope
2222
import com.squareup.anvil.annotations.ContributesBinding
2323
import javax.inject.Inject
2424

2525
interface AutofillDomainFormatter {
2626
fun extractDomain(domain: String?): String?
2727
}
2828

29-
@ContributesBinding(FragmentScope::class)
29+
@ContributesBinding(AppScope::class)
3030
class AutofillDomainFormatterDomainNameOnly @Inject constructor() : AutofillDomainFormatter {
3131
override fun extractDomain(domain: String?): String? {
3232
val domain = domain?.toUri()?.baseHost

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/ui/credential/management/AutofillManagementRecyclerAdapter.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class AutofillManagementRecyclerAdapter(
3232
val lifecycleOwner: LifecycleOwner,
3333
val faviconManager: FaviconManager,
3434
val grouper: CredentialGrouper,
35+
val titleExtractor: LoginCredentialTitleExtractor,
3536
val onCredentialSelected: (credentials: LoginCredentials) -> Unit,
3637
val onCopyUsername: (credentials: LoginCredentials) -> Unit,
3738
val onCopyPassword: (credentials: LoginCredentials) -> Unit
@@ -66,8 +67,8 @@ class AutofillManagementRecyclerAdapter(
6667
private fun onBindViewHolderCredential(position: Int, viewHolder: CredentialsViewHolder) {
6768
val item = listItems[position] as ListItem.Credential
6869
with(viewHolder.binding) {
69-
username.text = item.credentials.username
70-
domain.text = item.credentials.domain
70+
title.text = item.credentials.username
71+
subtitle.text = titleExtractor.extract(item.credentials)
7172
root.setOnClickListener { onCredentialSelected(item.credentials) }
7273
updateFavicon(item.credentials)
7374
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2022 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.autofill.ui.credential.management
18+
19+
import com.duckduckgo.autofill.AutofillDomainFormatter
20+
import com.duckduckgo.autofill.domain.app.LoginCredentials
21+
import com.duckduckgo.di.scopes.FragmentScope
22+
import com.squareup.anvil.annotations.ContributesBinding
23+
import javax.inject.Inject
24+
25+
interface LoginCredentialTitleExtractor {
26+
fun extract(credential: LoginCredentials): String
27+
}
28+
29+
@ContributesBinding(FragmentScope::class)
30+
class TitleOrDomainExtractor @Inject constructor(private val domainExtractor: AutofillDomainFormatter) : LoginCredentialTitleExtractor {
31+
32+
override fun extract(credential: LoginCredentials): String {
33+
val title = credential.domainTitle
34+
if (title != null && title.isNotBlank()) {
35+
return title
36+
}
37+
38+
return domainExtractor.extractDomain(credential.domain) ?: ""
39+
}
40+
}

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/ui/credential/management/viewing/AutofillManagementListMode.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.duckduckgo.autofill.impl.databinding.FragmentAutofillManagementListMo
3535
import com.duckduckgo.autofill.ui.credential.management.AutofillManagementRecyclerAdapter
3636
import com.duckduckgo.autofill.ui.credential.management.AutofillSettingsViewModel
3737
import com.duckduckgo.autofill.ui.credential.management.CredentialGrouper
38+
import com.duckduckgo.autofill.ui.credential.management.LoginCredentialTitleExtractor
3839
import com.duckduckgo.di.scopes.FragmentScope
3940
import com.duckduckgo.mobile.android.ui.view.quietlySetIsChecked
4041
import dagger.android.support.AndroidSupportInjection
@@ -54,6 +55,9 @@ class AutofillManagementListMode : Fragment() {
5455
@Inject
5556
lateinit var credentialGrouper: CredentialGrouper
5657

58+
@Inject
59+
lateinit var titleExtractor: LoginCredentialTitleExtractor
60+
5761
val viewModel by lazy {
5862
ViewModelProvider(requireActivity(), viewModelFactory)[AutofillSettingsViewModel::class.java]
5963
}
@@ -129,6 +133,7 @@ class AutofillManagementListMode : Fragment() {
129133
this,
130134
faviconManager = faviconManager,
131135
grouper = credentialGrouper,
136+
titleExtractor = titleExtractor,
132137
onCredentialSelected = this::onCredentialsSelected,
133138
onCopyUsername = this::onCopyUsername,
134139
onCopyPassword = this::onCopyPassword,

autofill/autofill-impl/src/main/res/layout/item_row_autofill_credentials_management_screen.xml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,30 @@
3535
app:layout_constraintTop_toTopOf="parent" />
3636

3737
<TextView
38-
android:id="@+id/domain"
38+
android:id="@+id/title"
3939
android:layout_width="0dp"
4040
android:layout_height="0dp"
4141
tools:text="duckduckgo.com"
4242
android:textColor="?normalTextColor"
43+
android:textSize="16sp"
4344
android:layout_marginStart="12dp"
4445
app:layout_constraintStart_toEndOf="@id/favicon"
4546
app:layout_constraintEnd_toEndOf="parent"
46-
app:layout_constraintBottom_toTopOf="@id/username"
47+
app:layout_constraintBottom_toTopOf="@id/subtitle"
4748
app:layout_constraintTop_toTopOf="@id/favicon" />
4849

4950
<TextView
50-
android:id="@+id/username"
51+
android:id="@+id/subtitle"
5152
android:layout_width="0dp"
5253
android:layout_height="wrap_content"
5354
android:layout_gravity="bottom"
5455
android:gravity="center_vertical"
55-
android:textColor="?normalTextColor"
56+
android:textColor="?attr/autofillCredentialListSubtitleColor"
57+
android:textSize="14sp"
5658
app:layout_constraintBottom_toBottomOf="@id/favicon"
57-
app:layout_constraintEnd_toEndOf="@id/domain"
58-
app:layout_constraintStart_toStartOf="@id/domain"
59-
app:layout_constraintTop_toBottomOf="@id/domain"
59+
app:layout_constraintEnd_toEndOf="@id/title"
60+
app:layout_constraintStart_toStartOf="@id/title"
61+
app:layout_constraintTop_toBottomOf="@id/title"
6062
tools:text="username" />
6163

6264
</androidx.constraintlayout.widget.ConstraintLayout>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2022 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.autofill.ui.credential.management
18+
19+
import com.duckduckgo.autofill.AutofillDomainFormatter
20+
import com.duckduckgo.autofill.domain.app.LoginCredentials
21+
import org.junit.Assert.assertEquals
22+
import org.junit.Test
23+
import org.mockito.kotlin.mock
24+
import org.mockito.kotlin.never
25+
import org.mockito.kotlin.verify
26+
27+
class TitleOrDomainExtractorTest {
28+
29+
private val domainFormatter: AutofillDomainFormatter = mock()
30+
private val testee = TitleOrDomainExtractor(domainFormatter)
31+
32+
@Test
33+
fun whenTitleIsNullThenDomainIsUsed() {
34+
val result = testee.extract(creds(title = null, domain = "example.com"))
35+
verify(domainFormatter).extractDomain("example.com")
36+
}
37+
38+
@Test
39+
fun whenTitleIsEmptyThenDomainIsUsed() {
40+
val result = testee.extract(creds(title = "", domain = "example.com"))
41+
verify(domainFormatter).extractDomain("example.com")
42+
}
43+
44+
@Test
45+
fun whenTitleIsBlankThenDomainIsUsed() {
46+
val result = testee.extract(creds(title = " ", domain = "example.com"))
47+
verify(domainFormatter).extractDomain("example.com")
48+
}
49+
50+
@Test
51+
fun whenTitleIsPopulatedThenDomainNotUsed() {
52+
val result = testee.extract(creds(title = "Site", domain = "example.com"))
53+
assertEquals("Site", result)
54+
verify(domainFormatter, never()).extractDomain("example.com")
55+
}
56+
57+
private fun creds(title: String?, domain: String?) = LoginCredentials(
58+
domainTitle = title,
59+
domain = domain,
60+
username = null,
61+
password = null
62+
)
63+
64+
}

common-ui/src/main/res/values/attrs-autofill.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717
<resources>
1818
<attr name="autofillDialogTitleColor" format="color" />
1919
<attr name="autofillDialogOnboardingExplanationColor" format="color" />
20+
<attr name="autofillCredentialListSubtitleColor" format="color" />
2021
</resources>

common-ui/src/main/res/values/themes.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146

147147
<item name="autofillDialogTitleColor">@color/white</item>
148148
<item name="autofillDialogOnboardingExplanationColor">@color/gray50</item>
149+
<item name="autofillCredentialListSubtitleColor">@color/paleBrownishGray</item>
149150
</style>
150151

151152
<style name="AppTheme.Dark" parent="AppTheme.BaseDark" />
@@ -269,6 +270,7 @@
269270

270271
<item name="autofillDialogTitleColor">@color/gray90</item>
271272
<item name="autofillDialogOnboardingExplanationColor">@color/gray70</item>
273+
<item name="autofillCredentialListSubtitleColor">@color/brownishGrayTwo</item>
272274
<item name="appTPHeaderBackground">@color/light_blue</item>
273275
</style>
274276

0 commit comments

Comments
 (0)