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 4ab3510315..32d8abb449 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 @@ -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) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/CreatePollFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/CreatePollFragment.kt index cb72e20fc9..8ce249e7c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/CreatePollFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/CreatePollFragment.kt @@ -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() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9af39164f6..ca51220401 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8790,7 +8790,7 @@ View poll - Unable to find poll + Poll not found View results