diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 4fab0772e2..4d67da2d76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -367,6 +367,7 @@ import org.thoughtcrime.securesms.util.toMillis import org.thoughtcrime.securesms.util.viewModel import org.thoughtcrime.securesms.util.views.Stub import org.thoughtcrime.securesms.util.visible +import org.thoughtcrime.securesms.verify.VerifyAutomaticallyEducationSheet import org.thoughtcrime.securesms.verify.VerifyIdentityActivity import org.thoughtcrime.securesms.wallpaper.ChatWallpaper import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil @@ -661,6 +662,7 @@ class ConversationFragment : presentGroupConversationSubtitle(createGroupSubtitleString(viewModel.titleViewParticipantsSnapshot)) presentActionBarMenu() presentStoryRing() + presentVerifyAutomaticallySheet() observeConversationThread() @@ -1414,6 +1416,12 @@ class ConversationFragment : } } + private fun presentVerifyAutomaticallySheet() { + if (RemoteConfig.keyTransparency && !SignalStore.uiHints.hasSeenVerifyAutomaticallySheet() && viewModel.recipientSnapshot?.isIndividual == true) { + VerifyAutomaticallyEducationSheet.show(parentFragmentManager) + } + } + private fun presentInputReadyState(inputReadyState: InputReadyState) { presentConversationTitle(inputReadyState.conversationRecipient) diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHintValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHintValues.java index 0579f8b3b0..e0724869b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHintValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHintValues.java @@ -31,6 +31,7 @@ public class UiHintValues extends SignalStoreValues { private static final String HAS_SEEN_LINK_DEVICE_QR_EDUCATION_SHEET = "uihints.has_seen_link_device_qr_education_sheet"; private static final String HAS_DISMISSED_SAVE_STORAGE_WARNING = "uihints.has_dismissed_save_storage_warning"; private static final String HAS_SEEN_PINNED_MESSAGE_SHEET = "uihints.has_seen_pinned_message_sheet"; + private static final String HAS_SEEN_VERIFY_AUTO_SHEET = "uihints.has_seen_verify_auto_sheet"; UiHintValues(@NonNull KeyValueStore store) { super(store); @@ -232,4 +233,12 @@ public class UiHintValues extends SignalStoreValues { private int getSeenPinnedSheetCount() { return getInteger(HAS_SEEN_PINNED_MESSAGE_SHEET, 0); } + + public boolean hasSeenVerifyAutomaticallySheet() { + return getBoolean(HAS_SEEN_VERIFY_AUTO_SHEET, false); + } + + public void setSeenVerifyAutomaticallySheet() { + putBoolean(HAS_SEEN_VERIFY_AUTO_SHEET, true); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt index 4177802f34..89a49387f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt @@ -1233,5 +1233,18 @@ object RemoteConfig { defaultValue = false, hotSwappable = true ) + + /** + * Whether or not to show any UI related to key transparency + */ + @JvmStatic + @get:JvmName("keyTransparency") + val keyTransparency: Boolean by remoteBoolean( + key = "android.keyTransparency", + active = false, + defaultValue = false, + hotSwappable = true + ) + // endregion } diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyAutomaticallyEducationSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyAutomaticallyEducationSheet.kt new file mode 100644 index 0000000000..5dca3bc025 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyAutomaticallyEducationSheet.kt @@ -0,0 +1,120 @@ +package org.thoughtcrime.securesms.verify + +import android.content.DialogInterface +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.fragment.app.FragmentManager +import org.signal.core.ui.compose.BottomSheets +import org.signal.core.ui.compose.Buttons +import org.signal.core.ui.compose.DayNightPreviews +import org.signal.core.ui.compose.Previews +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.BottomSheetUtil + +/** + * Education sheet explaining that conversations now have auto verification + */ +class VerifyAutomaticallyEducationSheet : ComposeBottomSheetDialogFragment() { + + override val peekHeightPercentage: Float = 0.75f + + companion object { + + @JvmStatic + fun show(fragmentManager: FragmentManager) { + VerifyAutomaticallyEducationSheet().show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) + } + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + SignalStore.uiHints.setSeenVerifyAutomaticallySheet() + } + + @Composable + override fun SheetContent() { + VerifyEducationSheet( + onVerify = {}, // TODO(michelle): Plug in to verify fragment + onLearnMore = {} // TODO(michelle): Update with support url + ) + } +} + +@Composable +fun VerifyEducationSheet( + onVerify: () -> Unit = {}, + onLearnMore: () -> Unit = {} +) { + return Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + BottomSheets.Handle() + Icon( + painter = painterResource(R.drawable.image_verify_successful), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.padding(top = 24.dp, bottom = 8.dp) + ) + + Text( + text = stringResource(R.string.VerifyAutomaticallyEducationSheet__title), + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(vertical = 12.dp, horizontal = 32.dp), + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = stringResource(R.string.VerifyAutomaticallyEducationSheet__body), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.padding(horizontal = 32.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp).padding(top = 60.dp, bottom = 28.dp) + ) { + TextButton( + onClick = onLearnMore + ) { + Text( + text = stringResource(id = R.string.VerifyAutomaticallyEducationSheet__learn_more) + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + Buttons.LargeTonal( + onClick = onVerify + ) { + Text(stringResource(id = R.string.VerifyAutomaticallyEducationSheet__verify)) + } + } + } +} + +@DayNightPreviews +@Composable +fun VerifyAutomaticallyEducationSheetPreview() { + Previews.BottomSheetContentPreview { + VerifyEducationSheet() + } +} diff --git a/app/src/main/res/drawable/image_verify_successful.xml b/app/src/main/res/drawable/image_verify_successful.xml new file mode 100644 index 0000000000..f2726b4289 --- /dev/null +++ b/app/src/main/res/drawable/image_verify_successful.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4476ba1e49..fd2ba0f871 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3816,6 +3816,15 @@ Mark as verified Clear verification + + Signal now auto-verifies end-to-end encryption + + When you verify a safety number, Signal will automatically confirm whether the connection is secure using a process called key transparency. You can still verify connections manually using a QR code or number. + + Learn more + + Verify + Scan the QR Code on your contact\'s device.