Skip to content

Commit

Permalink
fix: crashing message composer input [WPB-8727] (#2988)
Browse files Browse the repository at this point in the history
  • Loading branch information
saleniuk authored and github-actions[bot] committed May 10, 2024
1 parent bcee848 commit 5b22247
Show file tree
Hide file tree
Showing 17 changed files with 336 additions and 24 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ dependencies {

implementation(libs.compose.ui)
implementation(libs.compose.foundation)
implementation(libs.compose.material.android)
// we still cannot get rid of material2 because swipeable is still missing - https://issuetracker.google.com/issues/229839039
// https://developer.android.com/jetpack/compose/designsystems/material2-material3#components-and
implementation(libs.compose.material.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material.ripple
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
Expand Down Expand Up @@ -62,7 +62,7 @@ fun CameraButton(
.wrapContentSize()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(
indication = ripple(
bounded = false,
radius = dimensions().defaultCallingControlsSize / 2
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ package com.wire.android.ui.calling.controlbuttons
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material.ripple
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
Expand All @@ -46,7 +46,7 @@ fun CameraFlipButton(
.wrapContentSize()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false, radius = dimensions().defaultCallingControlsSize / 2),
indication = ripple(bounded = false, radius = dimensions().defaultCallingControlsSize / 2),
role = Role.Button,
onClick = onCameraFlipButtonClicked

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ package com.wire.android.ui.calling.controlbuttons
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.size
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material.ripple
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
Expand All @@ -45,7 +45,7 @@ fun DeclineButton(buttonClicked: () -> Unit) {
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(
indication = ripple(
bounded = false,
radius = dimensions().outgoingCallHangUpButtonSize / 2
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material.ripple
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
Expand All @@ -49,7 +49,7 @@ fun MicrophoneButton(
.wrapContentSize()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false, radius = dimensions().defaultCallingControlsSize / 2),
indication = ripple(bounded = false, radius = dimensions().defaultCallingControlsSize / 2),
role = Role.Button,
onClick = { onMicrophoneButtonClicked() }
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material.ripple
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
Expand All @@ -50,7 +49,7 @@ fun SpeakerButton(
.wrapContentSize()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false, radius = dimensions().defaultCallingControlsSize / 2),
indication = ripple(bounded = false, radius = dimensions().defaultCallingControlsSize / 2),
role = Role.Button,
onClick = { onSpeakerButtonClicked() }
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ package com.wire.android.ui.common
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material.ripple
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
Expand Down Expand Up @@ -62,7 +62,7 @@ fun Modifier.selectableBackground(isSelected: Boolean, onClick: () -> Unit): Mod
selected = isSelected,
onClick = { onClick() },
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = true, color = MaterialTheme.colorScheme.onBackground.copy(0.5f)),
indication = ripple(bounded = true, color = MaterialTheme.colorScheme.onBackground.copy(0.5f)),
role = Role.Tab
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.common.textfield

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.text.input.TextFieldCharSequence
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.ui.Modifier
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.text.input.TextFieldValue
import io.github.esentsov.PackagePrivate

/**
* Enables us to temporarily still use TextFieldValue and onValueChanged callback instead of TextFieldState directly,
* also allows us to get selection updates as by default BasicTextField2 callback only gives a String without selection.
* @sample androidx.compose.foundation.samples.BasicTextFieldWithValueOnValueChangeSample
* TODO: Remove this class once all WireTextField usages are migrated to use TextFieldState.
*/
@PackagePrivate
internal class StateSyncingModifier(
private val state: TextFieldState,
private val value: TextFieldValue,
private val onValueChanged: (TextFieldValue) -> Unit,
) : ModifierNodeElement<StateSyncingModifierNode>() {

override fun create(): StateSyncingModifierNode = StateSyncingModifierNode(state, onValueChanged)

override fun update(node: StateSyncingModifierNode) {
node.update(value, onValueChanged)
}

@Suppress("EqualsAlwaysReturnsTrueOrFalse")
override fun equals(other: Any?): Boolean = false

override fun hashCode(): Int = state.hashCode()

@Suppress("EmptyFunctionBlock")
override fun InspectorInfo.inspectableProperties() {}
}

@OptIn(ExperimentalFoundationApi::class)
@PackagePrivate
internal class StateSyncingModifierNode(
private val state: TextFieldState,
private var onValueChanged: (TextFieldValue) -> Unit,
) : Modifier.Node(), ObserverModifierNode {
override val shouldAutoInvalidate: Boolean
get() = false

fun update(value: TextFieldValue, onValueChanged: (TextFieldValue) -> Unit) {
this.onValueChanged = onValueChanged
if (value.text != state.text.toString() || value.selection != state.text.selection) {
state.edit {
if (value.text != state.text.toString()) {
replace(0, length, value.text)
}
if (value.selection != state.text.selection) {
selection = value.selection
}
}
onValueChanged(value)
}
}

override fun onAttach() {
observeTextState(fireOnValueChanged = false)
}

override fun onObservedReadsChanged() {
observeTextState()
}

private fun observeTextState(fireOnValueChanged: Boolean = true) {
lateinit var text: TextFieldCharSequence
observeReads {
text = state.text
}
if (fireOnValueChanged) {
val newValue = TextFieldValue(
text = text.toString(),
selection = text.selection,
composition = text.composition
)
onValueChanged(newValue)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import com.wire.android.ui.common.Tint
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.EMPTY
import io.github.esentsov.PackagePrivate

@Composable
internal fun WireTextField(
Expand Down Expand Up @@ -156,7 +157,7 @@ internal fun WireTextField(
decorationBox = { innerTextField ->
InnerText(
innerTextField,
value,
value.text.isEmpty(),
leadingIcon,
trailingIcon,
placeholderText,
Expand Down Expand Up @@ -219,10 +220,11 @@ fun Label(
}
}

@PackagePrivate
@Composable
private fun InnerText(
internal fun InnerText(
innerTextField: @Composable () -> Unit,
value: TextFieldValue,
shouldShowPlaceholder: Boolean,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
placeholderText: String? = null,
Expand All @@ -232,12 +234,12 @@ private fun InnerText(
inputMinHeight: Dp = 48.dp,
colors: WireTextFieldColors = wireTextFieldColors(),
shouldDetectTaps: Boolean = false,
onClick: (Offset) -> Unit = { }
onTap: (Offset) -> Unit = { }
) {
var modifier: Modifier = Modifier
if (shouldDetectTaps) {
modifier = modifier.pointerInput(Unit) {
detectTapGestures(onTap = onClick)
detectTapGestures(onTap = onTap)
}
}

Expand Down Expand Up @@ -266,7 +268,7 @@ private fun InnerText(
top = 2.dp, bottom = 2.dp
)
) {
if (value.text.isEmpty() && placeholderText != null) {
if (shouldShowPlaceholder && placeholderText != null) {
Text(
text = placeholderText,
style = placeholderTextStyle,
Expand Down
Loading

0 comments on commit 5b22247

Please sign in to comment.