From f3922c41566d2c666b2e17f485cc40d7d636848c Mon Sep 17 00:00:00 2001 From: Nicholas Date: Thu, 23 Feb 2023 11:37:51 -0500 Subject: [PATCH] Fix bottom sheet behavior and design. --- .../fragments/BaseEnterSmsCodeFragment.java | 29 ++-- .../ContactSupportBottomSheetFragment.kt | 140 +++++++++++++----- .../viewmodel/BaseRegistrationViewModel.java | 36 ++--- .../fragment_registration_enter_code.xml | 14 ++ app/src/main/res/values/strings.xml | 13 +- 5 files changed, 156 insertions(+), 76 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseEnterSmsCodeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseEnterSmsCodeFragment.java index fcd67f865..feaf1efc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseEnterSmsCodeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseEnterSmsCodeFragment.java @@ -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 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.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() { @Override @@ -416,19 +420,10 @@ public abstract class BaseEnterSmsCodeFragment + 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" diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java index 7644617bb..31059259b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java @@ -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 getSuccessfulCodeRequestAttempts() { - return savedState.getLiveData(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, 0); + public LiveData 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()); diff --git a/app/src/main/res/layout/fragment_registration_enter_code.xml b/app/src/main/res/layout/fragment_registration_enter_code.xml index 972cbe6b7..2e0bf4e38 100644 --- a/app/src/main/res/layout/fragment_registration_enter_code.xml +++ b/app/src/main/res/layout/fragment_registration_enter_code.xml @@ -101,6 +101,20 @@ app:layout_constraintVertical_bias="1.0" tools:text="@string/RegistrationActivity_resend_code" /> + + + Call Verification Code Resend Code + Having trouble registering? - • 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 - these troubleshooting steps - or - Contact Support + + • 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. + + For more information, please follow these troubleshooting steps or Contact Support + + these troubleshooting steps + + Contact Support Turn on Registration Lock?