diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/ViewExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/ui/ViewExtensions.kt index 3dedb16..1b886a6 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/ViewExtensions.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/ViewExtensions.kt @@ -4,12 +4,15 @@ import android.app.Activity import android.os.Build import android.view.View import android.view.WindowInsets +import android.widget.TextView import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity +import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.ChannelResult import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.onClosed import kotlinx.coroutines.channels.onFailure @@ -22,9 +25,7 @@ fun SwipeRefreshLayout.refreshesLiveData(): LiveData { val callbackFlow: Flow = callbackFlow { val listener = SwipeRefreshLayout.OnRefreshListener { Timber.v("Refresh requested") - trySend(Unit) - .onFailure { Timber.e(it, "refreshesFlow: can't send refresh callback") } - .onClosed { Timber.e(it, "refreshesFlow: flow has been closed") } + trySend(Unit).logErrors("refreshesFlow") } Timber.v("Adding refresh request listener") setOnRefreshListener(listener) @@ -60,4 +61,22 @@ fun AppCompatActivity.setActionBarVisibility(isVisible: Boolean) { Timber.v("setActionBarVisibility() called with: isVisible = $isVisible") supportActionBar?.apply { if (isVisible) show() else hide() } ?: Timber.w("setActionBarVisibility: action bar is null") +} + +@ExperimentalCoroutinesApi +fun TextView.textChangesFlow(): Flow = callbackFlow { + Timber.v("textChangesFlow() called") + val textWatcher = doAfterTextChanged { + trySend(it).logErrors("textChangesFlow") + } + awaitClose { + Timber.d("textChangesFlow: flow is closing") + removeTextChangedListener(textWatcher) + } +} + +fun ChannelResult.logErrors(methodName: String): ChannelResult { + onFailure { Timber.e(it, "$methodName: can't send event") } + onClosed { Timber.e(it, "$methodName: flow has been closed") } + return this } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt index 8187a9a..7605e59 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt @@ -9,14 +9,20 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.material.textfield.TextInputLayout import dagger.hilt.android.AndroidEntryPoint import gq.kirmanak.mealient.R import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.* import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding +import gq.kirmanak.mealient.ui.textChangesFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first import timber.log.Timber +@ExperimentalCoroutinesApi @AndroidEntryPoint class AuthenticationFragment : Fragment() { private var _binding: FragmentAuthenticationBinding? = null @@ -107,13 +113,23 @@ class AuthenticationFragment : Fragment() { Timber.v("checkIfInputIsEmpty() called with: input = $input, inputLayout = $inputLayout, errorText = $errorText") val text = input.text?.toString() Timber.d("Input text is \"$text\"") - if (text.isNullOrBlank()) { + if (text.isNullOrEmpty()) { inputLayout.error = errorText() + viewLifecycleOwner.lifecycleScope.launchWhenResumed { + waitUntilNotEmpty(input) + inputLayout.error = null + } return null } return text } + private suspend fun waitUntilNotEmpty(input: EditText) { + Timber.v("waitUntilNotEmpty() called with: input = $input") + input.textChangesFlow().filterNotNull().first { it.isNotEmpty() } + Timber.v("waitUntilNotEmpty() returned") + } + override fun onDestroyView() { super.onDestroyView() Timber.v("onDestroyView() called")