mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-28 12:44:34 +01:00
Username UX polish.
This commit is contained in:
@@ -23,14 +23,14 @@ import androidx.navigation.fragment.NavHostFragment;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import org.signal.core.util.EditTextUtil;
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
|
||||
import org.thoughtcrime.securesms.databinding.UsernameEditFragmentBinding;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.FragmentResultContract;
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
|
||||
@@ -157,58 +157,27 @@ public class UsernameEditFragment extends LoggingFragment {
|
||||
|
||||
binding.root.setLayoutTransition(ANIMATED_LAYOUT);
|
||||
|
||||
switch (state.usernameStatus) {
|
||||
case NONE:
|
||||
usernameInputWrapper.setError(null);
|
||||
break;
|
||||
case TOO_SHORT:
|
||||
case TOO_LONG:
|
||||
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_must_be_between_a_and_b_characters, UsernameUtil.MIN_NICKNAME_LENGTH, UsernameUtil.MAX_NICKNAME_LENGTH));
|
||||
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
|
||||
CharSequence error = switch (state.usernameStatus) {
|
||||
case NONE -> null;
|
||||
case TOO_SHORT, TOO_LONG -> getString(R.string.UsernameEditFragment_usernames_must_be_between_a_and_b_characters, UsernameUtil.MIN_NICKNAME_LENGTH, UsernameUtil.MAX_NICKNAME_LENGTH);
|
||||
case INVALID_CHARACTERS -> getString(R.string.UsernameEditFragment_usernames_can_only_include);
|
||||
case CANNOT_START_WITH_NUMBER -> getString(R.string.UsernameEditFragment_usernames_cannot_begin_with_a_number);
|
||||
case INVALID_GENERIC -> getString(R.string.UsernameEditFragment_username_is_invalid);
|
||||
case TAKEN -> getString(R.string.UsernameEditFragment_this_username_is_taken);
|
||||
case DISCRIMINATOR_HAS_INVALID_CHARACTERS, DISCRIMINATOR_NOT_AVAILABLE -> getString(R.string.UsernameEditFragment__this_username_is_not_available_try_another_number);
|
||||
case DISCRIMINATOR_TOO_LONG -> getString(R.string.UsernameEditFragment__invalid_username_enter_a_maximum_of_d_digits, UsernameUtil.MAX_DISCRIMINATOR_LENGTH);
|
||||
case DISCRIMINATOR_TOO_SHORT -> getString(R.string.UsernameEditFragment__invalid_username_enter_a_minimum_of_d_digits, UsernameUtil.MIN_DISCRIMINATOR_LENGTH);
|
||||
};
|
||||
|
||||
break;
|
||||
case INVALID_CHARACTERS:
|
||||
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_can_only_include));
|
||||
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
|
||||
int colorRes = error != null ? R.color.signal_colorError : R.color.signal_colorPrimary;
|
||||
int color = ContextCompat.getColor(requireContext(), colorRes);
|
||||
|
||||
break;
|
||||
case CANNOT_START_WITH_NUMBER:
|
||||
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_cannot_begin_with_a_number));
|
||||
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
|
||||
|
||||
break;
|
||||
case INVALID_GENERIC:
|
||||
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_username_is_invalid));
|
||||
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
|
||||
|
||||
break;
|
||||
case TAKEN:
|
||||
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_this_username_is_taken));
|
||||
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
|
||||
|
||||
break;
|
||||
case DISCRIMINATOR_HAS_INVALID_CHARACTERS:
|
||||
case DISCRIMINATOR_NOT_AVAILABLE:
|
||||
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment__this_username_is_not_available_try_another_number));
|
||||
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
|
||||
|
||||
break;
|
||||
case DISCRIMINATOR_TOO_LONG:
|
||||
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment__invalid_username_enter_a_maximum_of_d_digits, UsernameUtil.MAX_DISCRIMINATOR_LENGTH));
|
||||
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
|
||||
|
||||
break;
|
||||
case DISCRIMINATOR_TOO_SHORT:
|
||||
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment__invalid_username_enter_a_minimum_of_d_digits, UsernameUtil.MIN_DISCRIMINATOR_LENGTH));
|
||||
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
CharSequence error = usernameInputWrapper.getError();
|
||||
binding.usernameTextFocusedStroke.setBackgroundColor(color);
|
||||
binding.usernameTextWrapper.setHintTextColor(ColorStateList.valueOf(color));
|
||||
EditTextUtil.setCursorColor(binding.usernameText, color);
|
||||
EditTextUtil.setCursorColor(binding.discriminatorText, color);
|
||||
binding.usernameError.setVisibility(error != null ? View.VISIBLE : View.GONE);
|
||||
binding.usernameError.setText(usernameInputWrapper.getError());
|
||||
|
||||
binding.usernameError.setText(error);
|
||||
binding.root.setLayoutTransition(STATIC_LAYOUT);
|
||||
}
|
||||
|
||||
@@ -224,9 +193,7 @@ public class UsernameEditFragment extends LoggingFragment {
|
||||
if (usernameState.getUsername() != null) {
|
||||
binding.summary.setText(usernameState.getUsername().getUsername());
|
||||
binding.summary.setAlpha(1f);
|
||||
} else if (usernameState instanceof UsernameState.Loading) {
|
||||
binding.summary.setAlpha(0.5f);
|
||||
} else {
|
||||
} else if (!(usernameState instanceof UsernameState.Loading)) {
|
||||
binding.summary.setText(R.string.UsernameEditFragment__choose_your_username);
|
||||
binding.summary.setAlpha(1f);
|
||||
}
|
||||
|
||||
@@ -83,23 +83,11 @@ internal class UsernameEditViewModel private constructor(private val isInRegistr
|
||||
)
|
||||
}
|
||||
|
||||
val invalidReason: InvalidReason? = checkUsername(nickname)
|
||||
|
||||
if (invalidReason != null) {
|
||||
// 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,
|
||||
usernameState = state.usernameState
|
||||
)
|
||||
} else {
|
||||
State(
|
||||
buttonState = ButtonState.SUBMIT_DISABLED,
|
||||
usernameStatus = UsernameStatus.NONE,
|
||||
usernameState = state.usernameState
|
||||
)
|
||||
}
|
||||
State(
|
||||
buttonState = ButtonState.SUBMIT_DISABLED,
|
||||
usernameStatus = UsernameStatus.NONE,
|
||||
usernameState = state.usernameState
|
||||
)
|
||||
}
|
||||
|
||||
stateMachineStore.update {
|
||||
@@ -270,8 +258,6 @@ internal class UsernameEditViewModel private constructor(private val isInRegistr
|
||||
return
|
||||
}
|
||||
|
||||
uiState.update { State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, UsernameState.Loading) }
|
||||
|
||||
val isDiscriminatorSetByUser = state is UsernameEditStateMachine.UserEnteredDiscriminator || state is UsernameEditStateMachine.UserEnteredNicknameAndDiscriminator
|
||||
val discriminator = if (isDiscriminatorSetByUser) {
|
||||
state.discriminator
|
||||
@@ -281,16 +267,17 @@ internal class UsernameEditViewModel private constructor(private val isInRegistr
|
||||
|
||||
val discriminatorInvalidReason = checkDiscriminator(discriminator)
|
||||
if (isDiscriminatorSetByUser && discriminatorInvalidReason != null) {
|
||||
uiState.update { s ->
|
||||
State(
|
||||
uiState.update { uiState ->
|
||||
uiState.copy(
|
||||
buttonState = ButtonState.SUBMIT_DISABLED,
|
||||
usernameStatus = mapDiscriminatorError(discriminatorInvalidReason),
|
||||
usernameState = s.usernameState
|
||||
usernameStatus = mapDiscriminatorError(discriminatorInvalidReason)
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
uiState.update { State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, UsernameState.Loading) }
|
||||
|
||||
disposables += UsernameRepository.reserveUsername(nickname, discriminator).subscribe { result: Result<UsernameState.Reserved, UsernameSetResult> ->
|
||||
result.either(
|
||||
onSuccess = { reserved: UsernameState.Reserved ->
|
||||
|
||||
9
app/src/main/res/drawable/username_edit_box_fill.xml
Normal file
9
app/src/main/res/drawable/username_edit_box_fill.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/signal_colorSurfaceVariant" />
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" />
|
||||
</shape>
|
||||
@@ -39,7 +39,7 @@
|
||||
android:id="@+id/username_box_fill"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/signal_colorSurfaceVariant"
|
||||
android:background="@drawable/username_edit_box_fill"
|
||||
app:layout_constraintBottom_toBottomOf="@id/username_text_wrapper"
|
||||
app:layout_constraintEnd_toEndOf="@id/discriminator_text"
|
||||
app:layout_constraintStart_toStartOf="@id/username_text_wrapper"
|
||||
@@ -92,9 +92,10 @@
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="number"
|
||||
android:maxLength="19"
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:minHeight="48dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/username_text_wrapper"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -105,12 +106,13 @@
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:indicatorColor="@color/signal_colorOnSurfaceVariant"
|
||||
app:indicatorSize="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/username_text_wrapper"
|
||||
app:layout_constraintEnd_toStartOf="@id/discriminator_text"
|
||||
app:trackColor="@color/transparent"
|
||||
app:trackThickness="1dp" />
|
||||
app:trackThickness="1.75dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
@@ -134,7 +136,8 @@
|
||||
android:id="@+id/username_error"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_colorError"
|
||||
android:visibility="gone"
|
||||
@@ -161,7 +164,6 @@
|
||||
|
||||
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
|
||||
android:id="@+id/username_description"
|
||||
style="@style/Signal.Text.Caption"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dsl_settings_gutter"
|
||||
|
||||
Reference in New Issue
Block a user