Skip to content

Commit

Permalink
Fix bottom sheet behavior and design.
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholas-signal authored and skustra committed Feb 24, 2023
1 parent af5c9b5 commit 7ce12bc
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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;
Expand All @@ -31,9 +32,7 @@
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;
Expand Down Expand Up @@ -66,7 +65,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 EditText pigeonCodeView;
Expand Down Expand Up @@ -94,6 +94,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
resendSmsCountDown = view.findViewById(R.id.resend_sms_count_down);
wrongNumber = view.findViewById(R.id.wrong_number);
pigeonCodeView = view.findViewById(R.id.verification_code);
bottomSheetButton = view.findViewById(R.id.having_trouble_button);

new SignalStrengthPhoneStateListener(this, this);

Expand All @@ -107,6 +108,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
}

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);
Expand All @@ -131,9 +133,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat

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);
}
});

Expand Down Expand Up @@ -287,6 +289,8 @@ protected void handleKbsAccountLocked() {
}

protected void handleIncorrectCodeError() {
viewModel.incrementIncorrectCodeAttempts();

Toast.makeText(requireContext(), R.string.RegistrationActivity_incorrect_code, Toast.LENGTH_LONG).show();
keyboard.displayFailure().addListener(new AssertedSuccessListener<Boolean>() {
@Override
Expand Down Expand Up @@ -474,19 +478,10 @@ public void onResume() {
});
}

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
if (!doesStringEndWithContactSupport) {
append(completeString.substring(contactSupportEndIndex, completeString.lastIndex))
}
}
}

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)
)
}
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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@
*/
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;
Expand All @@ -65,7 +65,7 @@ public BaseRegistrationViewModel(@NonNull SavedStateHandle savedStateHandle,
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());
}
Expand Down Expand Up @@ -160,13 +160,13 @@ public void onVerificationCodeEntered(String code) {
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() {
Expand Down Expand Up @@ -244,10 +244,6 @@ public Single<RegistrationSessionProcessor> requestVerificationCode(@NonNull Mod
})
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess((RegistrationSessionProcessor processor) -> {
if (processor.hasResult()) {
markASuccessfulAttempt();
}

if (processor.hasResult() && processor.isAllowedToRequestCode()) {
setCanSmsAtTime(processor.getNextCodeViaSmsAttempt());
setCanCallAtTime(processor.getNextCodeViaCallAttempt());
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/res/layout/fragment_registration_enter_code.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
13 changes: 9 additions & 4 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit 7ce12bc

Please sign in to comment.