From a796316ad622de71351f2e597a2ddcec9a46c67d Mon Sep 17 00:00:00 2001 From: jeffrey-signal Date: Tue, 12 May 2026 10:25:27 -0400 Subject: [PATCH] Add click handling for regV5 welcome screen terms button. --- .../securesms/util/CommunicationActions.java | 18 +++++----- .../java/org/signal/core/util/LinkActions.kt | 35 +++++++++++++++++++ .../registration/RegistrationNavigation.kt | 19 ++++++++++ .../screens/welcome/WelcomeScreen.kt | 2 +- .../screens/welcome/WelcomeScreenEvents.kt | 1 + .../src/main/res/values/strings.xml | 4 +++ 6 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 core/util/src/main/java/org/signal/core/util/LinkActions.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index f572e763cd..39e4b23f90 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -23,6 +23,8 @@ import androidx.fragment.app.FragmentManager; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import org.signal.core.util.LinkActions; +import org.signal.core.util.LinkActions.OpenUrlError; import org.signal.core.util.Util; import org.signal.core.util.concurrent.JvmRxExtensions; import org.signal.core.util.concurrent.SignalExecutors; @@ -55,6 +57,7 @@ import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import org.whispersystems.signalservice.api.push.UsernameLinkComponents; import io.reactivex.rxjava3.core.Single; +import kotlin.Unit; import java.io.IOException; import java.util.Objects; @@ -230,15 +233,14 @@ public class CommunicationActions { } public static void openBrowserLink(@NonNull Context context, @NonNull String link) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link)); - if (!(context instanceof Activity)) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + LinkActions.openUrl(context, link, error -> { + if (error instanceof OpenUrlError.NoBrowserFound) { + Toast.makeText(context, R.string.CommunicationActions_no_browser_found, Toast.LENGTH_SHORT).show(); + } else { + throw new AssertionError("Unhandled OpenUrlError: " + error); } - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Toast.makeText(context, R.string.CommunicationActions_no_browser_found, Toast.LENGTH_SHORT).show(); - } + return Unit.INSTANCE; + }); } public static void openEmail(@NonNull Context context, @NonNull String address, @Nullable String subject, @Nullable String body) { diff --git a/core/util/src/main/java/org/signal/core/util/LinkActions.kt b/core/util/src/main/java/org/signal/core/util/LinkActions.kt new file mode 100644 index 0000000000..6c60ebf8df --- /dev/null +++ b/core/util/src/main/java/org/signal/core/util/LinkActions.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.core.util + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import androidx.core.net.toUri +import org.signal.core.util.logging.Log + +object LinkActions { + private val TAG = Log.tag(LinkActions::class) + + @JvmStatic + fun openUrl(context: Context, url: String, onError: (OpenUrlError) -> Unit) { + try { + val intent = Intent(Intent.ACTION_VIEW, url.toUri()) + if (context !is Activity) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + } catch (_: ActivityNotFoundException) { + Log.w(TAG, "Unable to open URL: no browser activity found") + onError(OpenUrlError.NoBrowserFound) + } + } + + sealed interface OpenUrlError { + data object NoBrowserFound : OpenUrlError + } +} diff --git a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt index 8c9341f251..64c2d1adba 100644 --- a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt +++ b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt @@ -8,6 +8,7 @@ package org.signal.registration import android.os.Parcelable +import android.widget.Toast import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -17,6 +18,8 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator @@ -35,6 +38,8 @@ import kotlinx.serialization.Serializable import org.signal.core.models.AccountEntropyPool import org.signal.core.ui.navigation.ResultEffect import org.signal.core.ui.navigation.TransitionSpecs +import org.signal.core.util.LinkActions +import org.signal.core.util.LinkActions.OpenUrlError import org.signal.core.util.serialization.AccountEntropyPoolSerializer import org.signal.registration.screens.accountlocked.AccountLockedScreen import org.signal.registration.screens.accountlocked.AccountLockedScreenEvents @@ -275,12 +280,15 @@ fun RegistrationNavHost( initialState.key is RegistrationRoute.CountryCodePicker -> { TransitionSpecs.VerticalSlide.popTransitionSpec.invoke(this) } + initialState.key == RegistrationRoute.EnterAepForLocalBackup.toString() || initialState.key == RegistrationRoute.EnterAepForRemoteBackupPreRegistration.toString() -> { TransitionSpecs.HorizontalSlide.transitionSpec.invoke(this) } + initialState.key == RegistrationRoute.LocalBackupRestore.toString() && targetState.key == RegistrationRoute.PhoneNumberEntry.toString() -> { TransitionSpecs.HorizontalSlide.transitionSpec.invoke(this) } + else -> { TransitionSpecs.HorizontalSlide.popTransitionSpec.invoke(this) } @@ -306,6 +314,8 @@ private fun EntryProviderScope.navigationEntries( // --- Welcome Screen entry { + val context = LocalContext.current + val termsAndPrivacyUrl = stringResource(R.string.terms_and_privacy_policy_url) WelcomeScreen( onEvent = { event -> when (event) { @@ -313,6 +323,13 @@ private fun EntryProviderScope.navigationEntries( WelcomeScreenEvents.LinkDevice -> throw NotImplementedError("Haven't implemented linked devices yet") WelcomeScreenEvents.HasOldPhone -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.QuickRestoreQrScan)) WelcomeScreenEvents.DoesNotHaveOldPhone -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.ArchiveRestoreSelection.forManualRestore())) + WelcomeScreenEvents.ViewTermsAndPrivacy -> { + LinkActions.openUrl(context, termsAndPrivacyUrl) { error -> + when (error) { + OpenUrlError.NoBrowserFound -> Toast.makeText(context, R.string.LinkActions_error_no_browser_found, Toast.LENGTH_SHORT).show() + } + } + } } } ) @@ -395,6 +412,7 @@ private fun EntryProviderScope.navigationEntries( registrationViewModel.resultBus.sendResult(CAPTCHA_RESULT, event.token) parentEventEmitter.navigateBack() } + CaptchaScreenEvents.Cancel -> { parentEventEmitter.navigateBack() } @@ -502,6 +520,7 @@ private fun EntryProviderScope.navigationEntries( // TODO: Navigate to appropriate next screen (likely back to welcome or phone entry) parentEventEmitter.navigateTo(RegistrationRoute.Welcome) } + AccountLockedScreenEvents.LearnMore -> { // TODO: Open learn more URL } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreen.kt index 0f1b7c8dee..18e4a280b1 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreen.kt @@ -79,7 +79,7 @@ fun WelcomeScreen( var showBottomSheet by remember { mutableStateOf(false) } val windowBreakpoint = rememberWindowBreakpoint() val onRestoreOrTransferClick = { showBottomSheet = true } - val onTermsAndPrivacyClick: () -> Unit = {} + val onTermsAndPrivacyClick = { onEvent(WelcomeScreenEvents.ViewTermsAndPrivacy) } when (windowBreakpoint) { WindowBreakpoint.SMALL -> { diff --git a/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreenEvents.kt b/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreenEvents.kt index 8a70c1eb6d..6f8108b18d 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreenEvents.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreenEvents.kt @@ -12,4 +12,5 @@ sealed class WelcomeScreenEvents : DebugLoggableModel() { data object LinkDevice : WelcomeScreenEvents() data object HasOldPhone : WelcomeScreenEvents() data object DoesNotHaveOldPhone : WelcomeScreenEvents() + data object ViewTermsAndPrivacy : WelcomeScreenEvents() } diff --git a/feature/registration/src/main/res/values/strings.xml b/feature/registration/src/main/res/values/strings.xml index 4e081c1f40..c7a790eeb2 100644 --- a/feature/registration/src/main/res/values/strings.xml +++ b/feature/registration/src/main/res/values/strings.xml @@ -1,5 +1,7 @@ + https://signal.org/legal + Take privacy with you.\nBe yourself in every message. Signal is a 501c3 nonprofit\nTerms & Privacy Policy @@ -273,4 +275,6 @@ Restoring… %1$s of %2$s (%3$s) + + No web browser found.