From 528e301ce4c8c2eaf0c5c659b5a56d3b6f23070a Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 2 Nov 2023 16:05:08 -0400 Subject: [PATCH] Improve username creation error debouncing. --- .../profiles/manage/UsernameEditFragment.java | 20 ++++++++++++---- .../profiles/manage/UsernameEditViewModel.kt | 24 ++++++++++++++++--- .../res/layout/username_edit_fragment.xml | 1 + 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java index 2a1b91db02..7509734e80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.profiles.manage; +import android.animation.LayoutTransition; import android.content.Intent; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; @@ -9,8 +10,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; @@ -24,8 +23,6 @@ import androidx.navigation.Navigation; import androidx.navigation.fragment.NavHostFragment; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.progressindicator.CircularProgressIndicatorSpec; -import com.google.android.material.progressindicator.IndeterminateDrawable; import com.google.android.material.textfield.TextInputLayout; import org.signal.core.util.DimensionUnit; @@ -52,6 +49,17 @@ public class UsernameEditFragment extends LoggingFragment { private LifecycleDisposable lifecycleDisposable; private UsernameEditFragmentArgs args; + private static final LayoutTransition ANIMATED_LAYOUT = new LayoutTransition(); + private static final LayoutTransition STATIC_LAYOUT = new LayoutTransition(); + + static { + STATIC_LAYOUT.disableTransitionType(LayoutTransition.CHANGE_APPEARING); + STATIC_LAYOUT.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); + STATIC_LAYOUT.disableTransitionType(LayoutTransition.APPEARING); + STATIC_LAYOUT.disableTransitionType(LayoutTransition.DISAPPEARING); + STATIC_LAYOUT.disableTransitionType(LayoutTransition.CHANGING); + } + public static UsernameEditFragment newInstance() { return new UsernameEditFragment(); } @@ -155,6 +163,8 @@ public class UsernameEditFragment extends LoggingFragment { presentButtonState(state.buttonState); presentSummary(state.username); + binding.root.setLayoutTransition(ANIMATED_LAYOUT); + switch (state.usernameStatus) { case NONE: usernameInputWrapper.setError(null); @@ -190,6 +200,8 @@ public class UsernameEditFragment extends LoggingFragment { CharSequence error = usernameInputWrapper.getError(); binding.usernameError.setVisibility(error != null ? View.VISIBLE : View.GONE); binding.usernameError.setText(usernameInputWrapper.getError()); + + binding.root.setLayoutTransition(STATIC_LAYOUT); } private void presentButtonState(@NonNull UsernameEditViewModel.ButtonState buttonState) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt index c1166cfe7e..0fa1d4df08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt @@ -71,9 +71,19 @@ internal class UsernameEditViewModel private constructor(private val isInRegistr val invalidReason: InvalidReason? = checkUsername(nickname) if (invalidReason != null) { - State(ButtonState.SUBMIT_DISABLED, mapUsernameError(invalidReason), state.username) + // We only want to show actual errors after debouncing. But we also don't want to allow users to submit names with errors. + // So we disable submit, but we don't show an error yet. + State( + buttonState = ButtonState.SUBMIT_DISABLED, + usernameStatus = UsernameStatus.NONE, + username = state.username + ) } else { - State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, state.username) + State( + buttonState = ButtonState.SUBMIT_DISABLED, + usernameStatus = UsernameStatus.NONE, + username = state.username + ) } } @@ -169,6 +179,13 @@ internal class UsernameEditViewModel private constructor(private val isInRegistr val invalidReason: InvalidReason? = checkUsername(nickname) if (invalidReason != null) { + uiState.update { state -> + State( + buttonState = ButtonState.SUBMIT_DISABLED, + usernameStatus = mapUsernameError(invalidReason), + username = state.username + ) + } return } @@ -229,7 +246,8 @@ internal class UsernameEditViewModel private constructor(private val isInRegistr } companion object { - private const val NICKNAME_PUBLISHER_DEBOUNCE_TIMEOUT_MILLIS: Long = 500 + private const val NICKNAME_PUBLISHER_DEBOUNCE_TIMEOUT_MILLIS: Long = 1000 + private fun mapUsernameError(invalidReason: InvalidReason): UsernameStatus { return when (invalidReason) { InvalidReason.TOO_SHORT -> UsernameStatus.TOO_SHORT diff --git a/app/src/main/res/layout/username_edit_fragment.xml b/app/src/main/res/layout/username_edit_fragment.xml index f273550a31..dee44d61ec 100644 --- a/app/src/main/res/layout/username_edit_fragment.xml +++ b/app/src/main/res/layout/username_edit_fragment.xml @@ -2,6 +2,7 @@