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

Moving voice logic to the MessageComposer #4523

Merged
merged 6 commits into from
Nov 22, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import im.vector.app.features.home.PromoteRestrictedViewModel
import im.vector.app.features.home.UnknownDeviceDetectorSharedViewModel
import im.vector.app.features.home.UnreadMessagesSharedViewModel
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel
import im.vector.app.features.home.room.detail.composer.TextComposerViewModel
import im.vector.app.features.home.room.detail.RoomDetailViewModel
import im.vector.app.features.home.room.detail.search.SearchViewModel
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsViewModel
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryViewModel
Expand Down Expand Up @@ -505,8 +505,8 @@ interface MavericksViewModelModule {

@Binds
@IntoMap
@MavericksViewModelKey(TextComposerViewModel::class)
fun textComposerViewModelFactory(factory: TextComposerViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@MavericksViewModelKey(RoomDetailViewModel::class)
fun roomDetailViewModelFactory(factory: RoomDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
Copy link
Member

Choose a reason for hiding this comment

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

This is not replaced by MessageComposerViewModel ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

unfortunately not :( when a view model is not in this module it's because it needs a workaround https://github.com/vector-im/element-android/blob/develop/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt#L161

Usually we can do...

companion object : MavericksViewModelFactory<*ViewModel, *ViewState> by hiltMavericksViewModelFactory()

which is where the MavericksViewModelModule comes in and injects for us

buttttt the voice helpers are marked as ActivityScoped which hilt doesn't let us inject into the ViewModels as they're some form of singleton scoped~, now that the voice helpers have moved to the MessageComposerViewModel we can add the RoomDetailViewModel to the normal way of providing the ViewModels

The workaround involves injecting the assisted factory into the fragment and then using that field to provide the ViewModel

@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: *ViewState): *ViewModel {
  val fragment: *Fragment = (viewModelContext as FragmentViewModelContext).fragment()
  return fragment.factory.create(state)
}


@Binds
@IntoMap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import android.view.View
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.call.conference.ConferenceEvent
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.timeline.Timeline
Expand Down Expand Up @@ -108,12 +107,4 @@ sealed class RoomDetailAction : VectorViewModelAction {
object RemoveAllFailedMessages : RoomDetailAction()

data class RoomUpgradeSuccess(val replacementRoomId: String) : RoomDetailAction()

// Voice Message
object StartRecordingVoiceMessage : RoomDetailAction()
data class EndRecordingVoiceMessage(val isCancelled: Boolean) : RoomDetailAction()
object PauseRecordingVoiceMessage : RoomDetailAction()
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : RoomDetailAction()
object PlayOrPauseRecordingPlayback : RoomDetailAction()
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : RoomDetailAction()
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,23 @@ import androidx.annotation.IdRes
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.flow.chunk
import im.vector.app.core.mvrx.runCatchingToAsync
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.BehaviorDataSource
import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.call.conference.ConferenceEvent
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
import im.vector.app.features.call.conference.JitsiService
Expand All @@ -47,7 +46,6 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
import im.vector.app.features.home.room.detail.composer.VoiceMessageHelper
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
Expand All @@ -56,7 +54,6 @@ import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.voice.VoicePlayerHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
Expand Down Expand Up @@ -116,8 +113,6 @@ class RoomDetailViewModel @AssistedInject constructor(
private val directRoomHelper: DirectRoomHelper,
private val jitsiService: JitsiService,
private val activeConferenceHolder: JitsiActiveConferenceHolder,
private val voiceMessageHelper: VoiceMessageHelper,
private val voicePlayerHelper: VoicePlayerHelper,
timelineFactory: TimelineFactory
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
Expand All @@ -144,22 +139,12 @@ class RoomDetailViewModel @AssistedInject constructor(
private var prepareToEncrypt: Async<Unit> = Uninitialized

@AssistedFactory
interface Factory {
fun create(initialState: RoomDetailViewState): RoomDetailViewModel
interface Factory : MavericksAssistedViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
override fun create(initialState: RoomDetailViewState): RoomDetailViewModel
}

/**
* 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<RoomDetailViewModel, RoomDetailViewState> {

companion object : MavericksViewModelFactory<RoomDetailViewModel, RoomDetailViewState> by hiltMavericksViewModelFactory() {
const val PAGINATION_COUNT = 50

@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this workaround is needed specifically because of the ActivityScoped voice helpers, have removed from here and added to the MessageComposer where it's needed instead

val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.roomDetailViewModelFactory.create(state)
}
}

init {
Expand Down Expand Up @@ -343,12 +328,6 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages()
RoomDetailAction.ResendAll -> handleResendAll()
RoomDetailAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage()
is RoomDetailAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled)
is RoomDetailAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
RoomDetailAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
RoomDetailAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
is RoomDetailAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord)
is RoomDetailAction.RoomUpgradeSuccess -> {
setState {
copy(joinUpgradedRoomAsync = Success(action.replacementRoomId))
Expand Down Expand Up @@ -612,56 +591,6 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}

private fun handleStartRecordingVoiceMessage() {
try {
voiceMessageHelper.startRecording()
} catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.Failure(failure))
}
}

private fun handleEndRecordingVoiceMessage(isCancelled: Boolean) {
voiceMessageHelper.stopPlayback()
if (isCancelled) {
voiceMessageHelper.deleteRecording()
} else {
voiceMessageHelper.stopRecording()?.let { audioType ->
if (audioType.duration > 1000) {
room.sendMedia(audioType.toContentAttachmentData(), false, emptySet())
} else {
voiceMessageHelper.deleteRecording()
}
}
}
}

private fun handlePlayOrPauseVoicePlayback(action: RoomDetailAction.PlayOrPauseVoicePlayback) {
viewModelScope.launch(Dispatchers.IO) {
try {
// Download can fail
val audioFile = session.fileService().downloadFile(action.messageAudioContent)
// Conversion can fail, fallback to the original file in this case and let the player fail for us
val convertedFile = voicePlayerHelper.convertFile(audioFile) ?: audioFile
// Play can fail
voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile)
} catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.Failure(failure))
}
}
}

private fun handlePlayOrPauseRecordingPlayback() {
voiceMessageHelper.startOrPauseRecordingPlayback()
}

private fun handleEndAllVoiceActions(deleteRecord: Boolean) {
voiceMessageHelper.stopAllVoiceActions(deleteRecord)
}

private fun handlePauseRecordingVoiceMessage() {
voiceMessageHelper.pauseRecording()
}

private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled()

fun isMenuItemVisible(@IdRes itemId: Int): Boolean = com.airbnb.mvrx.withState(this) { state ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,24 @@ 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
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent

sealed class TextComposerAction : VectorViewModelAction {
data class SaveDraft(val draft: String) : TextComposerAction()
data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : TextComposerAction()
data class EnterEditMode(val eventId: String, val text: String) : TextComposerAction()
data class EnterQuoteMode(val eventId: String, val text: String) : TextComposerAction()
data class EnterReplyMode(val eventId: String, val text: String) : TextComposerAction()
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 OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : TextComposerAction()
sealed class MessageComposerAction : VectorViewModelAction {
data class SaveDraft(val draft: String) : MessageComposerAction()
data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : MessageComposerAction()
data class EnterEditMode(val eventId: String, val text: String) : MessageComposerAction()
data class EnterQuoteMode(val eventId: String, val text: String) : MessageComposerAction()
data class EnterReplyMode(val eventId: String, val text: String) : MessageComposerAction()
data class EnterRegularMode(val text: String, val fromSharing: Boolean) : MessageComposerAction()
data class UserIsTyping(val isTyping: Boolean) : MessageComposerAction()
data class OnTextChanged(val text: CharSequence) : MessageComposerAction()

// Voice Message
data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction()
object StartRecordingVoiceMessage : MessageComposerAction()
data class EndRecordingVoiceMessage(val isCancelled: Boolean) : MessageComposerAction()
object PauseRecordingVoiceMessage : MessageComposerAction()
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
object PlayOrPauseRecordingPlayback : MessageComposerAction()
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import im.vector.app.databinding.ComposerLayoutBinding
/**
* Encapsulate the timeline composer UX.
*/
class TextComposerView @JvmOverloads constructor(
class MessageComposerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import androidx.annotation.StringRes
import im.vector.app.core.platform.VectorViewEvents
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()
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
Expand All @@ -36,10 +36,12 @@ sealed class TextComposerViewEvents : VectorViewEvents {
data class SlashCommandResultOk(@StringRes val messageRes: Int? = null) : SendMessageResult()
class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()

data class OpenRoomMemberProfile(val userId: String) : TextComposerViewEvents()
data class OpenRoomMemberProfile(val userId: String) : MessageComposerViewEvents()

// TODO Remove
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) : MessageComposerViewEvents()
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 VoicePlaybackOrRecordingFailure is RoomDetailViewEvents.Failure

}
Loading