Skip to content

Commit

Permalink
fix: blinking keyboard [WPB-4797] (#2258)
Browse files Browse the repository at this point in the history
  • Loading branch information
Garzas committed Sep 27, 2023
1 parent d15cb06 commit f65934a
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy
import com.datadog.android.rum.tracking.ComponentPredicate
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.ui.WireActivity
import com.wire.android.util.getDeviceIdString
import com.wire.android.util.sha256
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.wire.android.util.getDeviceIdString

private const val LONG_TASK_THRESH_HOLD_MS = 1000L

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,9 @@ private fun ConversationScreen(
snackbarHost = {
SwipeDismissSnackbarHost(
hostState = snackbarHostState,
modifier = Modifier.fillMaxWidth().imePadding()
modifier = Modifier
.fillMaxWidth()
.imePadding()
)
},
content = { internalPadding ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imeAnimationSource
import androidx.compose.foundation.layout.imeAnimationTarget
import androidx.compose.foundation.layout.isImeVisible
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
Expand All @@ -39,6 +41,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
Expand All @@ -53,7 +56,7 @@ import com.wire.android.ui.home.messagecomposer.state.MessageCompositionType
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.util.isPositiveNotNull

@OptIn(ExperimentalLayoutApi::class)
@OptIn(ExperimentalLayoutApi::class, ExperimentalComposeUiApi::class)
@Suppress("ComplexMethod")
@Composable
fun EnabledMessageComposer(
Expand All @@ -74,6 +77,7 @@ fun EnabledMessageComposer(
val navBarHeight = BottomNavigationBarHeight()
val isImeVisible = WindowInsets.isImeVisible
val offsetY = WindowInsets.ime.getBottom(density)
val isKeyboardMoving = isKeyboardMoving()

with(messageComposerStateHolder) {
val inputStateHolder = messageCompositionInputStateHolder
Expand All @@ -85,6 +89,7 @@ fun EnabledMessageComposer(
LaunchedEffect(isImeVisible) {
inputStateHolder.handleIMEVisibility(isImeVisible)
}

LaunchedEffect(modalBottomSheetState.isVisible) {
if (modalBottomSheetState.isVisible) {
messageCompositionInputStateHolder.clearFocus()
Expand Down Expand Up @@ -214,10 +219,13 @@ fun EnabledMessageComposer(
onRichOptionButtonClicked = messageCompositionHolder::addOrRemoveMessageMarkdown,
onPingOptionClicked = onPingOptionClicked,
onAdditionalOptionsMenuClicked = {
if (inputStateHolder.subOptionsVisible) {
messageCompositionInputStateHolder.toComposing()
} else {
showAdditionalOptionsMenu()
if (!isKeyboardMoving) {
if (additionalOptionStateHolder.selectedOption == AdditionalOptionSelectItem.AttachFile) {
additionalOptionStateHolder.hideAdditionalOptionsMenu()
messageCompositionInputStateHolder.toComposing()
} else {
showAdditionalOptionsMenu()
}
}
},
onRichEditingButtonClicked = additionalOptionStateHolder::toRichTextEditing,
Expand Down Expand Up @@ -259,3 +267,13 @@ fun EnabledMessageComposer(
}
}
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun isKeyboardMoving(): Boolean {
val density = LocalDensity.current
val isImeVisible = WindowInsets.isImeVisible
val imeAnimationSource = WindowInsets.imeAnimationSource.getBottom(density)
val imeAnimationTarget = WindowInsets.imeAnimationTarget.getBottom(density)
return isImeVisible && imeAnimationSource != imeAnimationTarget
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ package com.wire.android.ui.home.messagecomposer

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -48,7 +50,7 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
Expand Down Expand Up @@ -161,6 +163,8 @@ fun ActiveMessageComposerInput(
onChangeSelfDeletionClicked = onChangeSelfDeletionClicked
)
}

else -> {}
}
}
}
Expand Down Expand Up @@ -193,14 +197,27 @@ private fun MessageComposerTextInput(
onLineBottomYCoordinateChanged: (Float) -> Unit = { },
modifier: Modifier = Modifier
) {
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() }
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
var isReadOnly by remember { mutableStateOf(false) }

var focused by remember(inputFocused) { mutableStateOf(inputFocused) }
LaunchedEffect(inputFocused) {
if (inputFocused) {
isReadOnly = false
keyboardController?.show()
focusRequester.requestFocus()
} else {
isReadOnly = true
keyboardController?.hide()
}
}

LaunchedEffect(focused) {
if (focused) focusRequester.requestFocus()
else focusManager.clearFocus()
LaunchedEffect(isPressed) {
if (isPressed) {
onFocusChanged(true)
}
}

WireTextField(
Expand All @@ -212,13 +229,15 @@ private fun MessageComposerTextInput(
textStyle = MaterialTheme.wireTypography.body01,
// Add an extra space so that the cursor is placed one space before "Type a message"
placeholderText = " $placeHolderText",
modifier = modifier.then(
Modifier
.onFocusChanged { focusState ->
readOnly = isReadOnly,
modifier = modifier
.focusRequester(focusRequester)
.onFocusChanged { focusState ->
if (focusState.isFocused) {
onFocusChanged(focusState.isFocused)
}
.focusRequester(focusRequester)
),
},
interactionSource = interactionSource,
onSelectedLineIndexChanged = onSelectedLineIndexChanged,
onLineBottomYCoordinateChanged = onLineBottomYCoordinateChanged
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,33 @@ package com.wire.android.ui.home.messagecomposer.attachments

import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode
import com.wire.android.R
import com.wire.android.di.hiltViewModelScoped
import com.wire.android.ui.common.button.WireButtonState
import com.wire.android.ui.common.button.WireSecondaryIconButton
import com.wire.android.util.ui.PreviewMultipleThemes
import kotlinx.coroutines.delay

/**
* Represents an additional option button with controlled interactivity for message composer.
*
* The button's clickability can be controlled via the `isSelected` parameter. This composable also
* internally handles preventing rapid successive clicks using the `enableAgain` variable. This
* mechanism is particularly important to ensure that, during keyboard transitions (expanding or
* collapsing), unintended or unexpected repetitive button clicks do not occur, preventing the
* keyboard from collapsing in an unexpected manner.
*
* @param isSelected Indicates whether the button is selected or not.
* @param onClick The action to be performed when the button is clicked.
* @param modifier The optional [Modifier] to be applied to this composable.
*/
@Composable
fun AdditionalOptionButton(
isSelected: Boolean,
Expand All @@ -39,9 +58,21 @@ fun AdditionalOptionButton(
hiltViewModelScoped<IsFileSharingEnabledViewModelImpl, IsFileSharingEnabledArgs>(IsFileSharingEnabledArgs)
}

var enableAgain by remember { mutableStateOf(true) }
LaunchedEffect(enableAgain, block = {
if (enableAgain) return@LaunchedEffect
delay(timeMillis = BUTTON_CLICK_DELAY_MILLIS)
enableAgain = true
})

Box(modifier = modifier) {
WireSecondaryIconButton(
onButtonClicked = onClick,
onButtonClicked = {
if (enableAgain) {
enableAgain = false
onClick()
}
},
iconResource = R.drawable.ic_add,
contentDescription = R.string.content_description_attachment_item,
state = if (!viewModel.isFileSharingEnabled()) WireButtonState.Disabled
Expand All @@ -50,8 +81,16 @@ fun AdditionalOptionButton(
}
}

private const val BUTTON_CLICK_DELAY_MILLIS = 400L

@PreviewMultipleThemes
@Composable
fun PreviewAdditionalOptionButtonEnabled() {
fun PreviewAdditionalOptionButtonUnSelected() {
AdditionalOptionButton(isSelected = false, onClick = {})
}

@PreviewMultipleThemes
@Composable
fun PreviewAdditionalOptionButtonSelected() {
AdditionalOptionButton(isSelected = true, onClick = {})
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ class MessageComposerStateHolder(
fun showAdditionalOptionsMenu() {
messageCompositionInputStateHolder.showOptions()
additionalOptionStateHolder.showAdditionalOptionsMenu()
messageCompositionInputStateHolder.clearFocus()
}

fun clearMessage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ class MessageCompositionHolder(

messageComposition.update {
it.copy(
messageTextFieldValue = TextFieldValue(""),
quotedMessage = quotedMessage,
quotedMessageId = message.header.messageId
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class MessageCompositionInputStateHolder(
val actualOffset = max(offset - navBarHeight, 0.dp)

if (previousOffset < actualOffset) {
optionsVisible = true
if (!subOptionsVisible || optionsHeight <= actualOffset) {
optionsHeight = actualOffset
subOptionsVisible = false
Expand Down Expand Up @@ -151,6 +152,7 @@ class MessageCompositionInputStateHolder(
optionsVisible = true
subOptionsVisible = true
optionsHeight = keyboardHeight
clearFocus()
}

fun handleBackPressed(isImeVisible: Boolean, additionalOptionsSubMenuState: AdditionalOptionSubMenuState) {
Expand Down
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ androidx-workManager = "2.8.1"
androidx-browser = "1.5.0"

# Compose
compose = "1.5.1"
compose-material = "1.4.3"
compose = "1.6.0-alpha05"
compose-material = "1.6.0-alpha05"
compose-activity = "1.7.2"
compose-compiler = "1.5.0"
compose-compiler = "1.5.2"
compose-constraint = "1.0.1"
compose-material3 = "1.1.1"
compose-navigation = "2.6.0"
Expand Down

0 comments on commit f65934a

Please sign in to comment.