mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Show character count when creating a poll.
This commit is contained in:
@@ -2580,7 +2580,7 @@ class ConversationFragment :
|
||||
disposables += endPoll
|
||||
.subscribeBy(
|
||||
// TODO(michelle): Error state when poll terminate fails
|
||||
onError = { Log.w(TAG, "Error received during poll send!", it) }
|
||||
onError = { Log.w(TAG, "Error received during poll end!", it) }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -21,6 +22,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldColors
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -73,6 +75,7 @@ class CreatePollFragment : ComposeDialogFragment() {
|
||||
const val MAX_CHARACTER_LENGTH = 100
|
||||
const val MAX_OPTIONS = 10
|
||||
const val MIN_OPTIONS = 2
|
||||
const val CHARACTER_COUNTDOWN_THRESHOLD = 20
|
||||
const val REQUEST_KEY = "CreatePollFragment"
|
||||
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
@@ -198,7 +201,7 @@ private fun CreatePollScreen(
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
|
||||
TextField(
|
||||
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)) },
|
||||
@@ -209,8 +212,8 @@ private fun CreatePollScreen(
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp)
|
||||
.onFocusChanged { focusState -> if (focusState.isFocused) focusedOption = -1 }
|
||||
.onFocusChanged { focusState -> if (focusState.isFocused) focusedOption = -1 },
|
||||
countdownThreshold = CreatePollFragment.CHARACTER_COUNTDOWN_THRESHOLD
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(32.dp))
|
||||
@@ -225,28 +228,27 @@ private fun CreatePollScreen(
|
||||
|
||||
itemsIndexed(options) { index, option ->
|
||||
DraggableItem(dragDropState, 1 + index) {
|
||||
Box(modifier = Modifier.padding(start = 24.dp, end = 24.dp, bottom = 16.dp)) {
|
||||
TextField(
|
||||
value = option,
|
||||
label = { Text(text = stringResource(R.string.CreatePollFragment__option_n, index + 1)) },
|
||||
onValueChange = { options[index] = it.substring(0, minOf(it.length, CreatePollFragment.MAX_CHARACTER_LENGTH)) },
|
||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
||||
colors = TextFieldDefaults.colors(
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState -> if (focusState.isFocused) focusedOption = index },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.drag_handle),
|
||||
contentDescription = stringResource(R.string.CreatePollFragment__drag_handle),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
TextFieldWithCountdown(
|
||||
value = option,
|
||||
label = { Text(text = stringResource(R.string.CreatePollFragment__option_n, index + 1)) },
|
||||
onValueChange = { options[index] = it.substring(0, minOf(it.length, CreatePollFragment.MAX_CHARACTER_LENGTH)) },
|
||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
||||
colors = TextFieldDefaults.colors(
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState -> if (focusState.isFocused) focusedOption = index },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.drag_handle),
|
||||
contentDescription = stringResource(R.string.CreatePollFragment__drag_handle),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
},
|
||||
countdownThreshold = CreatePollFragment.CHARACTER_COUNTDOWN_THRESHOLD
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +284,50 @@ private fun CreatePollScreen(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Text field with a character countdown. Once [charactersRemaining] has hit [countdownThreshold],
|
||||
* the remaining character count will show within the text field.
|
||||
*/
|
||||
@Composable
|
||||
private fun TextFieldWithCountdown(
|
||||
value: String,
|
||||
label: @Composable () -> Unit,
|
||||
onValueChange: (String) -> Unit,
|
||||
keyboardOptions: KeyboardOptions,
|
||||
colors: TextFieldColors,
|
||||
modifier: Modifier,
|
||||
trailingIcon: @Composable () -> Unit = {},
|
||||
countdownThreshold: Int
|
||||
) {
|
||||
val charactersRemaining = CreatePollFragment.MAX_CHARACTER_LENGTH - value.length
|
||||
val displayCountdown = charactersRemaining <= countdownThreshold
|
||||
|
||||
Box(modifier = Modifier.padding(start = 24.dp, end = 24.dp, bottom = 16.dp)) {
|
||||
TextField(
|
||||
value = value,
|
||||
label = label,
|
||||
onValueChange = onValueChange,
|
||||
keyboardOptions = keyboardOptions,
|
||||
colors = colors,
|
||||
modifier = modifier,
|
||||
trailingIcon = trailingIcon
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = displayCountdown,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(bottom = 6.dp, end = 12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "$charactersRemaining",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (charactersRemaining <= 5) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.outline
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun CreatePollPreview() {
|
||||
|
||||
@@ -8790,7 +8790,7 @@
|
||||
<!-- Button in poll message to view the poll after it has ended -->
|
||||
<string name="Poll__view_poll">View poll</string>
|
||||
<!-- Toast message shown if we cannot find the poll -->
|
||||
<string name="Poll__unable_poll">Unable to find poll</string>
|
||||
<string name="Poll__unable_poll">Poll not found</string>
|
||||
<!-- Button in poll message to view results for a poll after it ends -->
|
||||
<string name="Poll__view_results">View results</string>
|
||||
<!-- Quote preview when replying to a poll. %1$s is the question of the poll -->
|
||||
|
||||
Reference in New Issue
Block a user