Loading app/src/main/java/org/mozilla/fenix/settings/logins/EditLoginFragment.kt +117 −34 Original line number Diff line number Diff line Loading @@ -7,30 +7,24 @@ package org.mozilla.fenix.settings.logins import android.os.Bundle import android.text.Editable import android.text.InputType import android.text.TextWatcher import android.util.Log import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_edit_login.inputLayoutPassword import kotlinx.android.synthetic.main.fragment_edit_login.inputLayoutUsername import kotlinx.android.synthetic.main.fragment_edit_login.hostnameText import kotlinx.android.synthetic.main.fragment_edit_login.usernameText import kotlinx.android.synthetic.main.fragment_edit_login.passwordText import kotlinx.android.synthetic.main.fragment_edit_login.clearUsernameTextButton import kotlinx.android.synthetic.main.fragment_edit_login.clearPasswordTextButton import kotlinx.android.synthetic.main.fragment_edit_login.revealPasswordButton import kotlinx.coroutines.Deferred import kotlinx.coroutines.launch import kotlinx.android.synthetic.main.fragment_edit_login.* import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.components.concept.storage.Login import mozilla.components.service.sync.logins.InvalidRecordException Loading @@ -38,7 +32,6 @@ import mozilla.components.service.sync.logins.LoginsStorageException import mozilla.components.service.sync.logins.NoSuchRecordException import mozilla.components.support.ktx.android.view.hideKeyboard import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components Loading @@ -55,10 +48,12 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { private lateinit var savedLoginsStore: LoginsFragmentStore fun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this) private lateinit var oldLogin: SavedLogin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) oldLogin = args.savedLoginItem savedLoginsStore = StoreProvider.get(this) { LoginsFragmentStore( LoginsListState( Loading @@ -82,12 +77,16 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { hostnameText.isFocusable = false usernameText.text = args.savedLoginItem.username.toEditable() passwordText.text = args.savedLoginItem.password!!.toEditable() passwordText.text = args.savedLoginItem.password.toEditable() // TODO: extend PasswordTransformationMethod() to change bullets to asterisks passwordText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD passwordText.compoundDrawablePadding = requireContext().resources .getDimensionPixelOffset(R.dimen.saved_logins_end_icon_drawable_padding) setUpClickListeners() setUpTextListeners() } private fun setUpClickListeners() { Loading @@ -96,18 +95,100 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { usernameText.isCursorVisible = true usernameText.hasFocus() inputLayoutUsername.hasFocus() it.isEnabled = false } clearPasswordTextButton.setOnClickListener { passwordText.text?.clear() passwordText.isCursorVisible = true passwordText.hasFocus() inputLayoutPassword.hasFocus() it.isEnabled = false } revealPasswordButton.setOnClickListener { togglePasswordReveal() } var firstClick = true passwordText.setOnClickListener { if (firstClick) { togglePasswordReveal() firstClick = false } } } private fun setUpTextListeners() { val frag = view?.findViewById<View>(R.id.editLoginFragment) frag?.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> if (hasFocus) { view?.hideKeyboard() } } editLoginLayout.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> if (!hasFocus) { view?.hideKeyboard() } } usernameText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(u: Editable?) { if (u.toString() == oldLogin.username) { inputLayoutUsername.error = null inputLayoutUsername.errorIconDrawable = null } else { clearUsernameTextButton.isEnabled = true // setDupeError() TODO in #10173 } } override fun beforeTextChanged(u: CharSequence?, start: Int, count: Int, after: Int) { // NOOP } override fun onTextChanged(u: CharSequence?, start: Int, before: Int, count: Int) { // NOOP } }) passwordText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(p: Editable?) { when { p.toString().isEmpty() -> { clearPasswordTextButton.isEnabled = false setPasswordError() } p.toString() == oldLogin.password -> { inputLayoutPassword.error = null inputLayoutPassword.errorIconDrawable = null clearPasswordTextButton.isEnabled = true } else -> { inputLayoutPassword.error = null inputLayoutPassword.errorIconDrawable = null clearPasswordTextButton.isEnabled = true } } } override fun beforeTextChanged(p: CharSequence?, start: Int, count: Int, after: Int) { // NOOP } override fun onTextChanged(p: CharSequence?, start: Int, before: Int, count: Int) { // NOOP } }) } private fun setPasswordError() { inputLayoutPassword?.let { layout -> layout.error = context?.getString(R.string.saved_login_password_required) layout.setErrorIconDrawable(R.drawable.mozac_ic_warning) layout.errorIconDrawable?.setTint( ContextCompat.getColor(requireContext(), R.color.design_default_color_error) ) } } Loading @@ -126,25 +207,25 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { R.id.save_login_button -> { view?.hideKeyboard() try { if (!passwordText.text.isNullOrBlank()) { try { attemptSaveAndExit() } else { view?.let { FenixSnackbar.make( view = it, duration = Snackbar.LENGTH_SHORT, isDisplayedWithBrowserToolbar = false ).setText(getString(R.string.saved_login_password_required)).show() } } } catch (loginException: LoginsStorageException) { when (loginException) { is NoSuchRecordException, is InvalidRecordException -> { Log.e("Edit login", "Failed to save edited login.", loginException) Log.e( "Edit login", "Failed to save edited login.", loginException ) } else -> Log.e( "Edit login", "Failed to save edited login.", loginException ) } else -> Log.e("Edit login", "Failed to save edited login.", loginException) } } true Loading @@ -158,9 +239,11 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { var saveLoginJob: Deferred<Unit>? = null viewLifecycleOwner.lifecycleScope.launch(IO) { saveLoginJob = async { val oldLogin = requireContext().components.core.passwordsStorage.get(args.savedLoginItem.guid) val oldLogin = requireContext().components.core.passwordsStorage.get(args.savedLoginItem.guid) // Update requires a Login type, which needs at least one of httpRealm or formActionOrigin // Update requires a Login type, which needs at least one of // httpRealm or formActionOrigin val loginToSave = Login( guid = oldLogin?.guid, origin = oldLogin?.origin!!, Loading app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt +1 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ data class SavedLogin( val guid: String, val origin: String, val username: String, val password: String?, val password: String, val timeLastUsed: Long ) : Parcelable Loading app/src/main/res/color/saved_login_clear_edit_text_tint.xml 0 → 100644 +10 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- 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/. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="true" android:color="?primaryText" /> <item android:state_enabled="false" android:color="@android:color/transparent" /> </selector> No newline at end of file app/src/main/res/layout/fragment_edit_login.xml +11 −9 Original line number Diff line number Diff line Loading @@ -12,7 +12,9 @@ android:layout_height="wrap_content" android:layout_marginStart="72dp" android:layout_marginEnd="20dp" android:layout_marginTop="12dp" > android:layout_marginTop="12dp" android:clickable="true" android:focusable="true" > <TextView android:id="@+id/hostnameHeaderText" Loading Loading @@ -125,7 +127,7 @@ android:clickable="true" android:focusable="true" android:cursorVisible="true" android:textCursorDrawable="?primaryText" android:textCursorDrawable="@null" app:backgroundTint="?primaryText" tools:ignore="Autofill"/> </com.google.android.material.textfield.TextInputLayout> Loading @@ -135,9 +137,9 @@ android:layout_width="48dp" android:layout_height="30dp" android:layout_marginBottom="10dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_login_copy_username" app:tint="?android:colorAccent" app:tint="@color/saved_login_clear_edit_text_tint" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/inputLayoutUsername" app:srcCompat="@drawable/ic_clear" /> Loading Loading @@ -186,6 +188,7 @@ android:colorControlActivated="?primaryText" android:colorControlHighlight="?primaryText" android:cursorVisible="true" android:textCursorDrawable="@null" android:ellipsize="end" android:focusable="true" android:fontFamily="sans-serif" Loading @@ -195,7 +198,6 @@ android:maxLines="1" android:singleLine="true" android:textColor="?primaryText" android:textCursorDrawable="?primaryText" android:textSize="16sp" android:textStyle="normal" app:backgroundTint="?primaryText" Loading @@ -207,9 +209,9 @@ android:layout_width="48dp" android:layout_height="30dp" android:layout_marginTop="3dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_login_reveal_password" app:tint="?android:colorAccent" app:tint="?primaryText" app:layout_constraintEnd_toStartOf="@id/clearPasswordTextButton" app:layout_constraintTop_toTopOf="@id/inputLayoutPassword" app:srcCompat="@drawable/mozac_ic_password_reveal" /> Loading @@ -218,9 +220,9 @@ android:id="@+id/clearPasswordTextButton" android:layout_width="48dp" android:layout_height="30dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_logins_copy_password" app:tint="?android:colorAccent" app:tint="@color/saved_login_clear_edit_text_tint" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/revealPasswordButton" app:srcCompat="@drawable/ic_clear" /> Loading app/src/main/res/layout/fragment_login_detail.xml +2 −3 Original line number Diff line number Diff line Loading @@ -124,7 +124,6 @@ android:layout_width="0dp" android:layout_height="30dp" android:gravity="center_vertical" android:inputType="textPassword|text" android:letterSpacing="0.01" android:lineSpacingExtra="8sp" android:layout_marginTop="2dp" Loading @@ -142,7 +141,7 @@ android:layout_width="48dp" android:layout_height="30dp" android:layout_marginBottom="2dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_login_reveal_password" app:layout_constraintBottom_toBottomOf="@id/passwordText" app:layout_constraintEnd_toStartOf="@id/copyPassword" Loading @@ -153,7 +152,7 @@ android:id="@+id/copyPassword" android:layout_width="48dp" android:layout_height="30dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_logins_copy_password" app:layout_constraintBottom_toBottomOf="@id/revealPasswordButton" app:layout_constraintEnd_toEndOf="parent" Loading Loading
app/src/main/java/org/mozilla/fenix/settings/logins/EditLoginFragment.kt +117 −34 Original line number Diff line number Diff line Loading @@ -7,30 +7,24 @@ package org.mozilla.fenix.settings.logins import android.os.Bundle import android.text.Editable import android.text.InputType import android.text.TextWatcher import android.util.Log import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_edit_login.inputLayoutPassword import kotlinx.android.synthetic.main.fragment_edit_login.inputLayoutUsername import kotlinx.android.synthetic.main.fragment_edit_login.hostnameText import kotlinx.android.synthetic.main.fragment_edit_login.usernameText import kotlinx.android.synthetic.main.fragment_edit_login.passwordText import kotlinx.android.synthetic.main.fragment_edit_login.clearUsernameTextButton import kotlinx.android.synthetic.main.fragment_edit_login.clearPasswordTextButton import kotlinx.android.synthetic.main.fragment_edit_login.revealPasswordButton import kotlinx.coroutines.Deferred import kotlinx.coroutines.launch import kotlinx.android.synthetic.main.fragment_edit_login.* import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.components.concept.storage.Login import mozilla.components.service.sync.logins.InvalidRecordException Loading @@ -38,7 +32,6 @@ import mozilla.components.service.sync.logins.LoginsStorageException import mozilla.components.service.sync.logins.NoSuchRecordException import mozilla.components.support.ktx.android.view.hideKeyboard import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components Loading @@ -55,10 +48,12 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { private lateinit var savedLoginsStore: LoginsFragmentStore fun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this) private lateinit var oldLogin: SavedLogin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) oldLogin = args.savedLoginItem savedLoginsStore = StoreProvider.get(this) { LoginsFragmentStore( LoginsListState( Loading @@ -82,12 +77,16 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { hostnameText.isFocusable = false usernameText.text = args.savedLoginItem.username.toEditable() passwordText.text = args.savedLoginItem.password!!.toEditable() passwordText.text = args.savedLoginItem.password.toEditable() // TODO: extend PasswordTransformationMethod() to change bullets to asterisks passwordText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD passwordText.compoundDrawablePadding = requireContext().resources .getDimensionPixelOffset(R.dimen.saved_logins_end_icon_drawable_padding) setUpClickListeners() setUpTextListeners() } private fun setUpClickListeners() { Loading @@ -96,18 +95,100 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { usernameText.isCursorVisible = true usernameText.hasFocus() inputLayoutUsername.hasFocus() it.isEnabled = false } clearPasswordTextButton.setOnClickListener { passwordText.text?.clear() passwordText.isCursorVisible = true passwordText.hasFocus() inputLayoutPassword.hasFocus() it.isEnabled = false } revealPasswordButton.setOnClickListener { togglePasswordReveal() } var firstClick = true passwordText.setOnClickListener { if (firstClick) { togglePasswordReveal() firstClick = false } } } private fun setUpTextListeners() { val frag = view?.findViewById<View>(R.id.editLoginFragment) frag?.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> if (hasFocus) { view?.hideKeyboard() } } editLoginLayout.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> if (!hasFocus) { view?.hideKeyboard() } } usernameText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(u: Editable?) { if (u.toString() == oldLogin.username) { inputLayoutUsername.error = null inputLayoutUsername.errorIconDrawable = null } else { clearUsernameTextButton.isEnabled = true // setDupeError() TODO in #10173 } } override fun beforeTextChanged(u: CharSequence?, start: Int, count: Int, after: Int) { // NOOP } override fun onTextChanged(u: CharSequence?, start: Int, before: Int, count: Int) { // NOOP } }) passwordText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(p: Editable?) { when { p.toString().isEmpty() -> { clearPasswordTextButton.isEnabled = false setPasswordError() } p.toString() == oldLogin.password -> { inputLayoutPassword.error = null inputLayoutPassword.errorIconDrawable = null clearPasswordTextButton.isEnabled = true } else -> { inputLayoutPassword.error = null inputLayoutPassword.errorIconDrawable = null clearPasswordTextButton.isEnabled = true } } } override fun beforeTextChanged(p: CharSequence?, start: Int, count: Int, after: Int) { // NOOP } override fun onTextChanged(p: CharSequence?, start: Int, before: Int, count: Int) { // NOOP } }) } private fun setPasswordError() { inputLayoutPassword?.let { layout -> layout.error = context?.getString(R.string.saved_login_password_required) layout.setErrorIconDrawable(R.drawable.mozac_ic_warning) layout.errorIconDrawable?.setTint( ContextCompat.getColor(requireContext(), R.color.design_default_color_error) ) } } Loading @@ -126,25 +207,25 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { R.id.save_login_button -> { view?.hideKeyboard() try { if (!passwordText.text.isNullOrBlank()) { try { attemptSaveAndExit() } else { view?.let { FenixSnackbar.make( view = it, duration = Snackbar.LENGTH_SHORT, isDisplayedWithBrowserToolbar = false ).setText(getString(R.string.saved_login_password_required)).show() } } } catch (loginException: LoginsStorageException) { when (loginException) { is NoSuchRecordException, is InvalidRecordException -> { Log.e("Edit login", "Failed to save edited login.", loginException) Log.e( "Edit login", "Failed to save edited login.", loginException ) } else -> Log.e( "Edit login", "Failed to save edited login.", loginException ) } else -> Log.e("Edit login", "Failed to save edited login.", loginException) } } true Loading @@ -158,9 +239,11 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { var saveLoginJob: Deferred<Unit>? = null viewLifecycleOwner.lifecycleScope.launch(IO) { saveLoginJob = async { val oldLogin = requireContext().components.core.passwordsStorage.get(args.savedLoginItem.guid) val oldLogin = requireContext().components.core.passwordsStorage.get(args.savedLoginItem.guid) // Update requires a Login type, which needs at least one of httpRealm or formActionOrigin // Update requires a Login type, which needs at least one of // httpRealm or formActionOrigin val loginToSave = Login( guid = oldLogin?.guid, origin = oldLogin?.origin!!, Loading
app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt +1 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ data class SavedLogin( val guid: String, val origin: String, val username: String, val password: String?, val password: String, val timeLastUsed: Long ) : Parcelable Loading
app/src/main/res/color/saved_login_clear_edit_text_tint.xml 0 → 100644 +10 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- 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/. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="true" android:color="?primaryText" /> <item android:state_enabled="false" android:color="@android:color/transparent" /> </selector> No newline at end of file
app/src/main/res/layout/fragment_edit_login.xml +11 −9 Original line number Diff line number Diff line Loading @@ -12,7 +12,9 @@ android:layout_height="wrap_content" android:layout_marginStart="72dp" android:layout_marginEnd="20dp" android:layout_marginTop="12dp" > android:layout_marginTop="12dp" android:clickable="true" android:focusable="true" > <TextView android:id="@+id/hostnameHeaderText" Loading Loading @@ -125,7 +127,7 @@ android:clickable="true" android:focusable="true" android:cursorVisible="true" android:textCursorDrawable="?primaryText" android:textCursorDrawable="@null" app:backgroundTint="?primaryText" tools:ignore="Autofill"/> </com.google.android.material.textfield.TextInputLayout> Loading @@ -135,9 +137,9 @@ android:layout_width="48dp" android:layout_height="30dp" android:layout_marginBottom="10dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_login_copy_username" app:tint="?android:colorAccent" app:tint="@color/saved_login_clear_edit_text_tint" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/inputLayoutUsername" app:srcCompat="@drawable/ic_clear" /> Loading Loading @@ -186,6 +188,7 @@ android:colorControlActivated="?primaryText" android:colorControlHighlight="?primaryText" android:cursorVisible="true" android:textCursorDrawable="@null" android:ellipsize="end" android:focusable="true" android:fontFamily="sans-serif" Loading @@ -195,7 +198,6 @@ android:maxLines="1" android:singleLine="true" android:textColor="?primaryText" android:textCursorDrawable="?primaryText" android:textSize="16sp" android:textStyle="normal" app:backgroundTint="?primaryText" Loading @@ -207,9 +209,9 @@ android:layout_width="48dp" android:layout_height="30dp" android:layout_marginTop="3dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_login_reveal_password" app:tint="?android:colorAccent" app:tint="?primaryText" app:layout_constraintEnd_toStartOf="@id/clearPasswordTextButton" app:layout_constraintTop_toTopOf="@id/inputLayoutPassword" app:srcCompat="@drawable/mozac_ic_password_reveal" /> Loading @@ -218,9 +220,9 @@ android:id="@+id/clearPasswordTextButton" android:layout_width="48dp" android:layout_height="30dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_logins_copy_password" app:tint="?android:colorAccent" app:tint="@color/saved_login_clear_edit_text_tint" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/revealPasswordButton" app:srcCompat="@drawable/ic_clear" /> Loading
app/src/main/res/layout/fragment_login_detail.xml +2 −3 Original line number Diff line number Diff line Loading @@ -124,7 +124,6 @@ android:layout_width="0dp" android:layout_height="30dp" android:gravity="center_vertical" android:inputType="textPassword|text" android:letterSpacing="0.01" android:lineSpacingExtra="8sp" android:layout_marginTop="2dp" Loading @@ -142,7 +141,7 @@ android:layout_width="48dp" android:layout_height="30dp" android:layout_marginBottom="2dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_login_reveal_password" app:layout_constraintBottom_toBottomOf="@id/passwordText" app:layout_constraintEnd_toStartOf="@id/copyPassword" Loading @@ -153,7 +152,7 @@ android:id="@+id/copyPassword" android:layout_width="48dp" android:layout_height="30dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:background="@null" android:contentDescription="@string/saved_logins_copy_password" app:layout_constraintBottom_toBottomOf="@id/revealPasswordButton" app:layout_constraintEnd_toEndOf="parent" Loading