Allow 1:1 polls and raise character limit.

This commit is contained in:
Michelle Tang
2026-01-05 12:56:13 -05:00
committed by jeffrey-signal
parent fd635542c0
commit f7d87f3436
19 changed files with 138 additions and 156 deletions

View File

@@ -218,12 +218,11 @@ class ConversationRepository(
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(messageRecord.threadId)!!
val pollSentTimestamp = messageRecord.dateSent
if (threadRecipient.groupId.getOrNull()?.isV2 != true) {
if (threadRecipient.isPushV2Group && threadRecipient.groupId.getOrNull()?.isV2 != true) {
Log.w(TAG, "Missing group id")
emitter.tryOnError(Exception("Poll terminate failed"))
}
val groupId = threadRecipient.requireGroupId().requireV2()
val message = OutgoingMessage.pollTerminateMessage(
threadRecipient = threadRecipient,
sentTimeMillis = System.currentTimeMillis(),
@@ -233,12 +232,17 @@ class ConversationRepository(
Log.i(TAG, "Sending poll terminate to " + message.threadRecipient.id + ", thread: " + messageRecord.threadId)
val possibleTargets: List<Recipient> = SignalDatabase.groups.getGroupMembers(groupId, GroupTable.MemberSet.FULL_MEMBERS_EXCLUDING_SELF)
.map { it.resolve() }
.distinctBy { it.id }
val possibleTargets: List<Recipient> = if (threadRecipient.isPushV2Group) {
SignalDatabase.groups.getGroupMembers(threadRecipient.requireGroupId().requireV2(), GroupTable.MemberSet.FULL_MEMBERS_EXCLUDING_SELF)
.map { it.resolve() }
.distinctBy { it.id }
} else {
listOf(threadRecipient)
}
val isSelf = threadRecipient.isSelf
val eligibleTargets: List<Recipient> = RecipientUtil.getEligibleForSending(possibleTargets)
val results = sendEndPoll(threadRecipient, message, eligibleTargets, poll.messageId)
val results = sendEndPoll(threadRecipient, message, eligibleTargets, isSelf, poll.messageId)
val sendResults = GroupSendJobHelper.getCompletedSends(eligibleTargets, results)
if (sendResults.completed.isNotEmpty() || possibleTargets.isEmpty()) {
@@ -271,9 +275,9 @@ class ConversationRepository(
}
@Throws(IOException::class, GroupNotAMemberException::class, UndeliverableMessageException::class)
fun sendEndPoll(group: Recipient, message: OutgoingMessage, destinations: List<Recipient>, messageId: Long): List<SendMessageResult?> {
val groupId = group.requireGroupId().requireV2()
val groupRecord: GroupRecord? = SignalDatabase.groups.getGroup(group.requireGroupId()).getOrNull()
fun sendEndPoll(threadRecipient: Recipient, message: OutgoingMessage, destinations: List<Recipient>, isSelf: Boolean, messageId: Long): List<SendMessageResult?> {
val groupId = if (threadRecipient.isPushV2Group) threadRecipient.requireGroupId().requireV2() else null
val groupRecord: GroupRecord? = if (threadRecipient.isPushV2Group) SignalDatabase.groups.getGroup(threadRecipient.requireGroupId()).getOrNull() else null
if (groupRecord != null && groupRecord.isAnnouncementGroup && !groupRecord.isAdmin(Recipient.self())) {
throw UndeliverableMessageException("Non-admins cannot send messages in announcement groups!")
@@ -281,29 +285,35 @@ class ConversationRepository(
val builder = newBuilder()
GroupUtil.setDataMessageGroupContext(AppDependencies.application, builder, groupId)
if (groupId != null) {
GroupUtil.setDataMessageGroupContext(AppDependencies.application, builder, groupId)
}
val sentTime = System.currentTimeMillis()
val groupMessage = builder
val message = builder
.withTimestamp(sentTime)
.withExpiration((message.expiresIn / 1000).toInt())
.withProfileKey(ProfileKeyUtil.getSelfProfileKey().serialize())
.withPollTerminate(SignalServiceDataMessage.PollTerminate(message.messageExtras!!.pollTerminate!!.targetTimestamp))
.build()
return GroupSendUtil.sendResendableDataMessage(
applicationContext,
groupId,
null,
destinations,
false,
ContentHint.RESENDABLE,
MessageId(messageId),
groupMessage,
true,
false,
null
) { System.currentTimeMillis() - sentTime > POLL_TERMINATE_TIMEOUT.inWholeMilliseconds }
return if (isSelf) {
listOf(AppDependencies.signalServiceMessageSender.sendSyncMessage(message))
} else {
GroupSendUtil.sendResendableDataMessage(
applicationContext,
groupId,
null,
destinations,
false,
ContentHint.RESENDABLE,
MessageId(messageId),
message,
true,
false,
null
) { System.currentTimeMillis() - sentTime > POLL_TERMINATE_TIMEOUT.inWholeMilliseconds }
}
}
fun getPinnedMessages(threadId: Long): List<MmsMessageRecord> {

View File

@@ -67,6 +67,7 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeDialogFragment
import org.thoughtcrime.securesms.polls.Poll
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.ViewUtil
import kotlin.time.Duration.Companion.milliseconds
@@ -78,6 +79,7 @@ class CreatePollFragment : ComposeDialogFragment() {
companion object {
private val TAG = Log.tag(CreatePollFragment::class)
val MAX_QUESTION_CHARACTER_LENGTH = if (RemoteConfig.pollsV2) 200 else 100
const val MAX_CHARACTER_LENGTH = 100
const val MAX_OPTIONS = 10
const val MIN_OPTIONS = 2
@@ -222,7 +224,7 @@ private fun CreatePollScreen(
TextFieldWithCountdown(
value = question,
label = { Text(text = stringResource(R.string.CreatePollFragment__ask_a_question)) },
onValueChange = { question = it.substring(0, minOf(it.length, CreatePollFragment.MAX_CHARACTER_LENGTH)) },
onValueChange = { question = it.substring(0, minOf(it.length, CreatePollFragment.MAX_QUESTION_CHARACTER_LENGTH)) },
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
colors = TextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
@@ -232,6 +234,7 @@ private fun CreatePollScreen(
.fillMaxWidth()
.onFocusChanged { focusState -> if (focusState.isFocused) focusedOption = -1 }
.focusRequester(focusRequester),
maxCharacterLength = CreatePollFragment.MAX_QUESTION_CHARACTER_LENGTH,
countdownThreshold = CreatePollFragment.CHARACTER_COUNTDOWN_THRESHOLD
)
@@ -266,6 +269,7 @@ private fun CreatePollScreen(
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
},
maxCharacterLength = CreatePollFragment.MAX_CHARACTER_LENGTH,
countdownThreshold = CreatePollFragment.CHARACTER_COUNTDOWN_THRESHOLD
)
}
@@ -316,9 +320,10 @@ private fun TextFieldWithCountdown(
colors: TextFieldColors,
modifier: Modifier,
trailingIcon: @Composable () -> Unit = {},
maxCharacterLength: Int,
countdownThreshold: Int
) {
val charactersRemaining = CreatePollFragment.MAX_CHARACTER_LENGTH - value.length
val charactersRemaining = maxCharacterLength - value.length
val displayCountdown = charactersRemaining <= countdownThreshold
Box(modifier = Modifier.padding(start = 24.dp, end = 24.dp, bottom = 16.dp)) {

View File

@@ -131,7 +131,7 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_
private fun updateButtonsAvailable(recipient: Recipient) {
val paymentsValues = SignalStore.payments
val isPaymentsAvailable = paymentsValues.paymentsAvailability.isSendAllowed && !recipient.isSelf && !recipient.isGroup && recipient.isRegistered
val isPollsAvailable = recipient.isPushV2Group && RemoteConfig.polls
val isPollsAvailable = recipient.isPushV2Group || RemoteConfig.pollsV2
if (!isPaymentsAvailable && !isPollsAvailable) {
attachmentKeyboardView.filterAttachmentKeyboardButtons(removePaymentFilter.and(removePollFilter))