Fix bottom sheet behavior and design.

main
Nicholas 2023-02-23 11:37:51 -05:00 zatwierdzone przez Cody Henthorne
rodzic 2ffc576387
commit f3922c4156
5 zmienionych plików z 156 dodań i 76 usunięć

Wyświetl plik

@ -14,6 +14,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.navigation.Navigation;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.greenrobot.eventbus.EventBus;
@ -28,9 +29,7 @@ import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboar
import org.thoughtcrime.securesms.registration.ReceivedSmsEvent;
import org.thoughtcrime.securesms.registration.VerifyAccountRepository;
import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.SupportEmailUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.thoughtcrime.securesms.util.dualsim.MccMncProducer;
@ -62,7 +61,8 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
private VerificationPinKeyboard keyboard;
private ActionCountDownButton callMeCountDown;
private ActionCountDownButton resendSmsCountDown;
private View wrongNumber;
private MaterialButton wrongNumber;
private MaterialButton bottomSheetButton;
private boolean autoCompleting;
private ViewModel viewModel;
@ -87,6 +87,7 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
callMeCountDown = view.findViewById(R.id.call_me_count_down);
resendSmsCountDown = view.findViewById(R.id.resend_sms_count_down);
wrongNumber = view.findViewById(R.id.wrong_number);
bottomSheetButton = view.findViewById(R.id.having_trouble_button);
new SignalStrengthPhoneStateListener(this, this);
@ -96,6 +97,7 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
setOnCodeFullyEnteredListener(verificationCodeView);
wrongNumber.setOnClickListener(v -> returnToPhoneEntryScreen());
bottomSheetButton.setOnClickListener( v -> showBottomSheet());
callMeCountDown.setTextResources(R.string.RegistrationActivity_call, R.string.RegistrationActivity_call_me_instead_available_in);
resendSmsCountDown.setTextResources(R.string.RegistrationActivity_resend_code, R.string.RegistrationActivity_resend_sms_available_in);
@ -120,9 +122,9 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
disposables.bindTo(getViewLifecycleOwner().getLifecycle());
viewModel = getViewModel();
viewModel.getSuccessfulCodeRequestAttempts().observe(getViewLifecycleOwner(), (attempts) -> {
viewModel.getIncorrectCodeAttempts().observe(getViewLifecycleOwner(), (attempts) -> {
if (attempts >= 3) {
new ContactSupportBottomSheetFragment(this::openTroubleshootingSteps, this::sendEmailToSupport).show(getChildFragmentManager(), "support_bottom_sheet");
bottomSheetButton.setVisibility(View.VISIBLE);
}
});
@ -229,6 +231,8 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
}
protected void handleIncorrectCodeError() {
viewModel.incrementIncorrectCodeAttempts();
Toast.makeText(requireContext(), R.string.RegistrationActivity_incorrect_code, Toast.LENGTH_LONG).show();
keyboard.displayFailure().addListener(new AssertedSuccessListener<Boolean>() {
@Override
@ -416,19 +420,10 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
});
}
private void openTroubleshootingSteps() {
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.support_center_url));
}
private void sendEmailToSupport() {
String body = SupportEmailUtil.generateSupportEmailBody(requireContext(),
R.string.RegistrationActivity_code_support_subject,
null,
null);
CommunicationActions.openEmail(requireContext(),
SupportEmailUtil.getSupportEmailAddress(requireContext()),
getString(R.string.RegistrationActivity_code_support_subject),
body);
private void showBottomSheet() {
ContactSupportBottomSheetFragment bottomSheet = new ContactSupportBottomSheetFragment();
bottomSheet.show(getChildFragmentManager(), "support_bottom_sheet");
}
@Override

Wyświetl plik

@ -6,31 +6,98 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.SupportEmailUtil
/**
* Helpful bottom sheet dialog displayed during registration when the user enters the wrong verification code too many times.
*/
class ContactSupportBottomSheetFragment(private val troubleshootingStepsListener: Runnable, private val contactSupportListener: Runnable) : ComposeBottomSheetDialogFragment() {
class ContactSupportBottomSheetFragment : ComposeBottomSheetDialogFragment() {
@Preview
@Composable
override fun SheetContent() {
val annotatedText = buildAnnotatedString {
withStyle(SpanStyle(fontSize = 28.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface)) {
append(stringResource(R.string.RegistrationActivity_support_bottom_sheet_title))
val annotatedText = buildClickableString()
return Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.wrapContentSize(Alignment.Center)
.padding(16.dp)
) {
Handle()
Text(
text = buildAnnotatedString {
withStyle(SpanStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface)) {
append(stringResource(R.string.RegistrationActivity_support_bottom_sheet_title))
}
},
modifier = Modifier.padding(8.dp)
)
Text(
text = stringResource(R.string.RegistrationActivity_support_bottom_sheet_body_suggestions),
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(8.dp)
)
ClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(
tag = "URL",
start = offset,
end = offset
)
.firstOrNull()?.let { annotation ->
when (annotation.item) {
TROUBLESHOOTING_STEPS_KEY -> openTroubleshootingSteps()
CONTACT_SUPPORT_KEY -> sendEmailToSupport()
}
}
},
modifier = Modifier.padding(8.dp)
)
}
}
@Composable
private fun buildClickableString(): AnnotatedString {
val troubleshootingStepsString = stringResource(R.string.RegistrationActivity_support_bottom_sheet_cta_troubleshooting_steps_substring)
val contactSupportString = stringResource(R.string.RegistrationActivity_support_bottom_sheet_cta_contact_support_substring)
val completeString = stringResource(R.string.RegistrationActivity_support_bottom_sheet_body_call_to_action)
val troubleshootingStartIndex = completeString.indexOf(troubleshootingStepsString)
val troubleshootingEndIndex = troubleshootingStartIndex + troubleshootingStepsString.length
val contactSupportStartIndex = completeString.indexOf(contactSupportString)
val contactSupportEndIndex = contactSupportStartIndex + contactSupportString.length
val doesStringEndWithContactSupport = contactSupportEndIndex >= completeString.lastIndex
return buildAnnotatedString {
withStyle(
style = SpanStyle(
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Normal
)
) {
append(completeString.substring(0, troubleshootingStartIndex))
}
append(stringResource(R.string.RegistrationActivity_support_bottom_sheet_body_part_1))
pushStringAnnotation(
tag = "URL",
annotation = TROUBLESHOOTING_STEPS_KEY
@ -41,10 +108,17 @@ class ContactSupportBottomSheetFragment(private val troubleshootingStepsListener
fontWeight = FontWeight.Bold
)
) {
append(stringResource(R.string.RegistrationActivity_support_bottom_sheet_body_part_2))
append(troubleshootingStepsString)
}
pop()
append(stringResource(R.string.RegistrationActivity_support_bottom_sheet_body_part_3))
withStyle(
style = SpanStyle(
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Normal
)
) {
append(completeString.substring(troubleshootingEndIndex, contactSupportStartIndex))
}
pushStringAnnotation(
tag = "URL",
annotation = CONTACT_SUPPORT_KEY
@ -55,38 +129,34 @@ class ContactSupportBottomSheetFragment(private val troubleshootingStepsListener
fontWeight = FontWeight.Bold
)
) {
append(stringResource(R.string.RegistrationActivity_support_bottom_sheet_body_part_4))
append(contactSupportString)
}
pop()
}
return Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentSize(Alignment.Center)
) {
Handle()
ClickableText(
text = annotatedText,
onClick = { offset ->
// We check if there is an *URL* annotation attached to the text
// at the clicked position
annotatedText.getStringAnnotations(
tag = "URL",
start = offset,
end = offset
)
.firstOrNull()?.let { annotation ->
when (annotation.item) {
TROUBLESHOOTING_STEPS_KEY -> troubleshootingStepsListener.run()
CONTACT_SUPPORT_KEY -> contactSupportListener.run()
}
}
},
modifier = Modifier.padding(16.dp)
)
if (!doesStringEndWithContactSupport) {
append(completeString.substring(contactSupportEndIndex, completeString.lastIndex))
}
}
}
private fun openTroubleshootingSteps() {
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.support_center_url))
}
private fun sendEmailToSupport() {
val body = SupportEmailUtil.generateSupportEmailBody(
requireContext(),
R.string.RegistrationActivity_code_support_subject,
null,
null
)
CommunicationActions.openEmail(
requireContext(),
SupportEmailUtil.getSupportEmailAddress(requireContext()),
getString(R.string.RegistrationActivity_code_support_subject),
body
)
}
companion object {
private const val TROUBLESHOOTING_STEPS_KEY = "troubleshooting"
private const val CONTACT_SUPPORT_KEY = "contact_support"

Wyświetl plik

@ -36,17 +36,17 @@ import io.reactivex.rxjava3.core.Single;
*/
public abstract class BaseRegistrationViewModel extends ViewModel {
private static final String STATE_NUMBER = "NUMBER";
private static final String STATE_REGISTRATION_SECRET = "REGISTRATION_SECRET";
private static final String STATE_VERIFICATION_CODE = "TEXT_CODE_ENTERED";
private static final String STATE_CAPTCHA = "CAPTCHA";
private static final String STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS = "SUCCESSFUL_CODE_REQUEST_ATTEMPTS";
private static final String STATE_REQUEST_RATE_LIMITER = "REQUEST_RATE_LIMITER";
private static final String STATE_KBS_TOKEN = "KBS_TOKEN";
private static final String STATE_TIME_REMAINING = "TIME_REMAINING";
private static final String STATE_CAN_CALL_AT_TIME = "CAN_CALL_AT_TIME";
private static final String STATE_CAN_SMS_AT_TIME = "CAN_SMS_AT_TIME";
private static final String STATE_RECOVERY_PASSWORD = "RECOVERY_PASSWORD";
private static final String STATE_NUMBER = "NUMBER";
private static final String STATE_REGISTRATION_SECRET = "REGISTRATION_SECRET";
private static final String STATE_VERIFICATION_CODE = "TEXT_CODE_ENTERED";
private static final String STATE_CAPTCHA = "CAPTCHA";
private static final String STATE_INCORRECT_CODE_ATTEMPTS = "STATE_INCORRECT_CODE_ATTEMPTS";
private static final String STATE_REQUEST_RATE_LIMITER = "REQUEST_RATE_LIMITER";
private static final String STATE_KBS_TOKEN = "KBS_TOKEN";
private static final String STATE_TIME_REMAINING = "TIME_REMAINING";
private static final String STATE_CAN_CALL_AT_TIME = "CAN_CALL_AT_TIME";
private static final String STATE_CAN_SMS_AT_TIME = "CAN_SMS_AT_TIME";
private static final String STATE_RECOVERY_PASSWORD = "RECOVERY_PASSWORD";
protected final SavedStateHandle savedState;
protected final VerifyAccountRepository verifyAccountRepository;
@ -65,7 +65,7 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
setInitialDefaultValue(STATE_NUMBER, NumberViewState.INITIAL);
setInitialDefaultValue(STATE_REGISTRATION_SECRET, password);
setInitialDefaultValue(STATE_VERIFICATION_CODE, "");
setInitialDefaultValue(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, 0);
setInitialDefaultValue(STATE_INCORRECT_CODE_ATTEMPTS, 0);
setInitialDefaultValue(STATE_REQUEST_RATE_LIMITER, new LocalCodeRequestRateLimiter(60_000));
setInitialDefaultValue(STATE_RECOVERY_PASSWORD, SignalStore.kbsValues().getRecoveryPassword());
}
@ -160,13 +160,13 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
savedState.set(STATE_VERIFICATION_CODE, code);
}
public void markASuccessfulAttempt() {
public void incrementIncorrectCodeAttempts() {
//noinspection ConstantConditions
savedState.set(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, (Integer) savedState.get(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS) + 1);
savedState.set(STATE_INCORRECT_CODE_ATTEMPTS, (Integer) savedState.get(STATE_INCORRECT_CODE_ATTEMPTS) + 1);
}
public LiveData<Integer> getSuccessfulCodeRequestAttempts() {
return savedState.getLiveData(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, 0);
public LiveData<Integer> getIncorrectCodeAttempts() {
return savedState.getLiveData(STATE_INCORRECT_CODE_ATTEMPTS, 0);
}
public @Nullable TokenData getKeyBackupCurrentToken() {
@ -244,10 +244,6 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
})
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess((RegistrationSessionProcessor processor) -> {
if (processor.hasResult()) {
markASuccessfulAttempt();
}
if (processor.hasResult() && processor.isAllowedToRequestCode()) {
setCanSmsAtTime(processor.getNextCodeViaSmsAttempt());
setCanCallAtTime(processor.getNextCodeViaCallAttempt());

Wyświetl plik

@ -101,6 +101,20 @@
app:layout_constraintVertical_bias="1.0"
tools:text="@string/RegistrationActivity_resend_code" />
<com.google.android.material.button.MaterialButton
android:id="@+id/having_trouble_button"
style="@style/Signal.Widget.Button.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/RegistrationActivity_support_bottom_sheet_title"
android:textColor="@color/signal_colorPrimary"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/code" />
<org.thoughtcrime.securesms.components.registration.VerificationCodeView
android:id="@+id/code"
android:layout_width="0dp"

Wyświetl plik

@ -1824,11 +1824,16 @@
<string name="RegistrationActivity_call">Call</string>
<string name="RegistrationActivity_verification_code">Verification Code</string>
<string name="RegistrationActivity_resend_code">Resend Code</string>
<!-- A title for a bottom sheet dialog offering to help a user having trouble entering their verification code.-->
<string name="RegistrationActivity_support_bottom_sheet_title">Having trouble registering?</string>
<string name="RegistrationActivity_support_bottom_sheet_body_part_1">• Make sure your phone has a cellular signal to receive your SMS or call\n • Confirm you can receive a phone call to the number\n • Check that you have entered your phone number correctly.\nFor more information, please follow </string>
<string name="RegistrationActivity_support_bottom_sheet_body_part_2">these troubleshooting steps</string>
<string name="RegistrationActivity_support_bottom_sheet_body_part_3"> or </string>
<string name="RegistrationActivity_support_bottom_sheet_body_part_4">Contact Support</string>
<!-- A list of suggestions to try for a user having trouble entering their verification code.-->
<string name="RegistrationActivity_support_bottom_sheet_body_suggestions">• Make sure your phone has a cellular signal to receive your SMS or call\n • Confirm you can receive a phone call to the number\n • Check that you have entered your phone number correctly.</string>
<!-- A call to action for a user having trouble entering the verification to seek further help. -->
<string name="RegistrationActivity_support_bottom_sheet_body_call_to_action">For more information, please follow these troubleshooting steps or Contact Support</string>
<!-- A clickable piece of text that will take the user to our website with additional suggestions.-->
<string name="RegistrationActivity_support_bottom_sheet_cta_troubleshooting_steps_substring">these troubleshooting steps</string>
<!-- A clickable piece of text that will pre-fill a request for support email in the user's email app.-->
<string name="RegistrationActivity_support_bottom_sheet_cta_contact_support_substring">Contact Support</string>
<!-- RegistrationLockV2Dialog -->
<string name="RegistrationLockV2Dialog_turn_on_registration_lock">Turn on Registration Lock?</string>