mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-27 04:04:43 +01:00
Redesign FTUX to use Material Design 3.
This commit is contained in:
committed by
Greyson Parrelli
parent
0303467c91
commit
150bbf181d
@@ -21,7 +21,7 @@ import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.registration.CallMeCountDownView;
|
||||
import org.thoughtcrime.securesms.components.registration.ActionCountDownButton;
|
||||
import org.thoughtcrime.securesms.components.registration.VerificationCodeView;
|
||||
import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard;
|
||||
import org.thoughtcrime.securesms.registration.ReceivedSmsEvent;
|
||||
@@ -55,13 +55,11 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
|
||||
private static final String TAG = Log.tag(BaseEnterSmsCodeFragment.class);
|
||||
|
||||
private ScrollView scrollView;
|
||||
private TextView header;
|
||||
private TextView subheader;
|
||||
private VerificationCodeView verificationCodeView;
|
||||
private VerificationPinKeyboard keyboard;
|
||||
private CallMeCountDownView callMeCountDown;
|
||||
private ActionCountDownButton callMeCountDown;
|
||||
private View wrongNumber;
|
||||
private View noCodeReceivedHelp;
|
||||
private View serviceWarning;
|
||||
private boolean autoCompleting;
|
||||
|
||||
private ViewModel viewModel;
|
||||
@@ -80,13 +78,11 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header));
|
||||
|
||||
scrollView = view.findViewById(R.id.scroll_view);
|
||||
header = view.findViewById(R.id.verify_header);
|
||||
subheader = view.findViewById(R.id.verification_subheader);
|
||||
verificationCodeView = view.findViewById(R.id.code);
|
||||
keyboard = view.findViewById(R.id.keyboard);
|
||||
callMeCountDown = view.findViewById(R.id.call_me_count_down);
|
||||
wrongNumber = view.findViewById(R.id.wrong_number);
|
||||
noCodeReceivedHelp = view.findViewById(R.id.no_code);
|
||||
serviceWarning = view.findViewById(R.id.cell_service_warning);
|
||||
|
||||
new SignalStrengthPhoneStateListener(this, this);
|
||||
|
||||
@@ -106,14 +102,12 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
|
||||
}
|
||||
});
|
||||
|
||||
noCodeReceivedHelp.setOnClickListener(v -> sendEmailToSupport());
|
||||
|
||||
disposables.bindTo(getViewLifecycleOwner().getLifecycle());
|
||||
viewModel = getViewModel();
|
||||
viewModel.getSuccessfulCodeRequestAttempts().observe(getViewLifecycleOwner(), (attempts) -> {
|
||||
if (attempts >= 3) {
|
||||
noCodeReceivedHelp.setVisibility(View.VISIBLE);
|
||||
scrollView.postDelayed(() -> scrollView.smoothScrollTo(0, noCodeReceivedHelp.getBottom()), 15000);
|
||||
// TODO Add bottom sheet for help
|
||||
}
|
||||
});
|
||||
|
||||
@@ -330,7 +324,7 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, viewModel.getNumber().getFullFormattedNumber()));
|
||||
subheader.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, viewModel.getNumber().getFullFormattedNumber()));
|
||||
|
||||
viewModel.getCanCallAtTime().observe(getViewLifecycleOwner(), callAtTime -> callMeCountDown.startCountDownTo(callAtTime));
|
||||
}
|
||||
@@ -348,40 +342,11 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
|
||||
|
||||
@Override
|
||||
public void onNoCellSignalPresent() {
|
||||
if (serviceWarning.getVisibility() == View.VISIBLE) {
|
||||
return;
|
||||
}
|
||||
serviceWarning.setVisibility(View.VISIBLE);
|
||||
serviceWarning.animate()
|
||||
.alpha(1)
|
||||
.setListener(null)
|
||||
.start();
|
||||
|
||||
scrollView.postDelayed(() -> {
|
||||
if (serviceWarning.getVisibility() == View.VISIBLE) {
|
||||
scrollView.smoothScrollTo(0, serviceWarning.getBottom());
|
||||
}
|
||||
}, 1000);
|
||||
// TODO animate in bottom sheet
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCellSignalPresent() {
|
||||
if (serviceWarning.getVisibility() != View.VISIBLE) {
|
||||
return;
|
||||
}
|
||||
serviceWarning.animate()
|
||||
.alpha(0)
|
||||
.setListener(new Animator.AnimatorListener() {
|
||||
@Override public void onAnimationEnd(Animator animation) {
|
||||
serviceWarning.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override public void onAnimationStart(Animator animation) {}
|
||||
|
||||
@Override public void onAnimationCancel(Animator animation) {}
|
||||
|
||||
@Override public void onAnimationRepeat(Animator animation) {}
|
||||
})
|
||||
.start();
|
||||
// TODO animate away bottom sheet
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@@ -27,6 +28,7 @@ import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
@@ -35,7 +37,6 @@ import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.LabeledEditText;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.registration.VerifyAccountRepository.Mode;
|
||||
import org.thoughtcrime.securesms.registration.util.RegistrationNumberInputController;
|
||||
@@ -51,6 +52,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
|
||||
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -64,10 +66,9 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
|
||||
private static final String TAG = Log.tag(EnterPhoneNumberFragment.class);
|
||||
|
||||
private LabeledEditText countryCode;
|
||||
private LabeledEditText number;
|
||||
private TextInputLayout countryCode;
|
||||
private TextInputLayout number;
|
||||
private CircularProgressMaterialButton register;
|
||||
private Spinner countrySpinner;
|
||||
private View cancel;
|
||||
private ScrollView scrollView;
|
||||
private RegistrationViewModel viewModel;
|
||||
@@ -91,20 +92,16 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header));
|
||||
|
||||
countryCode = view.findViewById(R.id.country_code);
|
||||
number = view.findViewById(R.id.number);
|
||||
countrySpinner = view.findViewById(R.id.country_spinner);
|
||||
cancel = view.findViewById(R.id.cancel_button);
|
||||
scrollView = view.findViewById(R.id.scroll_view);
|
||||
register = view.findViewById(R.id.registerButton);
|
||||
countryCode = view.findViewById(R.id.country_code);
|
||||
number = view.findViewById(R.id.number);
|
||||
cancel = view.findViewById(R.id.cancel_button);
|
||||
scrollView = view.findViewById(R.id.scroll_view);
|
||||
register = view.findViewById(R.id.registerButton);
|
||||
|
||||
RegistrationNumberInputController controller = new RegistrationNumberInputController(requireContext(),
|
||||
countryCode,
|
||||
number,
|
||||
countrySpinner,
|
||||
true,
|
||||
this);
|
||||
|
||||
this,
|
||||
Objects.requireNonNull(number.getEditText()),
|
||||
countryCode);
|
||||
register.setOnClickListener(v -> handleRegister(requireContext()));
|
||||
|
||||
disposables.bindTo(getViewLifecycleOwner().getLifecycle());
|
||||
@@ -125,7 +122,10 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
|
||||
Toolbar toolbar = view.findViewById(R.id.toolbar);
|
||||
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
|
||||
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(null);
|
||||
final ActionBar supportActionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setTitle(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,13 +144,13 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
}
|
||||
|
||||
private void handleRegister(@NonNull Context context) {
|
||||
if (TextUtils.isEmpty(countryCode.getText())) {
|
||||
if (TextUtils.isEmpty(countryCode.getEditText().getText())) {
|
||||
showErrorDialog(context, getString(R.string.RegistrationActivity_you_must_specify_your_country_code));
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(this.number.getText())) {
|
||||
showErrorDialog(context, getString(R.string.RegistrationActivity_you_must_specify_your_phone_number));
|
||||
if (TextUtils.isEmpty(this.number.getEditText().getText())) {
|
||||
showErrorDialog(context, getString(R.string.RegistrationActivity_please_enter_a_valid_phone_number_to_register));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,8 +184,8 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
disableAllEntries();
|
||||
|
||||
if (fcmSupported) {
|
||||
SmsRetrieverClient client = SmsRetriever.getClient(context);
|
||||
Task<Void> task = client.startSmsRetriever();
|
||||
SmsRetrieverClient client = SmsRetriever.getClient(context);
|
||||
Task<Void> task = client.startSmsRetriever();
|
||||
AtomicBoolean handled = new AtomicBoolean(false);
|
||||
|
||||
Debouncer debouncer = new Debouncer(TimeUnit.SECONDS.toMillis(5));
|
||||
@@ -224,14 +224,12 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
private void disableAllEntries() {
|
||||
countryCode.setEnabled(false);
|
||||
number.setEnabled(false);
|
||||
countrySpinner.setEnabled(false);
|
||||
cancel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void enableAllEntries() {
|
||||
countryCode.setEnabled(true);
|
||||
number.setEnabled(true);
|
||||
countrySpinner.setEnabled(true);
|
||||
if (viewModel.isReregister()) {
|
||||
cancel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
@@ -283,22 +281,12 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
scrollView.postDelayed(() -> scrollView.smoothScrollTo(0, register.getBottom()), 250);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNumberInputNext(@NonNull View view) {
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNumberInputDone(@NonNull View view) {
|
||||
ViewUtil.hideKeyboard(requireContext(), view);
|
||||
handleRegister(requireContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickCountry(@NonNull View view) {
|
||||
SafeNavigation.safeNavigate(Navigation.findNavController(view), R.id.action_pickCountry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNationalNumber(@NonNull String number) {
|
||||
viewModel.setNationalNumber(number);
|
||||
@@ -325,8 +313,8 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
d.dismiss();
|
||||
})
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
countryCode.setText(String.valueOf(phoneNumber.getCountryCode()));
|
||||
number.setText(String.valueOf(phoneNumber.getNationalNumber()));
|
||||
countryCode.getEditText().setText(String.valueOf(phoneNumber.getCountryCode()));
|
||||
number.getEditText().setText(String.valueOf(phoneNumber.getNationalNumber()));
|
||||
requestVerificationCode(mode);
|
||||
d.dismiss();
|
||||
})
|
||||
@@ -357,9 +345,9 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
R.string.RegistrationActivity_a_verification_code_will_be_sent_to,
|
||||
e164number,
|
||||
() -> {
|
||||
ViewUtil.hideKeyboard(context, number.getInput());
|
||||
ViewUtil.hideKeyboard(context, number.getEditText());
|
||||
onConfirmed.run();
|
||||
},
|
||||
() -> number.focusAndMoveCursorToEndAndOpenKeyboard());
|
||||
() -> ViewUtil.focusAndMoveCursorToEndAndOpenKeyboard(this.number.getEditText()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import org.thoughtcrime.securesms.components.LabeledEditText;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState;
|
||||
|
||||
/**
|
||||
* Handle the logic and formatting of phone number input for registration/change number flows.
|
||||
* Handle the logic and formatting of phone number input specifically for change number flows.
|
||||
*/
|
||||
public final class RegistrationNumberInputController {
|
||||
public final class ChangeNumberInputController {
|
||||
|
||||
private final Context context;
|
||||
private final LabeledEditText countryCode;
|
||||
@@ -38,12 +38,12 @@ public final class RegistrationNumberInputController {
|
||||
private AsYouTypeFormatter countryFormatter;
|
||||
private boolean isUpdating = true;
|
||||
|
||||
public RegistrationNumberInputController(@NonNull Context context,
|
||||
@NonNull LabeledEditText countryCode,
|
||||
@NonNull LabeledEditText number,
|
||||
@NonNull Spinner countrySpinner,
|
||||
boolean lastInput,
|
||||
@NonNull Callbacks callbacks)
|
||||
public ChangeNumberInputController(@NonNull Context context,
|
||||
@NonNull LabeledEditText countryCode,
|
||||
@NonNull LabeledEditText number,
|
||||
@NonNull Spinner countrySpinner,
|
||||
boolean lastInput,
|
||||
@NonNull Callbacks callbacks)
|
||||
{
|
||||
this.context = context;
|
||||
this.countryCode = countryCode;
|
||||
@@ -0,0 +1,155 @@
|
||||
package org.thoughtcrime.securesms.registration.util
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.View.OnFocusChangeListener
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.i18n.phonenumbers.AsYouTypeFormatter
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState
|
||||
|
||||
/**
|
||||
* Handle the logic and formatting of phone number input specifically for registration number the flow.
|
||||
*/
|
||||
class RegistrationNumberInputController(
|
||||
val context: Context,
|
||||
val callbacks: Callbacks,
|
||||
private val phoneNumberInputLayout: EditText,
|
||||
countryCodeInputLayout: TextInputLayout
|
||||
) {
|
||||
private val spinnerView: MaterialAutoCompleteTextView = countryCodeInputLayout.editText as MaterialAutoCompleteTextView
|
||||
private val supportedCountryPrefixes: List<CountryPrefix> = PhoneNumberUtil.getInstance().supportedCallingCodes
|
||||
.map { CountryPrefix(it, PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(it)) }
|
||||
.sortedBy { it.digits }
|
||||
private val spinnerAdapter: ArrayAdapter<CountryPrefix> = ArrayAdapter<CountryPrefix>(context, R.layout.registration_country_code_dropdown_item, supportedCountryPrefixes)
|
||||
|
||||
private var countryFormatter: AsYouTypeFormatter? = null
|
||||
private var isUpdating = true
|
||||
|
||||
init {
|
||||
setUpNumberInput()
|
||||
|
||||
spinnerView.setAdapter(spinnerAdapter)
|
||||
spinnerView.addTextChangedListener(CountryCodeEntryListener())
|
||||
}
|
||||
|
||||
private fun advanceToPhoneNumberInput() {
|
||||
if (!isUpdating) {
|
||||
phoneNumberInputLayout.requestFocus()
|
||||
}
|
||||
val numberLength: Int = phoneNumberInputLayout.text?.length ?: 0
|
||||
phoneNumberInputLayout.setSelection(numberLength, numberLength)
|
||||
}
|
||||
|
||||
private fun setUpNumberInput() {
|
||||
phoneNumberInputLayout.addTextChangedListener(NumberChangedListener())
|
||||
phoneNumberInputLayout.onFocusChangeListener = OnFocusChangeListener { v: View?, hasFocus: Boolean ->
|
||||
if (hasFocus) {
|
||||
callbacks.onNumberFocused()
|
||||
}
|
||||
}
|
||||
phoneNumberInputLayout.imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
phoneNumberInputLayout.setOnEditorActionListener { v: TextView?, actionId: Int, _: KeyEvent? ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
callbacks.onNumberInputDone(v!!)
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun updateNumber(numberViewState: NumberViewState) {
|
||||
val countryCode = numberViewState.countryCode
|
||||
|
||||
isUpdating = true
|
||||
val regionCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(countryCode)
|
||||
setCountryFormatter(regionCode)
|
||||
|
||||
isUpdating = false
|
||||
}
|
||||
|
||||
private fun setCountryFormatter(regionCode: String?) {
|
||||
val util = PhoneNumberUtil.getInstance()
|
||||
countryFormatter = if (regionCode != null) util.getAsYouTypeFormatter(regionCode) else null
|
||||
reformatText(phoneNumberInputLayout.text)
|
||||
}
|
||||
|
||||
private fun reformatText(editable: Editable): String? {
|
||||
if (TextUtils.isEmpty(editable)) {
|
||||
return null
|
||||
}
|
||||
val countryFormatter: AsYouTypeFormatter = countryFormatter ?: return null
|
||||
countryFormatter.clear()
|
||||
var formattedNumber: String? = null
|
||||
val justDigits = StringBuilder()
|
||||
for (character in editable) {
|
||||
if (Character.isDigit(character)) {
|
||||
formattedNumber = countryFormatter.inputDigit(character)
|
||||
justDigits.append(character)
|
||||
}
|
||||
}
|
||||
if (formattedNumber != null && editable.toString() != formattedNumber) {
|
||||
editable.replace(0, editable.length, formattedNumber)
|
||||
}
|
||||
return if (justDigits.isEmpty()) {
|
||||
null
|
||||
} else justDigits.toString()
|
||||
}
|
||||
|
||||
inner class NumberChangedListener : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
val number: String = reformatText(s) ?: return
|
||||
if (!isUpdating) {
|
||||
callbacks.setNationalNumber(number)
|
||||
}
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
}
|
||||
|
||||
inner class CountryCodeEntryListener : TextWatcher {
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
if (s.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (s[0] != '+') {
|
||||
s.insert(0, "+")
|
||||
}
|
||||
|
||||
supportedCountryPrefixes.firstOrNull { it.toString() == s.toString() }?.let {
|
||||
setCountryFormatter(it.regionCode)
|
||||
callbacks.setCountry(it.digits)
|
||||
advanceToPhoneNumberInput()
|
||||
}
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
}
|
||||
|
||||
interface Callbacks {
|
||||
fun onNumberFocused()
|
||||
fun onNumberInputDone(view: View)
|
||||
fun setNationalNumber(number: String)
|
||||
fun setCountry(countryCode: Int)
|
||||
}
|
||||
}
|
||||
|
||||
data class CountryPrefix(val digits: Int, val regionCode: String) {
|
||||
override fun toString(): String {
|
||||
return "+$digits"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user