Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Voice recording UI state in ViewModel #4515

Merged
merged 17 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f0ef9e9
inverting and splitting the voice message view into logic and views
ouchadam Nov 11, 2021
f269055
lifting voice display logic out of the view and to the layer above
ouchadam Nov 11, 2021
40d762c
lifting current recording state out of the view
ouchadam Nov 11, 2021
2ad121e
moving the recording ui state to the textcomposer view model and state
ouchadam Nov 11, 2021
e895dbd
replacing chained ifs with when
ouchadam Nov 17, 2021
9ae03b7
allows locking and cancelling to occur after choosing either option
ouchadam Nov 17, 2021
be685bc
aligning the locked recording view to the send message button without…
ouchadam Nov 18, 2021
dfc67b8
updating the state rather than calling display directly
ouchadam Nov 18, 2021
bf37437
removing no longer needed cancelled status check
ouchadam Nov 18, 2021
734e7df
renaming display function as its updating state, rather than directly…
ouchadam Nov 18, 2021
c5746a5
updating voice view interface method names for consistency
ouchadam Nov 18, 2021
16ca7d5
adding sending of voice message on send pressed
ouchadam Nov 18, 2021
4dbb150
clarifying why we do nothing when the state is locked on voice record…
ouchadam Nov 18, 2021
1afc1b5
separating the cancelled and ended events to make the consumption sim…
ouchadam Nov 18, 2021
7d262eb
removing no longer needed message delete on animation end, we delete …
ouchadam Nov 18, 2021
331bcbf
separating the drag state from the main UI state in order to clarify …
ouchadam Nov 19, 2021
7d0d105
adding changelog entry
ouchadam Nov 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/4515.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Voice recording mic button refactor with small animation tweaks in preparation for voice drafts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasn't 100% sure if the change log entry was needed as this is mostly refactoring (same as #4523)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always OK to have a changelog, if something goes wrong (I hope no!), we keep quickly see in the change history what have been done.

Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ import im.vector.app.features.home.room.detail.composer.TextComposerView
import im.vector.app.features.home.room.detail.composer.TextComposerViewEvents
import im.vector.app.features.home.room.detail.composer.TextComposerViewModel
import im.vector.app.features.home.room.detail.composer.TextComposerViewState
import im.vector.app.features.home.room.detail.composer.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.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.action.EventSharedAction
Expand Down Expand Up @@ -505,7 +506,7 @@ class RoomDetailFragment @Inject constructor(

private fun onCannotRecord() {
// Update the UI, cancel the animation
views.voiceMessageRecorderView.initVoiceRecordingViews()
textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingUiStateChanged(RecordingUiState.None))
}

private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) {
Expand Down Expand Up @@ -692,32 +693,56 @@ class RoomDetailFragment @Inject constructor(
}

private fun setupVoiceMessageView() {
views.voiceMessageRecorderView.voiceMessagePlaybackTracker = voiceMessagePlaybackTracker

voiceMessagePlaybackTracker.track(VoiceMessagePlaybackTracker.RECORDING_ID, views.voiceMessageRecorderView)
views.voiceMessageRecorderView.callback = object : VoiceMessageRecorderView.Callback {
override fun onVoiceRecordingStarted(): Boolean {
return if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) {

override fun onVoiceRecordingStarted() {
if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) {
roomDetailViewModel.handle(RoomDetailAction.StartRecordingVoiceMessage)
textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingStateChanged(true))
vibrate(requireContext())
true
} else {
// Permission dialog is displayed
false
updateRecordingUiState(RecordingUiState.Started)
}
}

override fun onVoiceRecordingEnded(isCancelled: Boolean) {
roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled))
textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingStateChanged(false))
override fun onVoicePlaybackButtonClicked() {
roomDetailViewModel.handle(RoomDetailAction.PlayOrPauseRecordingPlayback)
}

override fun onVoiceRecordingCancelled() {
roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled = true))
updateRecordingUiState(RecordingUiState.Cancelled)
}

override fun onVoiceRecordingLocked() {
updateRecordingUiState(RecordingUiState.Locked)
}

override fun onVoiceRecordingEnded() {
onSendVoiceMessage()
}

override fun onSendVoiceMessage() {
roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled = false))
updateRecordingUiState(RecordingUiState.None)
}

override fun onDeleteVoiceMessage() {
roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled = true))
updateRecordingUiState(RecordingUiState.None)
}

override fun onVoiceRecordingPlaybackModeOn() {
override fun onRecordingLimitReached() {
roomDetailViewModel.handle(RoomDetailAction.PauseRecordingVoiceMessage)
updateRecordingUiState(RecordingUiState.Playback)
}

override fun onVoicePlaybackButtonClicked() {
roomDetailViewModel.handle(RoomDetailAction.PlayOrPauseRecordingPlayback)
override fun onRecordingWaveformClicked() {
roomDetailViewModel.handle(RoomDetailAction.PauseRecordingVoiceMessage)
updateRecordingUiState(RecordingUiState.Playback)
}

private fun updateRecordingUiState(state: RecordingUiState) {
textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingUiStateChanged(state))
}
}
}
Expand Down Expand Up @@ -1109,7 +1134,7 @@ class RoomDetailFragment @Inject constructor(

// We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed.
roomDetailViewModel.handle(RoomDetailAction.EndAllVoiceActions(deleteRecord = false))
views.voiceMessageRecorderView.initVoiceRecordingViews()
views.voiceMessageRecorderView.display(RecordingUiState.None)
}

private val attachmentFileActivityResultLauncher = registerStartForActivityResult {
Expand Down Expand Up @@ -1405,6 +1430,7 @@ class RoomDetailFragment @Inject constructor(
views.composerLayout.isInvisible = !textComposerState.isComposerVisible
views.voiceMessageRecorderView.isVisible = textComposerState.isVoiceMessageRecorderVisible
views.composerLayout.views.sendButton.isInvisible = !textComposerState.isSendButtonVisible
views.voiceMessageRecorderView.display(textComposerState.voiceRecordingUiState)
views.composerLayout.setRoomEncrypted(summary.isEncrypted)
// views.composerLayout.alwaysShowSendButton = false
if (textComposerState.canSendMessage) {
Expand Down Expand Up @@ -1959,7 +1985,7 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
}
is EventSharedAction.Edit -> {
if (!views.voiceMessageRecorderView.isActive()) {
if (withState(textComposerViewModel) { it.isVoiceMessageIdle }) {
textComposerViewModel.handle(TextComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString()))
} else {
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
Expand All @@ -1969,7 +1995,7 @@ class RoomDetailFragment @Inject constructor(
textComposerViewModel.handle(TextComposerAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString()))
}
is EventSharedAction.Reply -> {
if (!views.voiceMessageRecorderView.isActive()) {
if (withState(textComposerViewModel) { it.isVoiceMessageIdle }) {
textComposerViewModel.handle(TextComposerAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString()))
} else {
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail.composer

import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView

sealed class TextComposerAction : VectorViewModelAction {
data class SaveDraft(val draft: String) : TextComposerAction()
Expand All @@ -27,5 +28,5 @@ sealed class TextComposerAction : VectorViewModelAction {
data class EnterRegularMode(val text: String, val fromSharing: Boolean) : TextComposerAction()
data class UserIsTyping(val isTyping: Boolean) : TextComposerAction()
data class OnTextChanged(val text: CharSequence) : TextComposerAction()
data class OnVoiceRecordingStateChanged(val isRecording: Boolean) : TextComposerAction()
data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : TextComposerAction()
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,20 @@ class TextComposerViewModel @AssistedInject constructor(
override fun handle(action: TextComposerAction) {
Timber.v("Handle action: $action")
when (action) {
is TextComposerAction.EnterEditMode -> handleEnterEditMode(action)
is TextComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action)
is TextComposerAction.EnterRegularMode -> handleEnterRegularMode(action)
is TextComposerAction.EnterReplyMode -> handleEnterReplyMode(action)
is TextComposerAction.SaveDraft -> handleSaveDraft(action)
is TextComposerAction.SendMessage -> handleSendMessage(action)
is TextComposerAction.UserIsTyping -> handleUserIsTyping(action)
is TextComposerAction.OnTextChanged -> handleOnTextChanged(action)
is TextComposerAction.OnVoiceRecordingStateChanged -> handleOnVoiceRecordingStateChanged(action)
is TextComposerAction.EnterEditMode -> handleEnterEditMode(action)
is TextComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action)
is TextComposerAction.EnterRegularMode -> handleEnterRegularMode(action)
is TextComposerAction.EnterReplyMode -> handleEnterReplyMode(action)
is TextComposerAction.SaveDraft -> handleSaveDraft(action)
is TextComposerAction.SendMessage -> handleSendMessage(action)
is TextComposerAction.UserIsTyping -> handleUserIsTyping(action)
is TextComposerAction.OnTextChanged -> handleOnTextChanged(action)
is TextComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action)
}
}

private fun handleOnVoiceRecordingStateChanged(action: TextComposerAction.OnVoiceRecordingStateChanged) = setState {
copy(isVoiceRecording = action.isRecording)
private fun handleOnVoiceRecordingUiStateChanged(action: TextComposerAction.OnVoiceRecordingUiStateChanged) = setState {
copy(voiceRecordingUiState = action.uiState)
}

private fun handleOnTextChanged(action: TextComposerAction.OnTextChanged) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.composer

import com.airbnb.mvrx.MavericksState
import im.vector.app.features.home.room.detail.RoomDetailArgs
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent

/**
Expand All @@ -44,13 +45,27 @@ sealed class SendMode(open val text: String) {
data class TextComposerViewState(
val roomId: String,
val canSendMessage: Boolean = true,
val isVoiceRecording: Boolean = false,
val isSendButtonVisible: Boolean = false,
val sendMode: SendMode = SendMode.REGULAR("", false)
val sendMode: SendMode = SendMode.REGULAR("", false),
val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.None
) : MavericksState {

val isVoiceRecording = when (voiceRecordingUiState) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the isRecording and isIdle states are inferred from the UI state

VoiceMessageRecorderView.RecordingUiState.None,
VoiceMessageRecorderView.RecordingUiState.Cancelled,
VoiceMessageRecorderView.RecordingUiState.Playback -> false
VoiceMessageRecorderView.RecordingUiState.Locked,
VoiceMessageRecorderView.RecordingUiState.Started -> true
}

val isVoiceMessageIdle = when (voiceRecordingUiState) {
VoiceMessageRecorderView.RecordingUiState.None, VoiceMessageRecorderView.RecordingUiState.Cancelled -> false
else -> true
}

val isComposerVisible = canSendMessage && !isVoiceRecording
val isVoiceMessageRecorderVisible = canSendMessage && !isSendButtonVisible

@Suppress("UNUSED") // needed by mavericks
constructor(args: RoomDetailArgs) : this(roomId = args.roomId)
}
Loading