[a11y] voice message improvements (#5980)
* A11Y: ensure a11y focus is not lost and reset to the back button when the user start playing a pending voice message. * A11Y: ensure a11y focus is not lost and reset to the back button when the user use the keyboard to focus the send button and press the space bar to perform a click. * Cleanup code. This if was not necessary. * Small rework to prepare a bugfix. No behavior / UI change. * Ensure that the keyboard focus and accessibility focus is not lost when deleting a pending voice message. * Update screenshots * Improve code readability. * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
commit
e311a719e1
15 changed files with 336 additions and 317 deletions
|
|
@ -27,9 +27,9 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredHeightIn
|
import androidx.compose.foundation.layout.requiredHeightIn
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -39,9 +39,10 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
|
|
||||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.hideFromAccessibility
|
import androidx.compose.ui.semantics.hideFromAccessibility
|
||||||
|
|
@ -61,6 +62,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconColorButton
|
import io.element.android.libraries.designsystem.theme.components.IconColorButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
|
@ -70,11 +72,11 @@ import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsProvider
|
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsProvider
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.textcomposer.components.SendButton
|
import io.element.android.libraries.textcomposer.components.SendButtonIcon
|
||||||
import io.element.android.libraries.textcomposer.components.TextFormatting
|
import io.element.android.libraries.textcomposer.components.TextFormatting
|
||||||
import io.element.android.libraries.textcomposer.components.VoiceMessageDeleteButton
|
import io.element.android.libraries.textcomposer.components.VoiceMessageDeleteButtonIcon
|
||||||
import io.element.android.libraries.textcomposer.components.VoiceMessagePreview
|
import io.element.android.libraries.textcomposer.components.VoiceMessagePreview
|
||||||
import io.element.android.libraries.textcomposer.components.VoiceMessageRecorderButton
|
import io.element.android.libraries.textcomposer.components.VoiceMessageRecorderButtonIcon
|
||||||
import io.element.android.libraries.textcomposer.components.VoiceMessageRecording
|
import io.element.android.libraries.textcomposer.components.VoiceMessageRecording
|
||||||
import io.element.android.libraries.textcomposer.components.markdown.MarkdownTextInput
|
import io.element.android.libraries.textcomposer.components.markdown.MarkdownTextInput
|
||||||
import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
|
import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
|
||||||
|
|
@ -123,9 +125,6 @@ fun TextComposer(
|
||||||
is TextEditorState.Markdown -> state.state.text.value()
|
is TextEditorState.Markdown -> state.state.text.value()
|
||||||
is TextEditorState.Rich -> state.richTextEditorState.messageMarkdown
|
is TextEditorState.Rich -> state.richTextEditorState.messageMarkdown
|
||||||
}
|
}
|
||||||
val onSendClick = {
|
|
||||||
onSendMessage()
|
|
||||||
}
|
|
||||||
|
|
||||||
val onPlayVoiceMessageClick = {
|
val onPlayVoiceMessageClick = {
|
||||||
onVoicePlayerEvent(VoiceMessagePlayerEvent.Play)
|
onVoicePlayerEvent(VoiceMessagePlayerEvent.Play)
|
||||||
|
|
@ -143,26 +142,6 @@ fun TextComposer(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.height(IntrinsicSize.Min)
|
.height(IntrinsicSize.Min)
|
||||||
|
|
||||||
val composerOptionsButton: @Composable () -> Unit = remember(composerMode) {
|
|
||||||
@Composable {
|
|
||||||
when (composerMode) {
|
|
||||||
is MessageComposerMode.Attachment -> {
|
|
||||||
Spacer(modifier = Modifier.width(9.dp))
|
|
||||||
}
|
|
||||||
is MessageComposerMode.EditCaption -> {
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
IconColorButton(
|
|
||||||
onClick = onAddAttachment,
|
|
||||||
imageVector = CompoundIcons.Plus(),
|
|
||||||
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val placeholder = if (composerMode.inThread) {
|
val placeholder = if (composerMode.inThread) {
|
||||||
stringResource(id = CommonStrings.action_reply_in_thread)
|
stringResource(id = CommonStrings.action_reply_in_thread)
|
||||||
} else if (composerMode is MessageComposerMode.Attachment || composerMode is MessageComposerMode.EditCaption) {
|
} else if (composerMode is MessageComposerMode.Attachment || composerMode is MessageComposerMode.EditCaption) {
|
||||||
|
|
@ -234,55 +213,137 @@ fun TextComposer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val canSendMessage = markdown.isNotBlank() || composerMode is MessageComposerMode.Attachment
|
val canSendTextMessage = markdown.isNotBlank() || composerMode is MessageComposerMode.Attachment
|
||||||
val sendButton = @Composable {
|
|
||||||
SendButton(
|
|
||||||
canSendMessage = canSendMessage,
|
|
||||||
onClick = onSendClick,
|
|
||||||
composerMode = composerMode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val recordVoiceButton = @Composable {
|
|
||||||
VoiceMessageRecorderButton(
|
|
||||||
isRecording = voiceMessageState is VoiceMessageState.Recording,
|
|
||||||
onEvent = onVoiceRecorderEvent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val sendVoiceButton = @Composable {
|
|
||||||
SendButton(
|
|
||||||
canSendMessage = voiceMessageState is VoiceMessageState.Preview,
|
|
||||||
onClick = onSendVoiceMessage,
|
|
||||||
composerMode = composerMode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val uploadVoiceProgress = @Composable {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val textFormattingOptions: @Composable (() -> Unit)? = (state as? TextEditorState.Rich)?.let {
|
val textFormattingOptions: @Composable (() -> Unit)? = (state as? TextEditorState.Rich)?.let {
|
||||||
@Composable { TextFormatting(state = it.richTextEditorState) }
|
@Composable { TextFormatting(state = it.richTextEditorState) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val sendOrRecordButton = when {
|
val hapticFeedback = LocalHapticFeedback.current
|
||||||
!canSendMessage ->
|
|
||||||
when (voiceMessageState) {
|
fun performHapticFeedback() {
|
||||||
VoiceMessageState.Idle,
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
is VoiceMessageState.Recording -> recordVoiceButton
|
|
||||||
is VoiceMessageState.Preview -> when (voiceMessageState.isSending) {
|
|
||||||
true -> uploadVoiceProgress
|
|
||||||
false -> sendVoiceButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> sendButton
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val endButtonA11y = endButtonA11y(
|
@Composable
|
||||||
composerMode = composerMode,
|
fun rememberEndButtonParams() = remember(
|
||||||
voiceMessageState = voiceMessageState,
|
composerMode.isEditing,
|
||||||
canSendMessage = canSendMessage,
|
voiceMessageState.endButtonKey(),
|
||||||
)
|
canSendTextMessage,
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
!canSendTextMessage ->
|
||||||
|
when (voiceMessageState) {
|
||||||
|
VoiceMessageState.Idle -> EndButtonParams(
|
||||||
|
endButtonContentDescriptionResId = CommonStrings.a11y_voice_message_record,
|
||||||
|
endButtonClick = {
|
||||||
|
performHapticFeedback()
|
||||||
|
onVoiceRecorderEvent.invoke(VoiceMessageRecorderEvent.Start)
|
||||||
|
},
|
||||||
|
endButtonContent = @Composable {
|
||||||
|
VoiceMessageRecorderButtonIcon(
|
||||||
|
isRecording = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is VoiceMessageState.Recording -> EndButtonParams(
|
||||||
|
endButtonContentDescriptionResId = CommonStrings.a11y_voice_message_stop_recording,
|
||||||
|
endButtonClick = {
|
||||||
|
performHapticFeedback()
|
||||||
|
onVoiceRecorderEvent.invoke(VoiceMessageRecorderEvent.Stop)
|
||||||
|
},
|
||||||
|
endButtonContent = @Composable {
|
||||||
|
VoiceMessageRecorderButtonIcon(
|
||||||
|
isRecording = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is VoiceMessageState.Preview -> if (voiceMessageState.isSending) {
|
||||||
|
EndButtonParams(
|
||||||
|
endButtonContentDescriptionResId = CommonStrings.common_sending,
|
||||||
|
endButtonClick = {},
|
||||||
|
endButtonContent = @Composable {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
EndButtonParams(
|
||||||
|
endButtonContentDescriptionResId = CommonStrings.action_send_voice_message,
|
||||||
|
endButtonClick = {
|
||||||
|
onSendVoiceMessage()
|
||||||
|
},
|
||||||
|
endButtonContent = @Composable {
|
||||||
|
SendButtonIcon(
|
||||||
|
canSendMessage = true,
|
||||||
|
isEditing = composerMode.isEditing,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composerMode.isEditing -> EndButtonParams(
|
||||||
|
endButtonContentDescriptionResId = CommonStrings.action_send_edited_message,
|
||||||
|
endButtonClick = {
|
||||||
|
onSendMessage()
|
||||||
|
},
|
||||||
|
endButtonContent = @Composable {
|
||||||
|
SendButtonIcon(
|
||||||
|
canSendMessage = true,
|
||||||
|
isEditing = true,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else -> EndButtonParams(
|
||||||
|
endButtonContentDescriptionResId = CommonStrings.action_send_message,
|
||||||
|
endButtonClick = {
|
||||||
|
onSendMessage()
|
||||||
|
},
|
||||||
|
endButtonContent = @Composable {
|
||||||
|
SendButtonIcon(
|
||||||
|
canSendMessage = true,
|
||||||
|
isEditing = false,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberEndButtonParamsFormatting() = remember(composerMode.isEditing, canSendTextMessage) {
|
||||||
|
if (composerMode.isEditing) {
|
||||||
|
EndButtonParams(
|
||||||
|
endButtonContentDescriptionResId = CommonStrings.action_send_edited_message,
|
||||||
|
endButtonClick = {
|
||||||
|
if (canSendTextMessage) {
|
||||||
|
onSendMessage()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endButtonContent = @Composable {
|
||||||
|
SendButtonIcon(
|
||||||
|
canSendMessage = canSendTextMessage,
|
||||||
|
isEditing = true,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
EndButtonParams(
|
||||||
|
endButtonContentDescriptionResId = CommonStrings.action_send_message,
|
||||||
|
endButtonClick = {
|
||||||
|
if (canSendTextMessage) {
|
||||||
|
onSendMessage()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endButtonContent = @Composable {
|
||||||
|
SendButtonIcon(
|
||||||
|
canSendMessage = canSendTextMessage,
|
||||||
|
isEditing = false,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val voiceRecording = @Composable {
|
val voiceRecording = @Composable {
|
||||||
when (voiceMessageState) {
|
when (voiceMessageState) {
|
||||||
|
|
@ -307,17 +368,8 @@ fun TextComposer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val voiceDeleteButton = @Composable {
|
|
||||||
when (voiceMessageState) {
|
|
||||||
is VoiceMessageState.Preview ->
|
|
||||||
VoiceMessageDeleteButton(enabled = !voiceMessageState.isSending, onClick = onDeleteVoiceMessage)
|
|
||||||
is VoiceMessageState.Recording ->
|
|
||||||
VoiceMessageDeleteButton(enabled = true, onClick = { onVoiceRecorderEvent(VoiceMessageRecorderEvent.Cancel) })
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showTextFormatting && textFormattingOptions != null) {
|
if (showTextFormatting && textFormattingOptions != null) {
|
||||||
|
val endButtonParams = rememberEndButtonParamsFormatting()
|
||||||
TextFormattingLayout(
|
TextFormattingLayout(
|
||||||
modifier = layoutModifier,
|
modifier = layoutModifier,
|
||||||
isRoomEncrypted = state.isRoomEncrypted,
|
isRoomEncrypted = state.isRoomEncrypted,
|
||||||
|
|
@ -330,20 +382,21 @@ fun TextComposer(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
textFormatting = textFormattingOptions,
|
textFormatting = textFormattingOptions,
|
||||||
endButtonA11y = endButtonA11y,
|
endButtonParams = endButtonParams,
|
||||||
sendButton = sendButton,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
val endButtonParams = rememberEndButtonParams()
|
||||||
StandardLayout(
|
StandardLayout(
|
||||||
|
composerMode = composerMode,
|
||||||
voiceMessageState = voiceMessageState,
|
voiceMessageState = voiceMessageState,
|
||||||
isRoomEncrypted = state.isRoomEncrypted,
|
isRoomEncrypted = state.isRoomEncrypted,
|
||||||
modifier = layoutModifier,
|
modifier = layoutModifier,
|
||||||
composerOptionsButton = composerOptionsButton,
|
|
||||||
textInput = textInput,
|
textInput = textInput,
|
||||||
endButton = sendOrRecordButton,
|
endButtonParams = endButtonParams,
|
||||||
endButtonA11y = endButtonA11y,
|
|
||||||
voiceRecording = voiceRecording,
|
voiceRecording = voiceRecording,
|
||||||
voiceDeleteButton = voiceDeleteButton,
|
onAddAttachment = onAddAttachment,
|
||||||
|
onDeleteVoiceMessage = onDeleteVoiceMessage,
|
||||||
|
onVoiceRecorderEvent = onVoiceRecorderEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,49 +420,23 @@ fun TextComposer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadOnlyComposable
|
private data class EndButtonParams(
|
||||||
@Composable
|
val endButtonContentDescriptionResId: Int,
|
||||||
private fun endButtonA11y(
|
val endButtonClick: () -> Unit,
|
||||||
composerMode: MessageComposerMode,
|
val endButtonContent: @Composable () -> Unit,
|
||||||
voiceMessageState: VoiceMessageState,
|
)
|
||||||
canSendMessage: Boolean,
|
|
||||||
): (SemanticsPropertyReceiver) -> Unit {
|
|
||||||
val a11ySendButtonDescription = stringResource(
|
|
||||||
id = when {
|
|
||||||
!canSendMessage ->
|
|
||||||
when (voiceMessageState) {
|
|
||||||
VoiceMessageState.Idle,
|
|
||||||
is VoiceMessageState.Recording -> if (voiceMessageState is VoiceMessageState.Recording) {
|
|
||||||
CommonStrings.a11y_voice_message_stop_recording
|
|
||||||
} else {
|
|
||||||
CommonStrings.a11y_voice_message_record
|
|
||||||
}
|
|
||||||
is VoiceMessageState.Preview -> when (voiceMessageState.isSending) {
|
|
||||||
true -> CommonStrings.common_sending
|
|
||||||
false -> CommonStrings.action_send_voice_message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
composerMode.isEditing -> CommonStrings.action_send_edited_message
|
|
||||||
else -> CommonStrings.action_send_message
|
|
||||||
}
|
|
||||||
)
|
|
||||||
val endButtonA11y: (SemanticsPropertyReceiver.() -> Unit) = {
|
|
||||||
contentDescription = a11ySendButtonDescription
|
|
||||||
onClick(null, null)
|
|
||||||
}
|
|
||||||
return endButtonA11y
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun StandardLayout(
|
private fun StandardLayout(
|
||||||
|
composerMode: MessageComposerMode,
|
||||||
voiceMessageState: VoiceMessageState,
|
voiceMessageState: VoiceMessageState,
|
||||||
isRoomEncrypted: Boolean?,
|
isRoomEncrypted: Boolean?,
|
||||||
textInput: @Composable () -> Unit,
|
textInput: @Composable () -> Unit,
|
||||||
composerOptionsButton: @Composable () -> Unit,
|
|
||||||
voiceRecording: @Composable () -> Unit,
|
voiceRecording: @Composable () -> Unit,
|
||||||
voiceDeleteButton: @Composable () -> Unit,
|
endButtonParams: EndButtonParams,
|
||||||
endButton: @Composable () -> Unit,
|
onAddAttachment: () -> Unit,
|
||||||
endButtonA11y: (SemanticsPropertyReceiver.() -> Unit),
|
onDeleteVoiceMessage: () -> Unit,
|
||||||
|
onVoiceRecorderEvent: (VoiceMessageRecorderEvent) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
|
|
@ -419,50 +446,80 @@ private fun StandardLayout(
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
}
|
}
|
||||||
Row(verticalAlignment = Alignment.Bottom) {
|
Row(verticalAlignment = Alignment.Bottom) {
|
||||||
if (voiceMessageState !is VoiceMessageState.Idle) {
|
when (composerMode) {
|
||||||
if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) {
|
is MessageComposerMode.Attachment -> {
|
||||||
Box(
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
}
|
||||||
|
is MessageComposerMode.EditCaption -> {
|
||||||
|
Spacer(modifier = Modifier.width(19.dp))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val endPadding = if (voiceMessageState is VoiceMessageState.Idle) 0.dp else 3.dp
|
||||||
|
// To avoid loosing keyboard focus, the IconButton has to be defined here and has to be always enabled.
|
||||||
|
IconButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp)
|
.padding(top = 5.dp, bottom = 5.dp, start = 3.dp, end = endPadding)
|
||||||
.size(48.dp),
|
.size(48.dp),
|
||||||
contentAlignment = Alignment.Center,
|
onClick = {
|
||||||
|
if (voiceMessageState is VoiceMessageState.Idle) {
|
||||||
|
onAddAttachment()
|
||||||
|
} else {
|
||||||
|
when (voiceMessageState) {
|
||||||
|
is VoiceMessageState.Preview -> if (!voiceMessageState.isSending) {
|
||||||
|
onDeleteVoiceMessage()
|
||||||
|
}
|
||||||
|
is VoiceMessageState.Recording ->
|
||||||
|
onVoiceRecorderEvent(VoiceMessageRecorderEvent.Cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
voiceDeleteButton()
|
if (voiceMessageState is VoiceMessageState.Idle) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.size(30.dp)
|
||||||
|
.background(ElementTheme.colors.iconPrimary)
|
||||||
|
.padding(3.dp),
|
||||||
|
imageVector = CompoundIcons.Plus(),
|
||||||
|
contentDescription = stringResource(R.string.rich_text_editor_a11y_add_attachment),
|
||||||
|
tint = ElementTheme.colors.iconOnSolidPrimary
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
when (voiceMessageState) {
|
||||||
|
is VoiceMessageState.Preview ->
|
||||||
|
VoiceMessageDeleteButtonIcon(enabled = !voiceMessageState.isSending)
|
||||||
|
is VoiceMessageState.Recording ->
|
||||||
|
VoiceMessageDeleteButtonIcon(enabled = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
}
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(bottom = 8.dp, top = 8.dp)
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
voiceRecording()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.padding(bottom = 5.dp, top = 5.dp, start = 3.dp)
|
|
||||||
) {
|
|
||||||
composerOptionsButton()
|
|
||||||
}
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(bottom = 8.dp, top = 8.dp)
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
textInput()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
modifier = Modifier
|
||||||
|
.padding(bottom = 8.dp, top = 8.dp)
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
if (voiceMessageState is VoiceMessageState.Idle) {
|
||||||
|
textInput()
|
||||||
|
} else {
|
||||||
|
voiceRecording()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// To avoid loosing keyboard focus, the IconButton has to be defined here and has to be always enabled.
|
||||||
|
val endButtonContentDescription = stringResource(endButtonParams.endButtonContentDescriptionResId)
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier
|
||||||
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
|
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.clearAndSetSemantics(endButtonA11y),
|
.clearAndSetSemantics {
|
||||||
contentAlignment = Alignment.Center,
|
contentDescription = endButtonContentDescription
|
||||||
) {
|
onClick(null, null)
|
||||||
endButton()
|
},
|
||||||
}
|
onClick = endButtonParams.endButtonClick,
|
||||||
|
content = endButtonParams.endButtonContent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -495,8 +552,7 @@ private fun TextFormattingLayout(
|
||||||
textInput: @Composable () -> Unit,
|
textInput: @Composable () -> Unit,
|
||||||
dismissTextFormattingButton: @Composable () -> Unit,
|
dismissTextFormattingButton: @Composable () -> Unit,
|
||||||
textFormatting: @Composable () -> Unit,
|
textFormatting: @Composable () -> Unit,
|
||||||
sendButton: @Composable () -> Unit,
|
endButtonParams: EndButtonParams,
|
||||||
endButtonA11y: (SemanticsPropertyReceiver.() -> Unit),
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -527,16 +583,22 @@ private fun TextFormattingLayout(
|
||||||
Box(modifier = Modifier.weight(1f)) {
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
textFormatting()
|
textFormatting()
|
||||||
}
|
}
|
||||||
Box(
|
// To avoid loosing keyboard focus, the IconButton has to be defined here and has to be always enabled.
|
||||||
|
val endButtonContentDescription = stringResource(endButtonParams.endButtonContentDescriptionResId)
|
||||||
|
IconButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
start = 14.dp,
|
start = 14.dp,
|
||||||
end = 6.dp,
|
end = 6.dp,
|
||||||
)
|
)
|
||||||
.clearAndSetSemantics(endButtonA11y)
|
.size(48.dp)
|
||||||
) {
|
.clearAndSetSemantics {
|
||||||
sendButton()
|
contentDescription = endButtonContentDescription
|
||||||
}
|
onClick(null, null)
|
||||||
|
},
|
||||||
|
onClick = endButtonParams.endButtonClick,
|
||||||
|
content = endButtonParams.endButtonContent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -596,6 +658,12 @@ private fun TextInputBox(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun VoiceMessageState.endButtonKey() = when (this) {
|
||||||
|
is VoiceMessageState.Idle -> "Idle"
|
||||||
|
is VoiceMessageState.Preview -> "Preview_$isSending"
|
||||||
|
is VoiceMessageState.Recording -> "Recording"
|
||||||
|
}
|
||||||
|
|
||||||
private fun aTextEditorStateMarkdownList(isRoomEncrypted: Boolean? = null) = persistentListOf(
|
private fun aTextEditorStateMarkdownList(isRoomEncrypted: Boolean? = null) = persistentListOf(
|
||||||
aTextEditorStateMarkdown(initialText = "", initialFocus = true, isRoomEncrypted = isRoomEncrypted),
|
aTextEditorStateMarkdown(initialText = "", initialFocus = true, isRoomEncrypted = isRoomEncrypted),
|
||||||
aTextEditorStateMarkdown(initialText = "A message", initialFocus = true, isRoomEncrypted = isRoomEncrypted),
|
aTextEditorStateMarkdown(initialText = "A message", initialFocus = true, isRoomEncrypted = isRoomEncrypted),
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
|
||||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send button for the message composer.
|
* Send button for the message composer.
|
||||||
|
|
@ -39,50 +36,42 @@ import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||||
* Temporary Figma : https://www.figma.com/design/Ni6Ii8YKtmXCKYNE90cC67/Timeline-(new)?node-id=2274-39944&m=dev
|
* Temporary Figma : https://www.figma.com/design/Ni6Ii8YKtmXCKYNE90cC67/Timeline-(new)?node-id=2274-39944&m=dev
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SendButton(
|
internal fun SendButtonIcon(
|
||||||
canSendMessage: Boolean,
|
canSendMessage: Boolean,
|
||||||
onClick: () -> Unit,
|
isEditing: Boolean,
|
||||||
composerMode: MessageComposerMode,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
IconButton(
|
val iconVector = when {
|
||||||
|
isEditing -> CompoundIcons.Check()
|
||||||
|
else -> CompoundIcons.SendSolid()
|
||||||
|
}
|
||||||
|
val iconStartPadding = when {
|
||||||
|
isEditing -> 0.dp
|
||||||
|
else -> 2.dp
|
||||||
|
}
|
||||||
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(48.dp),
|
.clip(CircleShape)
|
||||||
onClick = onClick,
|
.size(36.dp)
|
||||||
enabled = canSendMessage,
|
.buttonBackgroundModifier(canSendMessage)
|
||||||
) {
|
) {
|
||||||
val iconVector = when {
|
Icon(
|
||||||
composerMode.isEditing -> CompoundIcons.Check()
|
|
||||||
else -> CompoundIcons.SendSolid()
|
|
||||||
}
|
|
||||||
val iconStartPadding = when {
|
|
||||||
composerMode.isEditing -> 0.dp
|
|
||||||
else -> 2.dp
|
|
||||||
}
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.padding(start = iconStartPadding)
|
||||||
.size(36.dp)
|
.align(Alignment.Center),
|
||||||
.buttonBackgroundModifier(canSendMessage)
|
imageVector = iconVector,
|
||||||
) {
|
// Note: accessibility is managed in TextComposer.
|
||||||
Icon(
|
contentDescription = null,
|
||||||
modifier = Modifier
|
tint = if (canSendMessage) {
|
||||||
.padding(start = iconStartPadding)
|
if (ElementTheme.colors.isLight) {
|
||||||
.align(Alignment.Center),
|
ElementTheme.colors.iconOnSolidPrimary
|
||||||
imageVector = iconVector,
|
|
||||||
// Note: accessibility is managed in TextComposer.
|
|
||||||
contentDescription = null,
|
|
||||||
tint = if (canSendMessage) {
|
|
||||||
if (ElementTheme.colors.isLight) {
|
|
||||||
ElementTheme.colors.iconOnSolidPrimary
|
|
||||||
} else {
|
|
||||||
ElementTheme.colors.iconPrimary
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ElementTheme.colors.iconQuaternary
|
ElementTheme.colors.iconPrimary
|
||||||
}
|
}
|
||||||
)
|
} else {
|
||||||
}
|
ElementTheme.colors.iconQuaternary
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,13 +102,19 @@ private fun Modifier.buttonBackgroundModifier(
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SendButtonPreview() = ElementPreview {
|
internal fun SendButtonIconPreview() = ElementPreview {
|
||||||
val normalMode = MessageComposerMode.Normal
|
|
||||||
val editMode = MessageComposerMode.Edit(EventId("\$id").toEventOrTransactionId(), "")
|
|
||||||
Row {
|
Row {
|
||||||
SendButton(canSendMessage = true, onClick = {}, composerMode = normalMode)
|
IconButton(onClick = {}) {
|
||||||
SendButton(canSendMessage = false, onClick = {}, composerMode = normalMode)
|
SendButtonIcon(canSendMessage = true, isEditing = false)
|
||||||
SendButton(canSendMessage = true, onClick = {}, composerMode = editMode)
|
}
|
||||||
SendButton(canSendMessage = false, onClick = {}, composerMode = editMode)
|
IconButton(onClick = {}) {
|
||||||
|
SendButtonIcon(canSendMessage = false, isEditing = false)
|
||||||
|
}
|
||||||
|
IconButton(onClick = {}) {
|
||||||
|
SendButtonIcon(canSendMessage = true, isEditing = true)
|
||||||
|
}
|
||||||
|
IconButton(onClick = {}) {
|
||||||
|
SendButtonIcon(canSendMessage = false, isEditing = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -23,41 +23,35 @@ import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VoiceMessageDeleteButton(
|
fun VoiceMessageDeleteButtonIcon(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
IconButton(
|
Icon(
|
||||||
modifier = modifier
|
modifier = modifier.size(24.dp),
|
||||||
.size(48.dp),
|
imageVector = CompoundIcons.Delete(),
|
||||||
enabled = enabled,
|
contentDescription = stringResource(CommonStrings.a11y_delete),
|
||||||
onClick = onClick,
|
tint = if (enabled) {
|
||||||
) {
|
ElementTheme.colors.iconCriticalPrimary
|
||||||
Icon(
|
} else {
|
||||||
modifier = Modifier.size(24.dp),
|
ElementTheme.colors.iconDisabled
|
||||||
imageVector = CompoundIcons.Delete(),
|
},
|
||||||
contentDescription = stringResource(CommonStrings.a11y_delete),
|
)
|
||||||
tint = if (enabled) {
|
|
||||||
ElementTheme.colors.iconCriticalPrimary
|
|
||||||
} else {
|
|
||||||
ElementTheme.colors.iconDisabled
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun VoiceMessageDeleteButtonPreview() = ElementPreview {
|
internal fun VoiceMessageDeleteButtonIconPreview() = ElementPreview {
|
||||||
Row {
|
Row {
|
||||||
VoiceMessageDeleteButton(
|
IconButton(onClick = {}) {
|
||||||
enabled = true,
|
VoiceMessageDeleteButtonIcon(
|
||||||
onClick = {},
|
enabled = true,
|
||||||
)
|
)
|
||||||
VoiceMessageDeleteButton(
|
}
|
||||||
enabled = false,
|
IconButton(onClick = {}) {
|
||||||
onClick = {},
|
VoiceMessageDeleteButtonIcon(
|
||||||
)
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,22 +67,12 @@ internal fun VoiceMessagePreview(
|
||||||
.heightIn(26.dp),
|
.heightIn(26.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
if (isPlaying) {
|
PlayerButton(
|
||||||
PlayerButton(
|
type = if (isPlaying) PlayerButtonType.Pause else PlayerButtonType.Play,
|
||||||
type = PlayerButtonType.Pause,
|
onClick = if (isPlaying) onPauseClick else onPlayClick,
|
||||||
onClick = onPauseClick,
|
enabled = isInteractive,
|
||||||
enabled = isInteractive,
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
PlayerButton(
|
|
||||||
type = PlayerButtonType.Play,
|
|
||||||
onClick = onPlayClick,
|
|
||||||
enabled = isInteractive
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = time.formatShort(),
|
text = time.formatShort(),
|
||||||
color = ElementTheme.colors.textSecondary,
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
|
@ -90,9 +80,7 @@ internal fun VoiceMessagePreview(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
WaveformPlaybackView(
|
WaveformPlaybackView(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,8 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
|
@ -25,49 +24,25 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun VoiceMessageRecorderButton(
|
internal fun VoiceMessageRecorderButtonIcon(
|
||||||
isRecording: Boolean,
|
isRecording: Boolean,
|
||||||
onEvent: (VoiceMessageRecorderEvent) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val hapticFeedback = LocalHapticFeedback.current
|
|
||||||
|
|
||||||
val performHapticFeedback = {
|
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
StopButton(
|
StopButton(modifier)
|
||||||
modifier = modifier,
|
|
||||||
onClick = {
|
|
||||||
performHapticFeedback()
|
|
||||||
onEvent(VoiceMessageRecorderEvent.Stop)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
StartButton(
|
StartButton(modifier)
|
||||||
modifier = modifier,
|
|
||||||
onClick = {
|
|
||||||
performHapticFeedback()
|
|
||||||
onEvent(VoiceMessageRecorderEvent.Start)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun StartButton(
|
private fun StartButton(
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) = IconButton(
|
|
||||||
modifier = modifier.size(48.dp),
|
|
||||||
onClick = onClick,
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = modifier.size(24.dp),
|
||||||
imageVector = CompoundIcons.MicOn(),
|
imageVector = CompoundIcons.MicOn(),
|
||||||
// Note: accessibility is managed in TextComposer.
|
// Note: accessibility is managed in TextComposer.
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
|
|
@ -77,41 +52,40 @@ private fun StartButton(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun StopButton(
|
private fun StopButton(
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) = IconButton(
|
|
||||||
modifier = modifier
|
|
||||||
.size(48.dp),
|
|
||||||
onClick = onClick,
|
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
modifier
|
||||||
.size(36.dp)
|
.size(36.dp)
|
||||||
.background(
|
.background(
|
||||||
color = ElementTheme.colors.bgActionPrimaryRest,
|
color = ElementTheme.colors.bgActionPrimaryRest,
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
)
|
),
|
||||||
)
|
contentAlignment = Alignment.Center,
|
||||||
Icon(
|
) {
|
||||||
modifier = Modifier.size(24.dp),
|
Icon(
|
||||||
resourceId = CommonDrawables.ic_stop,
|
modifier = Modifier.size(24.dp),
|
||||||
// Note: accessibility is managed in TextComposer.
|
resourceId = CommonDrawables.ic_stop,
|
||||||
contentDescription = null,
|
// Note: accessibility is managed in TextComposer.
|
||||||
tint = ElementTheme.colors.iconOnSolidPrimary,
|
contentDescription = null,
|
||||||
)
|
tint = ElementTheme.colors.iconOnSolidPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun VoiceMessageRecorderButtonPreview() = ElementPreview {
|
internal fun VoiceMessageRecorderButtonIconPreview() = ElementPreview {
|
||||||
Row {
|
Row {
|
||||||
VoiceMessageRecorderButton(
|
IconButton(onClick = {}) {
|
||||||
isRecording = false,
|
VoiceMessageRecorderButtonIcon(
|
||||||
onEvent = {},
|
isRecording = false,
|
||||||
)
|
)
|
||||||
VoiceMessageRecorderButton(
|
}
|
||||||
isRecording = true,
|
IconButton(onClick = {}) {
|
||||||
onEvent = {},
|
VoiceMessageRecorderButtonIcon(
|
||||||
)
|
isRecording = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:09faeef5f5864f93d81f271a67826acf417b228987c6f918b95b31f91a468cb1
|
oid sha256:74c638ff6c7ea4961af24b176c3f0b2b40ce123c5f058c4f1ba06c6b823cb16f
|
||||||
size 36097
|
size 36090
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:246d4bf81c16bbbcfc82dd23b23bb1471e3d5e078bc7ecaba3c68d2ab3bb75c2
|
oid sha256:8d220ce93b1e7a7a7330ee90f59415a07f49d040ad5c85ff8086b3dba882452d
|
||||||
size 34307
|
size 34313
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:274f54991bbd79c4220d945964caf7fc523ce99a70b748ef992cc15ee030fdf5
|
oid sha256:75d285698529499a08b5a6107bed61de35b30a0e225951fc93edc1c632a5c7ba
|
||||||
size 25124
|
size 25121
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:258643d514aeb7a53788ba2addc53fe46f888193ced2970c73887f9eb1584753
|
oid sha256:77ac0f986e20bea03f10f00a699484c31a7959eba2ffb4764f8c1e71428159ed
|
||||||
size 24039
|
size 24040
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue