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

fix: crashing message composer input [WPB-8727] #2988

Merged
merged 11 commits into from
May 10, 2024
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
Loading