Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enter to sent message UI part-2 [WPB-15369] #3845

Merged
merged 15 commits into from
Feb 5, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class GlobalDataStore @Inject constructor(@ApplicationContext private val contex
private val IS_LOGGING_ENABLED = booleanPreferencesKey("is_logging_enabled")
private val APP_LOCK_PASSCODE = stringPreferencesKey("app_lock_passcode")
private val APP_LOCK_SOURCE = intPreferencesKey("app_lock_source")
private val ENTER_TO_SENT = booleanPreferencesKey("enter_to_sent")

val APP_THEME_OPTION = stringPreferencesKey("app_theme_option")
val RECORD_AUDIO_EFFECTS_CHECKBOX = booleanPreferencesKey("record_audio_effects_checkbox")
Expand Down Expand Up @@ -232,4 +233,9 @@ class GlobalDataStore @Inject constructor(@ApplicationContext private val contex
fun selectedThemeOptionFlow(): Flow<ThemeOption> =
getStringPreference(APP_THEME_OPTION, ThemeOption.SYSTEM.toString())
.map { ThemeOption.valueOf(it) }

fun enterToSendFlow(): Flow<Boolean> = getBooleanPreference(ENTER_TO_SENT, false)
suspend fun setEnterToSend(enabled: Boolean) {
context.dataStore.edit { it[ENTER_TO_SENT] = enabled }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ fun WireLabelledCheckbox(
fun WireCheckbox(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
enabled: Boolean = true,
modifier: Modifier = Modifier,
enabled: Boolean = true
) {
Checkbox(
checked = checked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ internal fun WireTextField(
textState,
onSelectedLineIndexChanged,
onLineBottomYCoordinateChanged
)
),
)
}
)
Expand Down Expand Up @@ -185,6 +185,24 @@ val KeyboardOptions.Companion.DefaultText: KeyboardOptions
capitalization = KeyboardCapitalization.Sentences,
)

@Stable
val KeyboardOptions.Companion.MessageComposerEnterToSend: KeyboardOptions
get() = Default.copy(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Send,
autoCorrectEnabled = true,
capitalization = KeyboardCapitalization.Sentences,
)

@Stable
val KeyboardOptions.Companion.MessageComposerDefault: KeyboardOptions
get() = Default.copy(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.None,
autoCorrectEnabled = true,
capitalization = KeyboardCapitalization.Sentences,
)

@Stable
val KeyboardOptions.Companion.DefaultEmailDone: KeyboardOptions
get() = defaultEmail(ImeAction.Done)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ data class MessageComposerViewState(
val isFileSharingEnabled: Boolean = true,
val interactionAvailability: InteractionAvailability = InteractionAvailability.ENABLED,
val mentionSearchResult: List<Contact> = listOf(),
val mentionSearchQuery: String = String.EMPTY
val mentionSearchQuery: String = String.EMPTY,
val enterToSend: Boolean = false,
)

sealed class AssetTooLargeDialogState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.mapper.ContactMapper
import com.wire.android.navigation.SavedStateViewModel
import com.wire.android.ui.home.conversations.ConversationNavArgs
Expand Down Expand Up @@ -56,6 +57,7 @@ import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.user.IsFileSharingEnabledUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
Expand All @@ -80,6 +82,7 @@ class MessageComposerViewModel @Inject constructor(
private val fileManager: FileManager,
private val kaliumFileSystem: KaliumFileSystem,
private val currentSessionFlowUseCase: CurrentSessionFlowUseCase,
private val globalDataStore: GlobalDataStore,
) : SavedStateViewModel(savedStateHandle) {

var messageComposerViewState = mutableStateOf(MessageComposerViewState())
Expand Down Expand Up @@ -107,6 +110,15 @@ class MessageComposerViewModel @Inject constructor(
initTempWritableImageUri()
observeIsTypingAvailable()
setFileSharingStatus()
getEnterToSendState()
}

private fun getEnterToSendState() {
viewModelScope.launch {
globalDataStore.enterToSendFlow().first().also {
messageComposerViewState.value = messageComposerViewState.value.copy(enterToSend = it)
}
}
}

private fun initTempWritableVideoUri() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.GenericShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.KeyboardActionHandler
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -65,6 +68,12 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isShiftPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
Expand All @@ -77,6 +86,8 @@ import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversa
import com.wire.android.ui.common.bottombar.bottomNavigationBarHeight
import com.wire.android.ui.common.colorsScheme
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.textfield.MessageComposerDefault
import com.wire.android.ui.common.textfield.MessageComposerEnterToSend
import com.wire.android.ui.home.conversations.ConversationActionPermissionType
import com.wire.android.ui.home.conversations.UsersTypingIndicatorForConversation
import com.wire.android.ui.home.conversations.model.UriAsset
Expand Down Expand Up @@ -209,10 +220,40 @@ fun EnabledMessageComposer(
Box(fillRemainingSpaceOrWrapContent, contentAlignment = Alignment.BottomCenter) {
var currentSelectedLineIndex by remember { mutableStateOf(0) }
var cursorCoordinateY by remember { mutableStateOf(0F) }
val canSendMessage by remember {
derivedStateOf {
messageCompositionInputStateHolder.inputType is InputType.Composing &&
(messageCompositionInputStateHolder.inputType as InputType.Composing).isSendButtonEnabled
}
}
val keyboardOptions by remember {
derivedStateOf {
if (messageComposerViewState.value.enterToSend) {
KeyboardOptions.Companion.MessageComposerEnterToSend
} else {
KeyboardOptions.Companion.MessageComposerDefault
}
}
}

val keyboardActionHandler by remember {
derivedStateOf {
KeyboardActionHandler {
if (canSendMessage) {
onSendButtonClicked()
} else {
Unit
}
}
}
}

ActiveMessageComposerInput(
conversationId = conversationId,
messageComposition = messageComposition.value,
keyboardOptions = keyboardOptions,
onKeyboardAction = keyboardActionHandler,
canSendMessage = canSendMessage,
messageTextState = inputStateHolder.messageTextState,
isTextExpanded = inputStateHolder.isTextExpanded,
inputType = messageCompositionInputStateHolder.inputType,
Expand Down Expand Up @@ -270,7 +311,27 @@ fun EnabledMessageComposer(
transferableContent
}
}
),
)
.onPreviewKeyEvent { keyEvent ->
if (keyEvent.type != KeyEventType.KeyDown) {
return@onPreviewKeyEvent false
}
if (keyEvent.isShiftPressed && keyEvent.key == Key.Enter) {
messageComposerStateHolder.messageCompositionInputStateHolder.messageTextState.edit {
append("\n")
}
true
} else if (keyEvent.key == Key.Enter) {
if (canSendMessage) {
onSendButtonClicked()
} else {
Unit
}
true
} else {
false
}
},
)

val mentionSearchResult = messageComposerViewState.value.mentionSearchResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.KeyboardActionHandler
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
Expand All @@ -52,7 +53,6 @@ import androidx.compose.ui.input.key.nativeKeyCode
import androidx.compose.ui.input.key.onPreInterceptKeyBeforeSoftKeyboard
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
Expand All @@ -62,7 +62,7 @@ import com.wire.android.di.hiltViewModelScoped
import com.wire.android.ui.common.colorsScheme
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.spacers.VerticalSpace
import com.wire.android.ui.common.textfield.DefaultText
import com.wire.android.ui.common.textfield.MessageComposerDefault
import com.wire.android.ui.common.textfield.WireTextField
import com.wire.android.ui.common.textfield.WireTextFieldColors
import com.wire.android.ui.common.textfield.WireTextFieldState
Expand All @@ -89,6 +89,9 @@ fun ActiveMessageComposerInput(
isTextExpanded: Boolean,
inputType: InputType,
focusRequester: FocusRequester,
keyboardOptions: KeyboardOptions,
onKeyboardAction: KeyboardActionHandler?,
canSendMessage: Boolean,
onSendButtonClicked: () -> Unit,
onEditButtonClicked: () -> Unit,
onChangeSelfDeletionClicked: (currentlySelected: SelfDeletionTimer) -> Unit,
Expand All @@ -102,7 +105,7 @@ fun ActiveMessageComposerInput(
showOptions: Boolean,
optionsSelected: Boolean,
onPlusClick: () -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
Expand Down Expand Up @@ -133,6 +136,9 @@ fun ActiveMessageComposerInput(
inputType = inputType,
focusRequester = focusRequester,
onSendButtonClicked = onSendButtonClicked,
keyboardOptions = keyboardOptions,
onKeyboardAction = onKeyboardAction,
canSendMessage = canSendMessage,
onChangeSelfDeletionClicked = onChangeSelfDeletionClicked,
onFocused = onFocused,
onSelectedLineIndexChanged = onSelectedLineIndexChanged,
Expand Down Expand Up @@ -169,6 +175,9 @@ private fun InputContent(
isTextExpanded: Boolean,
inputType: InputType,
focusRequester: FocusRequester,
keyboardOptions: KeyboardOptions,
onKeyboardAction: KeyboardActionHandler?,
canSendMessage: Boolean,
onSendButtonClicked: () -> Unit,
onChangeSelfDeletionClicked: (currentlySelected: SelfDeletionTimer) -> Unit,
onFocused: () -> Unit,
Expand Down Expand Up @@ -215,6 +224,8 @@ private fun InputContent(
onSelectedLineIndexChanged = onSelectedLineIndexChanged,
onLineBottomYCoordinateChanged = onLineBottomYCoordinateChanged,
onTextCollapse = onTextCollapse,
keyboardOptions = keyboardOptions,
onKeyBoardAction = onKeyboardAction,
modifier = Modifier
.fillMaxWidth()
.constrainAs(input) {
Expand Down Expand Up @@ -247,15 +258,13 @@ private fun InputContent(
UsersTypingIndicatorForConversation(conversationId = conversationId)
}
}
if (inputType is InputType.Composing && (showOptions || inputType.isSendButtonEnabled)) {
MessageSendActions(
onSendButtonClicked = onSendButtonClicked,
sendButtonEnabled = inputType.isSendButtonEnabled,
selfDeletionTimer = viewModel.state(),
onChangeSelfDeletionClicked = onChangeSelfDeletionClicked,
modifier = Modifier.padding(end = dimensions().spacing8x)
)
}
MessageSendActions(
onSendButtonClicked = onSendButtonClicked,
sendButtonEnabled = canSendMessage,
selfDeletionTimer = viewModel.state(),
onChangeSelfDeletionClicked = onChangeSelfDeletionClicked,
modifier = Modifier.padding(end = dimensions().spacing8x)
)
}
}
}
Expand All @@ -270,9 +279,11 @@ private fun MessageComposerTextInput(
placeHolderText: String,
onTextCollapse: () -> Unit,
onFocused: () -> Unit,
keyboardOptions: KeyboardOptions,
onKeyBoardAction: KeyboardActionHandler?,
modifier: Modifier = Modifier,
onSelectedLineIndexChanged: (Int) -> Unit = { },
onLineBottomYCoordinateChanged: (Float) -> Unit = { }
onLineBottomYCoordinateChanged: (Float) -> Unit = { },
) {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
Expand All @@ -290,7 +301,8 @@ private fun MessageComposerTextInput(
// Add an extra space so that the cursor is placed one space before "Type a message"
placeholderText = " $placeHolderText",
state = WireTextFieldState.Default,
keyboardOptions = KeyboardOptions.DefaultText.copy(imeAction = ImeAction.None),
keyboardOptions = keyboardOptions,
onKeyboardAction = onKeyBoardAction,
modifier = modifier
.focusable(true)
.focusRequester(focusRequester)
Expand Down Expand Up @@ -356,6 +368,9 @@ private fun PreviewActiveMessageComposerInput(inputType: InputType, isTextExpand
messageTextState = TextFieldState(""),
isTextExpanded = isTextExpanded,
inputType = inputType,
keyboardOptions = KeyboardOptions.Companion.MessageComposerDefault,
onKeyboardAction = null,
canSendMessage = true,
focusRequester = FocusRequester(),
onSendButtonClicked = {},
onEditButtonClicked = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import com.wire.android.ui.common.clickable
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.destinations.AboutThisAppScreenDestination
import com.wire.android.ui.destinations.AppSettingsScreenDestination
import com.wire.android.ui.destinations.AppearanceScreenDestination
import com.wire.android.ui.destinations.CustomizationScreenDestination
import com.wire.android.ui.destinations.BackupAndRestoreScreenDestination
import com.wire.android.ui.destinations.DebugScreenDestination
import com.wire.android.ui.destinations.DependenciesScreenDestination
Expand Down Expand Up @@ -157,10 +157,10 @@ sealed class SettingsItem(open val id: String, open val title: UIText) {
direction = MyAccountScreenDestination
)

data object Appearance : DirectionItem(
id = "appearance_settings",
title = UIText.StringResource(R.string.settings_appearance_label),
direction = AppearanceScreenDestination
data object Customization : DirectionItem(
id = "customization_settings",
title = UIText.StringResource(R.string.settings_customization_label),
direction = CustomizationScreenDestination
)

data object NetworkSettings : DirectionItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ fun SettingsScreenContent(
header = context.getString(R.string.settings_account_settings_label),
items = buildList {
add(SettingsItem.YourAccount)
add(SettingsItem.Appearance)
add(SettingsItem.PrivacySettings)
add(SettingsItem.ManageDevices)
if (BackUpSettings) {
Expand All @@ -117,6 +116,7 @@ fun SettingsScreenContent(
folderWithElements(
header = context.getString(R.string.app_settings_screen_title),
items = buildList {
add(SettingsItem.Customization)
if (AppSettings) {
add(SettingsItem.AppSettings)
}
Expand Down
Loading
Loading