update text composer classes to message composer (as they include voice not just text)

This commit is contained in:
Adam Brown 2021-11-19 14:34:42 +00:00
parent 8837640b5a
commit f140dbc0a0
7 changed files with 161 additions and 161 deletions

View File

@ -133,11 +133,11 @@ import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivit
import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.composer.SendMode import im.vector.app.features.home.room.detail.composer.SendMode
import im.vector.app.features.home.room.detail.composer.TextComposerAction import im.vector.app.features.home.room.detail.composer.MessageComposerAction
import im.vector.app.features.home.room.detail.composer.TextComposerView import im.vector.app.features.home.room.detail.composer.MessageComposerView
import im.vector.app.features.home.room.detail.composer.TextComposerViewEvents import im.vector.app.features.home.room.detail.composer.MessageComposerViewEvents
import im.vector.app.features.home.room.detail.composer.TextComposerViewModel import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel
import im.vector.app.features.home.room.detail.composer.TextComposerViewState import im.vector.app.features.home.room.detail.composer.MessageComposerViewState
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
@ -241,7 +241,7 @@ class RoomDetailFragment @Inject constructor(
autoCompleterFactory: AutoCompleter.Factory, autoCompleterFactory: AutoCompleter.Factory,
private val permalinkHandler: PermalinkHandler, private val permalinkHandler: PermalinkHandler,
private val notificationDrawerManager: NotificationDrawerManager, private val notificationDrawerManager: NotificationDrawerManager,
val textComposerViewModelFactory: TextComposerViewModel.Factory, val messageComposerViewModelFactory: MessageComposerViewModel.Factory,
private val eventHtmlRenderer: EventHtmlRenderer, private val eventHtmlRenderer: EventHtmlRenderer,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
@ -294,7 +294,7 @@ class RoomDetailFragment @Inject constructor(
autoCompleterFactory.create(roomDetailArgs.roomId) autoCompleterFactory.create(roomDetailArgs.roomId)
} }
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel() private val messageComposerViewModel: MessageComposerViewModel by fragmentViewModel()
private val debouncer = Debouncer(createUIHandler()) private val debouncer = Debouncer(createUIHandler())
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
@ -387,7 +387,7 @@ class RoomDetailFragment @Inject constructor(
updateJumpToReadMarkerViewVisibility() updateJumpToReadMarkerViewVisibility()
} }
textComposerViewModel.onEach(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage) { mode, canSend -> messageComposerViewModel.onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage) { mode, canSend ->
if (!canSend) { if (!canSend) {
return@onEach return@onEach
} }
@ -412,15 +412,15 @@ class RoomDetailFragment @Inject constructor(
) )
} }
textComposerViewModel.observeViewEvents { messageComposerViewModel.observeViewEvents {
when (it) { when (it) {
is TextComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) is MessageComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
is TextComposerViewEvents.SendMessageResult -> renderSendMessageResult(it) is MessageComposerViewEvents.SendMessageResult -> renderSendMessageResult(it)
is TextComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message) is MessageComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message)
is TextComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it) is MessageComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it)
is TextComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it) is MessageComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it)
is TextComposerViewEvents.OpenRoomMemberProfile -> openRoomMemberProfile(it.userId) is MessageComposerViewEvents.OpenRoomMemberProfile -> openRoomMemberProfile(it.userId)
is TextComposerViewEvents.VoicePlaybackOrRecordingFailure -> { is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> {
if (it.throwable is VoiceFailure.UnableToRecord) { if (it.throwable is VoiceFailure.UnableToRecord) {
onCannotRecord() onCannotRecord()
} }
@ -471,7 +471,7 @@ class RoomDetailFragment @Inject constructor(
} }
} }
private fun handleSendButtonVisibilityChanged(event: TextComposerViewEvents.AnimateSendButtonVisibility) { private fun handleSendButtonVisibilityChanged(event: MessageComposerViewEvents.AnimateSendButtonVisibility) {
if (event.isVisible) { if (event.isVisible) {
views.voiceMessageRecorderView.isVisible = false views.voiceMessageRecorderView.isVisible = false
views.composerLayout.views.sendButton.alpha = 0f views.composerLayout.views.sendButton.alpha = 0f
@ -507,7 +507,7 @@ class RoomDetailFragment @Inject constructor(
private fun onCannotRecord() { private fun onCannotRecord() {
// Update the UI, cancel the animation // Update the UI, cancel the animation
textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingUiStateChanged(RecordingUiState.None)) messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(RecordingUiState.None))
} }
private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) { private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) {
@ -526,7 +526,7 @@ class RoomDetailFragment @Inject constructor(
JoinReplacementRoomBottomSheet().show(childFragmentManager, tag) JoinReplacementRoomBottomSheet().show(childFragmentManager, tag)
} }
private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: TextComposerViewEvents.ShowRoomUpgradeDialog) { private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: MessageComposerViewEvents.ShowRoomUpgradeDialog) {
val tag = MigrateRoomBottomSheet::javaClass.name val tag = MigrateRoomBottomSheet::javaClass.name
MigrateRoomBottomSheet.newInstance(roomDetailArgs.roomId, roomDetailViewEvents.newVersion) MigrateRoomBottomSheet.newInstance(roomDetailArgs.roomId, roomDetailViewEvents.newVersion)
.show(parentFragmentManager, tag) .show(parentFragmentManager, tag)
@ -699,18 +699,18 @@ class RoomDetailFragment @Inject constructor(
override fun onVoiceRecordingStarted() { override fun onVoiceRecordingStarted() {
if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) {
textComposerViewModel.handle(TextComposerAction.StartRecordingVoiceMessage) messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage)
vibrate(requireContext()) vibrate(requireContext())
updateRecordingUiState(RecordingUiState.Started) updateRecordingUiState(RecordingUiState.Started)
} }
} }
override fun onVoicePlaybackButtonClicked() { override fun onVoicePlaybackButtonClicked() {
textComposerViewModel.handle(TextComposerAction.PlayOrPauseRecordingPlayback) messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseRecordingPlayback)
} }
override fun onVoiceRecordingCancelled() { override fun onVoiceRecordingCancelled() {
textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = true)) messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true))
updateRecordingUiState(RecordingUiState.Cancelled) updateRecordingUiState(RecordingUiState.Cancelled)
} }
@ -723,27 +723,27 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onSendVoiceMessage() { override fun onSendVoiceMessage() {
textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = false)) messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false))
updateRecordingUiState(RecordingUiState.None) updateRecordingUiState(RecordingUiState.None)
} }
override fun onDeleteVoiceMessage() { override fun onDeleteVoiceMessage() {
textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = true)) messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true))
updateRecordingUiState(RecordingUiState.None) updateRecordingUiState(RecordingUiState.None)
} }
override fun onRecordingLimitReached() { override fun onRecordingLimitReached() {
textComposerViewModel.handle(TextComposerAction.PauseRecordingVoiceMessage) messageComposerViewModel.handle(MessageComposerAction.PauseRecordingVoiceMessage)
updateRecordingUiState(RecordingUiState.Playback) updateRecordingUiState(RecordingUiState.Playback)
} }
override fun onRecordingWaveformClicked() { override fun onRecordingWaveformClicked() {
textComposerViewModel.handle(TextComposerAction.PauseRecordingVoiceMessage) messageComposerViewModel.handle(MessageComposerAction.PauseRecordingVoiceMessage)
updateRecordingUiState(RecordingUiState.Playback) updateRecordingUiState(RecordingUiState.Playback)
} }
private fun updateRecordingUiState(state: RecordingUiState) { private fun updateRecordingUiState(state: RecordingUiState) {
textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingUiStateChanged(state)) messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(state))
} }
} }
} }
@ -820,7 +820,7 @@ class RoomDetailFragment @Inject constructor(
.show() .show()
} }
private fun handleJoinedToAnotherRoom(action: TextComposerViewEvents.JoinRoomCommandSuccess) { private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) {
views.composerLayout.setTextIfDifferent("") views.composerLayout.setTextIfDifferent("")
lockSendButton = false lockSendButton = false
navigator.openRoom(vectorBaseActivity, action.roomId) navigator.openRoom(vectorBaseActivity, action.roomId)
@ -829,7 +829,7 @@ class RoomDetailFragment @Inject constructor(
private fun handleShareData() { private fun handleShareData() {
when (val sharedData = roomDetailArgs.sharedData) { when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> { is SharedData.Text -> {
textComposerViewModel.handle(TextComposerAction.EnterRegularMode(sharedData.text, fromSharing = true)) messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(sharedData.text, fromSharing = true))
} }
is SharedData.Attachments -> { is SharedData.Attachments -> {
// open share edition // open share edition
@ -1134,10 +1134,10 @@ class RoomDetailFragment @Inject constructor(
notificationDrawerManager.setCurrentRoom(null) notificationDrawerManager.setCurrentRoom(null)
textComposerViewModel.handle(TextComposerAction.SaveDraft(views.composerLayout.text.toString())) messageComposerViewModel.handle(MessageComposerAction.SaveDraft(views.composerLayout.text.toString()))
// We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed.
textComposerViewModel.handle(TextComposerAction.EndAllVoiceActions(deleteRecord = false)) messageComposerViewModel.handle(MessageComposerAction.EndAllVoiceActions(deleteRecord = false))
views.voiceMessageRecorderView.display(RecordingUiState.None) views.voiceMessageRecorderView.display(RecordingUiState.None)
} }
@ -1253,12 +1253,12 @@ class RoomDetailFragment @Inject constructor(
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
(model as? AbsMessageItem)?.attributes?.informationData?.let { (model as? AbsMessageItem)?.attributes?.informationData?.let {
val eventId = it.eventId val eventId = it.eventId
textComposerViewModel.handle(TextComposerAction.EnterReplyMode(eventId, views.composerLayout.text.toString())) messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(eventId, views.composerLayout.text.toString()))
} }
} }
override fun canSwipeModel(model: EpoxyModel<*>): Boolean { override fun canSwipeModel(model: EpoxyModel<*>): Boolean {
val canSendMessage = withState(textComposerViewModel) { val canSendMessage = withState(messageComposerViewModel) {
it.canSendMessage it.canSendMessage
} }
if (!canSendMessage) { if (!canSendMessage) {
@ -1347,7 +1347,7 @@ class RoomDetailFragment @Inject constructor(
views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard()
views.composerLayout.callback = object : TextComposerView.Callback { views.composerLayout.callback = object : MessageComposerView.Callback {
override fun onAddAttachment() { override fun onAddAttachment() {
if (!::attachmentTypeSelector.isInitialized) { if (!::attachmentTypeSelector.isInitialized) {
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment) attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment)
@ -1360,7 +1360,7 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onCloseRelatedMessage() { override fun onCloseRelatedMessage() {
textComposerViewModel.handle(TextComposerAction.EnterRegularMode(views.composerLayout.text.toString(), false)) messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(views.composerLayout.text.toString(), false))
} }
override fun onRichContentSelected(contentUri: Uri): Boolean { override fun onRichContentSelected(contentUri: Uri): Boolean {
@ -1368,7 +1368,7 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onTextChanged(text: CharSequence) { override fun onTextChanged(text: CharSequence) {
textComposerViewModel.handle(TextComposerAction.OnTextChanged(text)) messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text))
} }
} }
} }
@ -1382,7 +1382,7 @@ class RoomDetailFragment @Inject constructor(
// We collapse ASAP, if not there will be a slight annoying delay // We collapse ASAP, if not there will be a slight annoying delay
views.composerLayout.collapse(true) views.composerLayout.collapse(true)
lockSendButton = true lockSendButton = true
textComposerViewModel.handle(TextComposerAction.SendMessage(text, vectorPreferences.isMarkdownEnabled())) messageComposerViewModel.handle(MessageComposerAction.SendMessage(text, vectorPreferences.isMarkdownEnabled()))
emojiPopup.dismiss() emojiPopup.dismiss()
} }
} }
@ -1394,7 +1394,7 @@ class RoomDetailFragment @Inject constructor(
.map { it.isNotEmpty() } .map { it.isNotEmpty() }
.onEach { .onEach {
Timber.d("Typing: User is typing: $it") Timber.d("Typing: User is typing: $it")
textComposerViewModel.handle(TextComposerAction.UserIsTyping(it)) messageComposerViewModel.handle(MessageComposerAction.UserIsTyping(it))
} }
.launchIn(viewLifecycleOwner.lifecycleScope) .launchIn(viewLifecycleOwner.lifecycleScope)
@ -1414,7 +1414,7 @@ class RoomDetailFragment @Inject constructor(
return isHandled return isHandled
} }
override fun invalidate() = withState(roomDetailViewModel, textComposerViewModel) { mainState, textComposerState -> override fun invalidate() = withState(roomDetailViewModel, messageComposerViewModel) { mainState, messageComposerState ->
invalidateOptionsMenu() invalidateOptionsMenu()
val summary = mainState.asyncRoomSummary() val summary = mainState.asyncRoomSummary()
renderToolbar(summary, mainState.formattedTypingUsers) renderToolbar(summary, mainState.formattedTypingUsers)
@ -1431,13 +1431,13 @@ class RoomDetailFragment @Inject constructor(
timelineEventController.update(mainState) timelineEventController.update(mainState)
lazyLoadedViews.inviteView(false)?.isVisible = false lazyLoadedViews.inviteView(false)?.isVisible = false
if (mainState.tombstoneEvent == null) { if (mainState.tombstoneEvent == null) {
views.composerLayout.isInvisible = !textComposerState.isComposerVisible views.composerLayout.isInvisible = !messageComposerState.isComposerVisible
views.voiceMessageRecorderView.isVisible = textComposerState.isVoiceMessageRecorderVisible views.voiceMessageRecorderView.isVisible = messageComposerState.isVoiceMessageRecorderVisible
views.composerLayout.views.sendButton.isInvisible = !textComposerState.isSendButtonVisible views.composerLayout.views.sendButton.isInvisible = !messageComposerState.isSendButtonVisible
views.voiceMessageRecorderView.display(textComposerState.voiceRecordingUiState) views.voiceMessageRecorderView.display(messageComposerState.voiceRecordingUiState)
views.composerLayout.setRoomEncrypted(summary.isEncrypted) views.composerLayout.setRoomEncrypted(summary.isEncrypted)
// views.composerLayout.alwaysShowSendButton = false // views.composerLayout.alwaysShowSendButton = false
if (textComposerState.canSendMessage) { if (messageComposerState.canSendMessage) {
views.notificationAreaView.render(NotificationAreaView.State.Hidden) views.notificationAreaView.render(NotificationAreaView.State.Hidden)
} else { } else {
views.notificationAreaView.render(NotificationAreaView.State.NoPermissionToPost) views.notificationAreaView.render(NotificationAreaView.State.NoPermissionToPost)
@ -1494,27 +1494,27 @@ class RoomDetailFragment @Inject constructor(
} }
} }
private fun renderSendMessageResult(sendMessageResult: TextComposerViewEvents.SendMessageResult) { private fun renderSendMessageResult(sendMessageResult: MessageComposerViewEvents.SendMessageResult) {
when (sendMessageResult) { when (sendMessageResult) {
is TextComposerViewEvents.SlashCommandLoading -> { is MessageComposerViewEvents.SlashCommandLoading -> {
showLoading(null) showLoading(null)
} }
is TextComposerViewEvents.SlashCommandError -> { is MessageComposerViewEvents.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
} }
is TextComposerViewEvents.SlashCommandUnknown -> { is MessageComposerViewEvents.SlashCommandUnknown -> {
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
} }
is TextComposerViewEvents.SlashCommandResultOk -> { is MessageComposerViewEvents.SlashCommandResultOk -> {
dismissLoadingDialog() dismissLoadingDialog()
views.composerLayout.setTextIfDifferent("") views.composerLayout.setTextIfDifferent("")
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
} }
is TextComposerViewEvents.SlashCommandResultError -> { is MessageComposerViewEvents.SlashCommandResultError -> {
dismissLoadingDialog() dismissLoadingDialog()
displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable))
} }
is TextComposerViewEvents.SlashCommandNotImplemented -> { is MessageComposerViewEvents.SlashCommandNotImplemented -> {
displayCommandError(getString(R.string.not_implemented)) displayCommandError(getString(R.string.not_implemented))
} }
} // .exhaustive } // .exhaustive
@ -1884,7 +1884,7 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent) { override fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent) {
textComposerViewModel.handle(TextComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent)) messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
} }
private fun onShareActionClicked(action: EventSharedAction.Share) { private fun onShareActionClicked(action: EventSharedAction.Share) {
@ -1989,18 +1989,18 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
} }
is EventSharedAction.Edit -> { is EventSharedAction.Edit -> {
if (withState(textComposerViewModel) { it.isVoiceMessageIdle }) { if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
textComposerViewModel.handle(TextComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString()))
} else { } else {
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
} }
} }
is EventSharedAction.Quote -> { is EventSharedAction.Quote -> {
textComposerViewModel.handle(TextComposerAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString())) messageComposerViewModel.handle(MessageComposerAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString()))
} }
is EventSharedAction.Reply -> { is EventSharedAction.Reply -> {
if (withState(textComposerViewModel) { it.isVoiceMessageIdle }) { if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
textComposerViewModel.handle(TextComposerAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString()))
} else { } else {
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
} }
@ -2213,7 +2213,7 @@ class RoomDetailFragment @Inject constructor(
override fun onContactAttachmentReady(contactAttachment: ContactAttachment) { override fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
super.onContactAttachmentReady(contactAttachment) super.onContactAttachmentReady(contactAttachment)
val formattedContact = contactAttachment.toHumanReadable() val formattedContact = contactAttachment.toHumanReadable()
textComposerViewModel.handle(TextComposerAction.SendMessage(formattedContact, false)) messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, false))
} }
private fun onViewWidgetsClicked() { private fun onViewWidgetsClicked() {

View File

@ -20,22 +20,22 @@ import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
sealed class TextComposerAction : VectorViewModelAction { sealed class MessageComposerAction : VectorViewModelAction {
data class SaveDraft(val draft: String) : TextComposerAction() data class SaveDraft(val draft: String) : MessageComposerAction()
data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : TextComposerAction() data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : MessageComposerAction()
data class EnterEditMode(val eventId: String, val text: String) : TextComposerAction() data class EnterEditMode(val eventId: String, val text: String) : MessageComposerAction()
data class EnterQuoteMode(val eventId: String, val text: String) : TextComposerAction() data class EnterQuoteMode(val eventId: String, val text: String) : MessageComposerAction()
data class EnterReplyMode(val eventId: String, val text: String) : TextComposerAction() data class EnterReplyMode(val eventId: String, val text: String) : MessageComposerAction()
data class EnterRegularMode(val text: String, val fromSharing: Boolean) : TextComposerAction() data class EnterRegularMode(val text: String, val fromSharing: Boolean) : MessageComposerAction()
data class UserIsTyping(val isTyping: Boolean) : TextComposerAction() data class UserIsTyping(val isTyping: Boolean) : MessageComposerAction()
data class OnTextChanged(val text: CharSequence) : TextComposerAction() data class OnTextChanged(val text: CharSequence) : MessageComposerAction()
// Voice Message // Voice Message
data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : TextComposerAction() data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction()
object StartRecordingVoiceMessage : TextComposerAction() object StartRecordingVoiceMessage : MessageComposerAction()
data class EndRecordingVoiceMessage(val isCancelled: Boolean) : TextComposerAction() data class EndRecordingVoiceMessage(val isCancelled: Boolean) : MessageComposerAction()
object PauseRecordingVoiceMessage : TextComposerAction() object PauseRecordingVoiceMessage : MessageComposerAction()
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : TextComposerAction() data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
object PlayOrPauseRecordingPlayback : TextComposerAction() object PlayOrPauseRecordingPlayback : MessageComposerAction()
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : TextComposerAction() data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
} }

View File

@ -36,7 +36,7 @@ import im.vector.app.databinding.ComposerLayoutBinding
/** /**
* Encapsulate the timeline composer UX. * Encapsulate the timeline composer UX.
*/ */
class TextComposerView @JvmOverloads constructor( class MessageComposerView @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) { defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {

View File

@ -20,13 +20,13 @@ import androidx.annotation.StringRes
import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.features.command.Command import im.vector.app.features.command.Command
sealed class TextComposerViewEvents : VectorViewEvents { sealed class MessageComposerViewEvents : VectorViewEvents {
data class AnimateSendButtonVisibility(val isVisible: Boolean) : TextComposerViewEvents() data class AnimateSendButtonVisibility(val isVisible: Boolean) : MessageComposerViewEvents()
data class ShowMessage(val message: String) : TextComposerViewEvents() data class ShowMessage(val message: String) : MessageComposerViewEvents()
abstract class SendMessageResult : TextComposerViewEvents() abstract class SendMessageResult : MessageComposerViewEvents()
object MessageSent : SendMessageResult() object MessageSent : SendMessageResult()
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
@ -36,12 +36,12 @@ sealed class TextComposerViewEvents : VectorViewEvents {
data class SlashCommandResultOk(@StringRes val messageRes: Int? = null) : SendMessageResult() data class SlashCommandResultOk(@StringRes val messageRes: Int? = null) : SendMessageResult()
class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
data class OpenRoomMemberProfile(val userId: String) : TextComposerViewEvents() data class OpenRoomMemberProfile(val userId: String) : MessageComposerViewEvents()
// TODO Remove // TODO Remove
object SlashCommandNotImplemented : SendMessageResult() object SlashCommandNotImplemented : SendMessageResult()
data class ShowRoomUpgradeDialog(val newVersion: String, val isPublic: Boolean) : TextComposerViewEvents() data class ShowRoomUpgradeDialog(val newVersion: String, val isPublic: Boolean) : MessageComposerViewEvents()
data class VoicePlaybackOrRecordingFailure(val throwable: Throwable) : TextComposerViewEvents() data class VoicePlaybackOrRecordingFailure(val throwable: Throwable) : MessageComposerViewEvents()
} }

View File

@ -58,15 +58,15 @@ import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.CreateSpaceParams
import timber.log.Timber import timber.log.Timber
class TextComposerViewModel @AssistedInject constructor( class MessageComposerViewModel @AssistedInject constructor(
@Assisted initialState: TextComposerViewState, @Assisted initialState: MessageComposerViewState,
private val session: Session, private val session: Session,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val rainbowGenerator: RainbowGenerator, private val rainbowGenerator: RainbowGenerator,
private val voiceMessageHelper: VoiceMessageHelper, private val voiceMessageHelper: VoiceMessageHelper,
private val voicePlayerHelper: VoicePlayerHelper private val voicePlayerHelper: VoicePlayerHelper
) : VectorViewModel<TextComposerViewState, TextComposerAction, TextComposerViewEvents>(initialState) { ) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
@ -79,32 +79,32 @@ class TextComposerViewModel @AssistedInject constructor(
subscribeToStateInternal() subscribeToStateInternal()
} }
override fun handle(action: TextComposerAction) { override fun handle(action: MessageComposerAction) {
Timber.v("Handle action: $action") Timber.v("Handle action: $action")
when (action) { when (action) {
is TextComposerAction.EnterEditMode -> handleEnterEditMode(action) is MessageComposerAction.EnterEditMode -> handleEnterEditMode(action)
is TextComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action) is MessageComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action)
is TextComposerAction.EnterRegularMode -> handleEnterRegularMode(action) is MessageComposerAction.EnterRegularMode -> handleEnterRegularMode(action)
is TextComposerAction.EnterReplyMode -> handleEnterReplyMode(action) is MessageComposerAction.EnterReplyMode -> handleEnterReplyMode(action)
is TextComposerAction.SaveDraft -> handleSaveDraft(action) is MessageComposerAction.SaveDraft -> handleSaveDraft(action)
is TextComposerAction.SendMessage -> handleSendMessage(action) is MessageComposerAction.SendMessage -> handleSendMessage(action)
is TextComposerAction.UserIsTyping -> handleUserIsTyping(action) is MessageComposerAction.UserIsTyping -> handleUserIsTyping(action)
is TextComposerAction.OnTextChanged -> handleOnTextChanged(action) is MessageComposerAction.OnTextChanged -> handleOnTextChanged(action)
is TextComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action) is MessageComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action)
TextComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage() MessageComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage()
is TextComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled) is MessageComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled)
is TextComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action) is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
TextComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage() MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
TextComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback() MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
is TextComposerAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord) is MessageComposerAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord)
} }
} }
private fun handleOnVoiceRecordingUiStateChanged(action: TextComposerAction.OnVoiceRecordingUiStateChanged) = setState { private fun handleOnVoiceRecordingUiStateChanged(action: MessageComposerAction.OnVoiceRecordingUiStateChanged) = setState {
copy(voiceRecordingUiState = action.uiState) copy(voiceRecordingUiState = action.uiState)
} }
private fun handleOnTextChanged(action: TextComposerAction.OnTextChanged) { private fun handleOnTextChanged(action: MessageComposerAction.OnTextChanged) {
setState { setState {
// Makes sure currentComposerText is upToDate when accessing further setState // Makes sure currentComposerText is upToDate when accessing further setState
currentComposerText = action.text currentComposerText = action.text
@ -114,7 +114,7 @@ class TextComposerViewModel @AssistedInject constructor(
} }
private fun subscribeToStateInternal() { private fun subscribeToStateInternal() {
onEach(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ -> onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage, MessageComposerViewState::isVoiceRecording) { _, _, _ ->
updateIsSendButtonVisibility(false) updateIsSendButtonVisibility(false)
} }
} }
@ -122,16 +122,16 @@ class TextComposerViewModel @AssistedInject constructor(
private fun updateIsSendButtonVisibility(triggerAnimation: Boolean) = setState { private fun updateIsSendButtonVisibility(triggerAnimation: Boolean) = setState {
val isSendButtonVisible = isComposerVisible && (sendMode !is SendMode.REGULAR || currentComposerText.isNotBlank()) val isSendButtonVisible = isComposerVisible && (sendMode !is SendMode.REGULAR || currentComposerText.isNotBlank())
if (this.isSendButtonVisible != isSendButtonVisible && triggerAnimation) { if (this.isSendButtonVisible != isSendButtonVisible && triggerAnimation) {
_viewEvents.post(TextComposerViewEvents.AnimateSendButtonVisibility(isSendButtonVisible)) _viewEvents.post(MessageComposerViewEvents.AnimateSendButtonVisibility(isSendButtonVisible))
} }
copy(isSendButtonVisible = isSendButtonVisible) copy(isSendButtonVisible = isSendButtonVisible)
} }
private fun handleEnterRegularMode(action: TextComposerAction.EnterRegularMode) = setState { private fun handleEnterRegularMode(action: MessageComposerAction.EnterRegularMode) = setState {
copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing)) copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing))
} }
private fun handleEnterEditMode(action: TextComposerAction.EnterEditMode) { private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) {
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent())) } setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent())) }
} }
@ -145,19 +145,19 @@ class TextComposerViewModel @AssistedInject constructor(
} }
} }
private fun handleEnterQuoteMode(action: TextComposerAction.EnterQuoteMode) { private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) {
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) } setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) }
} }
} }
private fun handleEnterReplyMode(action: TextComposerAction.EnterReplyMode) { private fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) {
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) } setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) }
} }
} }
private fun handleSendMessage(action: TextComposerAction.SendMessage) { private fun handleSendMessage(action: MessageComposerAction.SendMessage) {
withState { state -> withState { state ->
when (state.sendMode) { when (state.sendMode) {
is SendMode.REGULAR -> { is SendMode.REGULAR -> {
@ -165,22 +165,22 @@ class TextComposerViewModel @AssistedInject constructor(
is ParsedCommand.ErrorNotACommand -> { is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room // Send the text message to the room
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
_viewEvents.post(TextComposerViewEvents.MessageSent) _viewEvents.post(MessageComposerViewEvents.MessageSent)
popDraft() popDraft()
} }
is ParsedCommand.ErrorSyntax -> { is ParsedCommand.ErrorSyntax -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandError(slashCommandResult.command)) _viewEvents.post(MessageComposerViewEvents.SlashCommandError(slashCommandResult.command))
} }
is ParsedCommand.ErrorEmptySlashCommand -> { is ParsedCommand.ErrorEmptySlashCommand -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandUnknown("/")) _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown("/"))
} }
is ParsedCommand.ErrorUnknownSlashCommand -> { is ParsedCommand.ErrorUnknownSlashCommand -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand)) _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand))
} }
is ParsedCommand.SendPlainText -> { is ParsedCommand.SendPlainText -> {
// Send the text message to the room, without markdown // Send the text message to the room, without markdown
room.sendTextMessage(slashCommandResult.message, autoMarkdown = false) room.sendTextMessage(slashCommandResult.message, autoMarkdown = false)
_viewEvents.post(TextComposerViewEvents.MessageSent) _viewEvents.post(MessageComposerViewEvents.MessageSent)
popDraft() popDraft()
} }
is ParsedCommand.ChangeRoomName -> { is ParsedCommand.ChangeRoomName -> {
@ -197,11 +197,11 @@ class TextComposerViewModel @AssistedInject constructor(
} }
is ParsedCommand.ClearScalarToken -> { is ParsedCommand.ClearScalarToken -> {
// TODO // TODO
_viewEvents.post(TextComposerViewEvents.SlashCommandNotImplemented) _viewEvents.post(MessageComposerViewEvents.SlashCommandNotImplemented)
} }
is ParsedCommand.SetMarkdown -> { is ParsedCommand.SetMarkdown -> {
vectorPreferences.setMarkdownEnabled(slashCommandResult.enable) vectorPreferences.setMarkdownEnabled(slashCommandResult.enable)
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk( _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(
if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled))
popDraft() popDraft()
} }
@ -229,21 +229,21 @@ class TextComposerViewModel @AssistedInject constructor(
} }
is ParsedCommand.SendEmote -> { is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown)
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendRainbow -> { is ParsedCommand.SendRainbow -> {
slashCommandResult.message.toString().let { slashCommandResult.message.toString().let {
room.sendFormattedTextMessage(it, rainbowGenerator.generate(it)) room.sendFormattedTextMessage(it, rainbowGenerator.generate(it))
} }
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendRainbowEmote -> { is ParsedCommand.SendRainbowEmote -> {
slashCommandResult.message.toString().let { slashCommandResult.message.toString().let {
room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE) room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE)
} }
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendSpoiler -> { is ParsedCommand.SendSpoiler -> {
@ -251,22 +251,22 @@ class TextComposerViewModel @AssistedInject constructor(
"[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})", "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})",
"<span data-mx-spoiler>${slashCommandResult.message}</span>" "<span data-mx-spoiler>${slashCommandResult.message}</span>"
) )
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendShrug -> { is ParsedCommand.SendShrug -> {
sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message) sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message)
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendLenny -> { is ParsedCommand.SendLenny -> {
sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message) sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message)
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendChatEffect -> { is ParsedCommand.SendChatEffect -> {
sendChatEffect(slashCommandResult) sendChatEffect(slashCommandResult)
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.ChangeTopic -> { is ParsedCommand.ChangeTopic -> {
@ -285,25 +285,25 @@ class TextComposerViewModel @AssistedInject constructor(
handleChangeAvatarForRoomSlashCommand(slashCommandResult) handleChangeAvatarForRoomSlashCommand(slashCommandResult)
} }
is ParsedCommand.ShowUser -> { is ParsedCommand.ShowUser -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
handleWhoisSlashCommand(slashCommandResult) handleWhoisSlashCommand(slashCommandResult)
popDraft() popDraft()
} }
is ParsedCommand.DiscardSession -> { is ParsedCommand.DiscardSession -> {
if (room.isEncrypted()) { if (room.isEncrypted()) {
session.cryptoService().discardOutboundSession(room.roomId) session.cryptoService().discardOutboundSession(room.roomId)
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} else { } else {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
_viewEvents.post( _viewEvents.post(
TextComposerViewEvents MessageComposerViewEvents
.ShowMessage(stringProvider.getString(R.string.command_description_discard_session_not_handled)) .ShowMessage(stringProvider.getString(R.string.command_description_discard_session_not_handled))
) )
} }
} }
is ParsedCommand.CreateSpace -> { is ParsedCommand.CreateSpace -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandLoading) _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val params = CreateSpaceParams().apply { val params = CreateSpaceParams().apply {
@ -319,15 +319,15 @@ class TextComposerViewModel @AssistedInject constructor(
true true
) )
popDraft() popDraft()
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
} }
} }
Unit Unit
} }
is ParsedCommand.AddToSpace -> { is ParsedCommand.AddToSpace -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandLoading) _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
session.spaceService().getSpace(slashCommandResult.spaceId) session.spaceService().getSpace(slashCommandResult.spaceId)
@ -338,22 +338,22 @@ class TextComposerViewModel @AssistedInject constructor(
false false
) )
popDraft() popDraft()
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
} }
} }
Unit Unit
} }
is ParsedCommand.JoinSpace -> { is ParsedCommand.JoinSpace -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandLoading) _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias) session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias)
popDraft() popDraft()
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
} }
} }
Unit Unit
@ -363,21 +363,21 @@ class TextComposerViewModel @AssistedInject constructor(
try { try {
session.getRoom(slashCommandResult.roomId)?.leave(null) session.getRoom(slashCommandResult.roomId)?.leave(null)
popDraft() popDraft()
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
} }
} }
Unit Unit
} }
is ParsedCommand.UpgradeRoom -> { is ParsedCommand.UpgradeRoom -> {
_viewEvents.post( _viewEvents.post(
TextComposerViewEvents.ShowRoomUpgradeDialog( MessageComposerViewEvents.ShowRoomUpgradeDialog(
slashCommandResult.newVersion, slashCommandResult.newVersion,
room.roomSummary()?.isPublic ?: false room.roomSummary()?.isPublic ?: false
) )
) )
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
}.exhaustive }.exhaustive
@ -402,7 +402,7 @@ class TextComposerViewModel @AssistedInject constructor(
Timber.w("Same message content, do not send edition") Timber.w("Same message content, do not send edition")
} }
} }
_viewEvents.post(TextComposerViewEvents.MessageSent) _viewEvents.post(MessageComposerViewEvents.MessageSent)
popDraft() popDraft()
} }
is SendMode.QUOTE -> { is SendMode.QUOTE -> {
@ -423,13 +423,13 @@ class TextComposerViewModel @AssistedInject constructor(
} else { } else {
room.sendFormattedTextMessage(finalText, htmlText) room.sendFormattedTextMessage(finalText, htmlText)
} }
_viewEvents.post(TextComposerViewEvents.MessageSent) _viewEvents.post(MessageComposerViewEvents.MessageSent)
popDraft() popDraft()
} }
is SendMode.REPLY -> { is SendMode.REPLY -> {
state.sendMode.timelineEvent.let { state.sendMode.timelineEvent.let {
room.replyToMessage(it, action.text.toString(), action.autoMarkdown) room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
_viewEvents.post(TextComposerViewEvents.MessageSent) _viewEvents.post(MessageComposerViewEvents.MessageSent)
popDraft() popDraft()
} }
} }
@ -478,7 +478,7 @@ class TextComposerViewModel @AssistedInject constructor(
} }
} }
private fun handleUserIsTyping(action: TextComposerAction.UserIsTyping) { private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) {
if (vectorPreferences.sendTypingNotifs()) { if (vectorPreferences.sendTypingNotifs()) {
if (action.isTyping) { if (action.isTyping) {
room.userIsTyping() room.userIsTyping()
@ -506,13 +506,13 @@ class TextComposerViewModel @AssistedInject constructor(
try { try {
session.joinRoom(command.roomAlias, command.reason, emptyList()) session.joinRoom(command.roomAlias, command.reason, emptyList())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
return@launch return@launch
} }
session.getRoomSummary(command.roomAlias) session.getRoomSummary(command.roomAlias)
?.roomId ?.roomId
?.let { ?.let {
_viewEvents.post(TextComposerViewEvents.JoinRoomCommandSuccess(it)) _viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it))
} }
} }
} }
@ -659,7 +659,7 @@ class TextComposerViewModel @AssistedInject constructor(
} }
private fun handleWhoisSlashCommand(whois: ParsedCommand.ShowUser) { private fun handleWhoisSlashCommand(whois: ParsedCommand.ShowUser) {
_viewEvents.post(TextComposerViewEvents.OpenRoomMemberProfile(whois.userId)) _viewEvents.post(MessageComposerViewEvents.OpenRoomMemberProfile(whois.userId))
} }
private fun sendPrefixedMessage(prefix: String, message: CharSequence) { private fun sendPrefixedMessage(prefix: String, message: CharSequence) {
@ -676,7 +676,7 @@ class TextComposerViewModel @AssistedInject constructor(
/** /**
* Convert a send mode to a draft and save the draft * Convert a send mode to a draft and save the draft
*/ */
private fun handleSaveDraft(action: TextComposerAction.SaveDraft) = withState { private fun handleSaveDraft(action: MessageComposerAction.SaveDraft) = withState {
session.coroutineScope.launch { session.coroutineScope.launch {
when { when {
it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
@ -703,7 +703,7 @@ class TextComposerViewModel @AssistedInject constructor(
try { try {
voiceMessageHelper.startRecording() voiceMessageHelper.startRecording()
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
} }
} }
@ -722,7 +722,7 @@ class TextComposerViewModel @AssistedInject constructor(
} }
} }
private fun handlePlayOrPauseVoicePlayback(action: TextComposerAction.PlayOrPauseVoicePlayback) { private fun handlePlayOrPauseVoicePlayback(action: MessageComposerAction.PlayOrPauseVoicePlayback) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
// Download can fail // Download can fail
@ -732,7 +732,7 @@ class TextComposerViewModel @AssistedInject constructor(
// Play can fail // Play can fail
voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile) voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile)
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
} }
} }
} }
@ -750,14 +750,14 @@ class TextComposerViewModel @AssistedInject constructor(
} }
private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) { private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) {
_viewEvents.post(TextComposerViewEvents.SlashCommandLoading) _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
viewModelScope.launch { viewModelScope.launch {
val event = try { val event = try {
block() block()
popDraft() popDraft()
TextComposerViewEvents.SlashCommandResultOk() MessageComposerViewEvents.SlashCommandResultOk()
} catch (failure: Throwable) { } catch (failure: Throwable) {
TextComposerViewEvents.SlashCommandResultError(failure) MessageComposerViewEvents.SlashCommandResultError(failure)
} }
_viewEvents.post(event) _viewEvents.post(event)
} }
@ -765,18 +765,18 @@ class TextComposerViewModel @AssistedInject constructor(
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(initialState: TextComposerViewState): TextComposerViewModel fun create(initialState: MessageComposerViewState): MessageComposerViewModel
} }
/** /**
* Can't use the hiltMaverick here because some dependencies are injected here and in fragment but they don't share the graph. * Can't use the hiltMaverick here because some dependencies are injected here and in fragment but they don't share the graph.
*/ */
companion object : MavericksViewModelFactory<TextComposerViewModel, TextComposerViewState> { companion object : MavericksViewModelFactory<MessageComposerViewModel, MessageComposerViewState> {
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel { override fun create(viewModelContext: ViewModelContext, state: MessageComposerViewState): MessageComposerViewModel {
val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment() val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.textComposerViewModelFactory.create(state) return fragment.messageComposerViewModelFactory.create(state)
} }
} }
} }

View File

@ -42,7 +42,7 @@ sealed class SendMode(open val text: String) {
data class REPLY(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text) data class REPLY(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
} }
data class TextComposerViewState( data class MessageComposerViewState(
val roomId: String, val roomId: String,
val canSendMessage: Boolean = true, val canSendMessage: Boolean = true,
val isSendButtonVisible: Boolean = false, val isSendButtonVisible: Boolean = false,

View File

@ -199,7 +199,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<im.vector.app.features.home.room.detail.composer.TextComposerView <im.vector.app.features.home.room.detail.composer.MessageComposerView
android:id="@+id/composerLayout" android:id="@+id/composerLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"