From 9867fa3f50f13ae69b81454dfc2b8e353de2e254 Mon Sep 17 00:00:00 2001 From: Jeffrey Starke Date: Mon, 5 May 2025 15:43:39 -0400 Subject: [PATCH] Add round checkbox composable. Adds `RoundCheckbox` composable, which is styled to match the appearance of the other view checkboxes used in the app. --- .../thoughtcrime/securesms/MainActivity.kt | 4 +- .../ConnectivityWarningBottomSheet.kt | 7 +- .../DeleteSyncEducationDialog.kt | 2 +- .../DeviceSpecificNotificationBottomSheet.kt | 7 +- .../components/compose/RoundCheckbox.kt | 111 ++++++++++++++++++ .../InternalBackupPlaygroundFragment.kt | 4 +- .../conversation/v2/ConversationFragment.kt | 2 +- .../ConversationListFragment.java | 2 +- .../MediaOverviewPageFragment.java | 2 +- .../mediapreview/MediaPreviewV2Fragment.kt | 2 +- .../stickers/StickerPackListItems.kt | 6 +- app/src/main/res/values/strings.xml | 7 ++ 12 files changed, 142 insertions(+), 14 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/components/{ => compose}/ConnectivityWarningBottomSheet.kt (96%) rename app/src/main/java/org/thoughtcrime/securesms/components/{ => compose}/DeleteSyncEducationDialog.kt (98%) rename app/src/main/java/org/thoughtcrime/securesms/components/{ => compose}/DeviceSpecificNotificationBottomSheet.kt (97%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/compose/RoundCheckbox.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt index e2eb054880..0df53ef53c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt @@ -70,10 +70,10 @@ import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar.show import org.thoughtcrime.securesms.calls.log.CallLogFilter import org.thoughtcrime.securesms.calls.log.CallLogFragment import org.thoughtcrime.securesms.calls.new.NewCallActivity -import org.thoughtcrime.securesms.components.ConnectivityWarningBottomSheet import org.thoughtcrime.securesms.components.DebugLogsPromptDialogFragment -import org.thoughtcrime.securesms.components.DeviceSpecificNotificationBottomSheet import org.thoughtcrime.securesms.components.PromptBatterySaverDialogFragment +import org.thoughtcrime.securesms.components.compose.ConnectivityWarningBottomSheet +import org.thoughtcrime.securesms.components.compose.DeviceSpecificNotificationBottomSheet import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity.Companion.manageSubscriptions import org.thoughtcrime.securesms.components.settings.app.notifications.manual.NotificationProfileSelectionFragment diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConnectivityWarningBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/compose/ConnectivityWarningBottomSheet.kt similarity index 96% rename from app/src/main/java/org/thoughtcrime/securesms/components/ConnectivityWarningBottomSheet.kt rename to app/src/main/java/org/thoughtcrime/securesms/components/compose/ConnectivityWarningBottomSheet.kt index 12ef0aa067..845bb9e6d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConnectivityWarningBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/compose/ConnectivityWarningBottomSheet.kt @@ -1,4 +1,9 @@ -package org.thoughtcrime.securesms.components +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.components.compose import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/DeleteSyncEducationDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/components/compose/DeleteSyncEducationDialog.kt similarity index 98% rename from app/src/main/java/org/thoughtcrime/securesms/components/DeleteSyncEducationDialog.kt rename to app/src/main/java/org/thoughtcrime/securesms/components/compose/DeleteSyncEducationDialog.kt index f2d994cafb..9410cb6dd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/DeleteSyncEducationDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/compose/DeleteSyncEducationDialog.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -package org.thoughtcrime.securesms.components +package org.thoughtcrime.securesms.components.compose import android.content.DialogInterface import android.os.Bundle diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/DeviceSpecificNotificationBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/compose/DeviceSpecificNotificationBottomSheet.kt similarity index 97% rename from app/src/main/java/org/thoughtcrime/securesms/components/DeviceSpecificNotificationBottomSheet.kt rename to app/src/main/java/org/thoughtcrime/securesms/components/compose/DeviceSpecificNotificationBottomSheet.kt index f96b80ad58..b431af6e26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/DeviceSpecificNotificationBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/compose/DeviceSpecificNotificationBottomSheet.kt @@ -1,4 +1,9 @@ -package org.thoughtcrime.securesms.components +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.components.compose import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/compose/RoundCheckbox.kt b/app/src/main/java/org/thoughtcrime/securesms/components/compose/RoundCheckbox.kt new file mode 100644 index 0000000000..f422ad1e9d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/compose/RoundCheckbox.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.components.compose + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Checkbox +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import org.signal.core.ui.compose.SignalPreview +import org.signal.core.ui.compose.theme.SignalTheme +import org.thoughtcrime.securesms.R + +/** + * A custom circular [Checkbox] that can be toggled between checked an unchecked states. + * + * @param checked Indicates whether the checkbox is checked or not. + * @param onCheckedChange A callback function invoked when this checkbox is clicked. + * @param modifier The [Modifier] to be applied to this checkbox. + */ +@Composable +fun RoundCheckbox( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier +) { + val contentDescription = if (checked) { + stringResource(R.string.SignalCheckbox_accessibility_checked_description) + } else { + stringResource(R.string.SignalCheckbox_accessibility_unchecked_description) + } + + Box( + modifier = modifier + .padding(12.dp) + .size(24.dp) + .aspectRatio(1f) + .border( + width = 1.5.dp, + color = if (checked) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.outline + }, + shape = CircleShape + ) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { onCheckedChange(!checked) }, + onClickLabel = stringResource(R.string.SignalCheckbox_accessibility_on_click_label) + ) + .semantics(mergeDescendants = true) { + this.role = Role.Checkbox + this.contentDescription = contentDescription + } + ) { + AnimatedVisibility( + visible = checked, + enter = fadeIn(animationSpec = tween(durationMillis = 150)) + scaleIn(initialScale = 1.20f, animationSpec = tween(durationMillis = 500)), + exit = fadeOut(animationSpec = tween(durationMillis = 300)) + scaleOut(targetScale = 0.50f, animationSpec = tween(durationMillis = 600)) + ) { + Image( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_check_circle_solid_24), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary), + contentDescription = null, + modifier = Modifier.fillMaxSize() + ) + } + } +} + +@SignalPreview +@Composable +private fun RoundCheckboxCheckedPreview() = SignalTheme { + RoundCheckbox(checked = true, onCheckedChange = {}) +} + +@SignalPreview +@Composable +private fun RoundCheckboxUncheckedPreview() = SignalTheme { + RoundCheckbox(checked = false, onCheckedChange = {}) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt index 69d47b4f93..39ae5a0249 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt @@ -28,7 +28,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button -import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -83,6 +82,7 @@ import org.signal.core.util.getLength import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.backup.v2.MessageBackupTier +import org.thoughtcrime.securesms.components.compose.RoundCheckbox import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.DialogState import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.ScreenState import org.thoughtcrime.securesms.compose.ComposeFragment @@ -629,7 +629,7 @@ fun MediaList( .padding(horizontal = 16.dp, vertical = 8.dp) ) { if (selectionState.selecting) { - Checkbox( + RoundCheckbox( checked = selectionState.selected.contains(attachment.id), onCheckedChange = { selected -> selectionState = selectionState.copy(selected = if (selected) selectionState.selected + attachment.id else selectionState.selected - attachment.id) 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 21feed90ec..c3cf736f83 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 @@ -122,7 +122,6 @@ import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar import org.thoughtcrime.securesms.components.AnimatingToggle import org.thoughtcrime.securesms.components.ComposeText import org.thoughtcrime.securesms.components.ConversationSearchBottomBar -import org.thoughtcrime.securesms.components.DeleteSyncEducationDialog import org.thoughtcrime.securesms.components.HidingLinearLayout import org.thoughtcrime.securesms.components.InputAwareConstraintLayout import org.thoughtcrime.securesms.components.InputPanel @@ -130,6 +129,7 @@ import org.thoughtcrime.securesms.components.InsetAwareConstraintLayout import org.thoughtcrime.securesms.components.ScrollToPositionDelegate import org.thoughtcrime.securesms.components.SendButton import org.thoughtcrime.securesms.components.ViewBinderDelegate +import org.thoughtcrime.securesms.components.compose.DeleteSyncEducationDialog import org.thoughtcrime.securesms.components.emoji.EmojiEventListener import org.thoughtcrime.securesms.components.emoji.MediaKeyboard import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 3510e5906e..a30f9d0d45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -98,7 +98,7 @@ import org.thoughtcrime.securesms.banner.banners.OutdatedBuildBanner; import org.thoughtcrime.securesms.banner.banners.ServiceOutageBanner; import org.thoughtcrime.securesms.banner.banners.UnauthorizedBanner; import org.thoughtcrime.securesms.banner.banners.UsernameOutOfSyncBanner; -import org.thoughtcrime.securesms.components.DeleteSyncEducationDialog; +import org.thoughtcrime.securesms.components.compose.DeleteSyncEducationDialog; import org.thoughtcrime.securesms.components.RatingManager; import org.thoughtcrime.securesms.components.SignalProgressDialog; import org.thoughtcrime.securesms.components.menu.ActionItem; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java index 45e68902cf..a41fac5715 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java @@ -36,7 +36,7 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.components.DeleteSyncEducationDialog; +import org.thoughtcrime.securesms.components.compose.DeleteSyncEducationDialog; import org.thoughtcrime.securesms.components.menu.ActionItem; import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index 0b85bf2212..ac659f80dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -47,8 +47,8 @@ import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.attachments.AttachmentSaver import org.thoughtcrime.securesms.attachments.DatabaseAttachment -import org.thoughtcrime.securesms.components.DeleteSyncEducationDialog import org.thoughtcrime.securesms.components.ViewBinderDelegate +import org.thoughtcrime.securesms.components.compose.DeleteSyncEducationDialog import org.thoughtcrime.securesms.components.mention.MentionAnnotation import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackListItems.kt b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackListItems.kt index e9ed7c81ed..ecffeb1874 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackListItems.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackListItems.kt @@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -37,6 +36,7 @@ import org.signal.core.ui.compose.SignalPreview import org.signal.core.ui.compose.theme.SignalTheme import org.signal.core.util.nullIfBlank import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.compose.RoundCheckbox import org.thoughtcrime.securesms.components.transfercontrols.TransferProgressIndicator import org.thoughtcrime.securesms.components.transfercontrols.TransferProgressState import org.thoughtcrime.securesms.compose.GlideImage @@ -158,14 +158,14 @@ fun InstalledStickerPackRow( color = if (selected) SignalTheme.colors.colorSurface2 else MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(18.dp) ) - .padding(vertical = 10.dp) + .padding(horizontal = 4.dp, vertical = 10.dp) ) { AnimatedVisibility( visible = multiSelectEnabled, enter = fadeIn() + expandHorizontally(), exit = fadeOut() + shrinkHorizontally() ) { - Checkbox( + RoundCheckbox( checked = selected, onCheckedChange = { onSelectionToggle(pack) }, modifier = Modifier.padding(end = 8.dp) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b219b64a8c..1bb2b1c0d5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8498,5 +8498,12 @@ Navigate up + + Toggle + + Ticked + + Unticked +