Trim e-mail and username to ease the login process

Spaces aren't visible in EditText and it's possible to
get authentication errors because of that invisible space.
This commit is contained in:
Kirill Kamakin
2022-04-04 21:19:57 +05:00
parent 2e5684c8a3
commit 97ffbff89a
3 changed files with 29 additions and 21 deletions

View File

@@ -7,9 +7,11 @@ import android.view.WindowInsets
import android.widget.EditText import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -21,6 +23,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@@ -83,25 +86,24 @@ fun <T> ChannelResult<T>.logErrors(methodName: String): ChannelResult<T> {
fun EditText.checkIfInputIsEmpty( fun EditText.checkIfInputIsEmpty(
inputLayout: TextInputLayout, inputLayout: TextInputLayout,
lifecycleCoroutineScope: LifecycleCoroutineScope, lifecycleOwner: LifecycleOwner,
errorText: () -> String @StringRes stringId: Int,
trim: Boolean = true,
): String? { ): String? {
Timber.v("checkIfInputIsEmpty() called with: input = $this, inputLayout = $inputLayout, errorText = $errorText") val input = if (trim) text?.trim() else text
val text = text?.toString() val text = input?.toString().orEmpty()
Timber.d("Input text is \"$text\"") Timber.d("Input text is \"$text\"")
if (text.isNullOrEmpty()) { return text.ifEmpty {
inputLayout.error = errorText() inputLayout.error = resources.getString(stringId)
lifecycleCoroutineScope.launchWhenResumed { lifecycleOwner.lifecycleScope.launch {
waitUntilNotEmpty() waitUntilNotEmpty()
inputLayout.error = null inputLayout.error = null
} }
return null null
} }
return text
} }
suspend fun EditText.waitUntilNotEmpty() { suspend fun EditText.waitUntilNotEmpty() {
Timber.v("waitUntilNotEmpty() called with: input = $this")
textChangesFlow().filterNotNull().first { it.isNotEmpty() } textChangesFlow().filterNotNull().first { it.isNotEmpty() }
Timber.v("waitUntilNotEmpty() returned") Timber.v("waitUntilNotEmpty() returned")
} }

View File

@@ -39,13 +39,18 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
private fun onLoginClicked(): Unit = with(binding) { private fun onLoginClicked(): Unit = with(binding) {
Timber.v("onLoginClicked() called") Timber.v("onLoginClicked() called")
val email: String = emailInput.checkIfInputIsEmpty(emailInputLayout, lifecycleScope) { val email: String = emailInput.checkIfInputIsEmpty(
getString(R.string.fragment_authentication_email_input_empty) inputLayout = emailInputLayout,
} ?: return lifecycleOwner = viewLifecycleOwner,
stringId = R.string.fragment_authentication_email_input_empty,
) ?: return
val pass: String = passwordInput.checkIfInputIsEmpty(passwordInputLayout, lifecycleScope) { val pass: String = passwordInput.checkIfInputIsEmpty(
getString(R.string.fragment_authentication_password_input_empty) inputLayout = passwordInputLayout,
} ?: return lifecycleOwner = viewLifecycleOwner,
stringId = R.string.fragment_authentication_password_input_empty,
trim = false,
) ?: return
button.isClickable = false button.isClickable = false
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {

View File

@@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import by.kirich1409.viewbindingdelegate.viewBinding import by.kirich1409.viewbindingdelegate.viewBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -29,9 +28,11 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
private fun onProceedClick(view: View) { private fun onProceedClick(view: View) {
Timber.v("onProceedClick() called with: view = $view") Timber.v("onProceedClick() called with: view = $view")
val url = binding.urlInput.checkIfInputIsEmpty(binding.urlInputLayout, lifecycleScope) { val url = binding.urlInput.checkIfInputIsEmpty(
getString(R.string.fragment_baseurl_url_input_empty) inputLayout = binding.urlInputLayout,
} ?: return lifecycleOwner = viewLifecycleOwner,
stringId = R.string.fragment_baseurl_url_input_empty,
) ?: return
viewModel.saveBaseUrl(url) viewModel.saveBaseUrl(url)
} }