Unverified Commit 137d66a5 authored by Elise Richards's avatar Elise Richards Committed by GitHub
Browse files

For 10172: Set edit text listeners (#11196)

* Set edit text listeners

* Set clearable icons and change with error states

* Clear text buttons show and hide

* Move error checks to afterTextChanged. Refactor. Remove unused color.
parent 31edbc92
Loading
Loading
Loading
Loading
+117 −34
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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(
@@ -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() {
@@ -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)
            )
        }
    }

@@ -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
@@ -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!!,
+1 −1
Original line number Diff line number Diff line
@@ -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

+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
+11 −9
Original line number Diff line number Diff line
@@ -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"
@@ -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>
@@ -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" />
@@ -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"
@@ -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"
@@ -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" />
@@ -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" />
+2 −3
Original line number Diff line number Diff line
@@ -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"
@@ -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"
@@ -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