mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
8 Commits
v1.6.44
...
feature/on
Author | SHA1 | Date | |
---|---|---|---|
|
5cb49eac5c | ||
|
26713c449b | ||
|
139c2bfe18 | ||
|
2e078cac86 | ||
|
b54079ad84 | ||
|
67f25d1938 | ||
|
950c6f2909 | ||
|
b356a5c2ad |
1
changelog.d/3922.feature
Normal file
1
changelog.d/3922.feature
Normal file
@@ -0,0 +1 @@
|
||||
Voice messages: Persist drafts of voice messages when navigating between rooms
|
@@ -22,6 +22,7 @@ import androidx.exifinterface.media.ExifInterface
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
|
||||
@Parcelize
|
||||
@JsonClass(generateAdapter = true)
|
||||
@@ -48,4 +49,14 @@ data class ContentAttachmentData(
|
||||
}
|
||||
|
||||
fun getSafeMimeType() = mimeType?.normalizeMimeType()
|
||||
|
||||
fun toJsonString(): String {
|
||||
return MoshiProvider.providesMoshi().adapter(ContentAttachmentData::class.java).toJson(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromJsonString(json: String): ContentAttachmentData? {
|
||||
return MoshiProvider.providesMoshi().adapter(ContentAttachmentData::class.java).fromJson(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -23,15 +23,15 @@ package org.matrix.android.sdk.api.session.room.send
|
||||
* EDIT: draft of an edition of a message
|
||||
* REPLY: draft of a reply of another message
|
||||
*/
|
||||
sealed class UserDraft(open val text: String) {
|
||||
data class REGULAR(override val text: String) : UserDraft(text)
|
||||
data class QUOTE(val linkedEventId: String, override val text: String) : UserDraft(text)
|
||||
data class EDIT(val linkedEventId: String, override val text: String) : UserDraft(text)
|
||||
data class REPLY(val linkedEventId: String, override val text: String) : UserDraft(text)
|
||||
sealed class UserDraft(open val content: String, open val messageType: String) {
|
||||
data class REGULAR(override val content: String, override val messageType: String) : UserDraft(content, messageType)
|
||||
data class QUOTE(val linkedEventId: String, override val content: String, override val messageType: String) : UserDraft(content, messageType)
|
||||
data class EDIT(val linkedEventId: String, override val content: String, override val messageType: String) : UserDraft(content, messageType)
|
||||
data class REPLY(val linkedEventId: String, override val content: String, override val messageType: String) : UserDraft(content, messageType)
|
||||
|
||||
fun isValid(): Boolean {
|
||||
return when (this) {
|
||||
is REGULAR -> text.isNotBlank()
|
||||
is REGULAR -> content.isNotBlank()
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
@@ -24,8 +24,10 @@ import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.DraftEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
@@ -381,6 +383,13 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
||||
|
||||
private fun migrateTo19(realm: DynamicRealm) {
|
||||
Timber.d("Step 18 -> 19")
|
||||
realm.schema.get("DraftEntity")
|
||||
?.addField(DraftEntityFields.MESSAGE_TYPE, String::class.java)
|
||||
?.setRequired(DraftEntityFields.MESSAGE_TYPE, true)
|
||||
?.transform {
|
||||
it.setString(DraftEntityFields.MESSAGE_TYPE, MessageType.MSGTYPE_TEXT)
|
||||
}
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, String::class.java)
|
||||
?.transform {
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.database.mapper
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||
import org.matrix.android.sdk.internal.database.model.DraftEntity
|
||||
|
||||
@@ -26,20 +27,20 @@ internal object DraftMapper {
|
||||
|
||||
fun map(entity: DraftEntity): UserDraft {
|
||||
return when (entity.draftMode) {
|
||||
DraftEntity.MODE_REGULAR -> UserDraft.REGULAR(entity.content)
|
||||
DraftEntity.MODE_EDIT -> UserDraft.EDIT(entity.linkedEventId, entity.content)
|
||||
DraftEntity.MODE_QUOTE -> UserDraft.QUOTE(entity.linkedEventId, entity.content)
|
||||
DraftEntity.MODE_REPLY -> UserDraft.REPLY(entity.linkedEventId, entity.content)
|
||||
DraftEntity.MODE_REGULAR -> UserDraft.REGULAR(entity.content, entity.messageType)
|
||||
DraftEntity.MODE_EDIT -> UserDraft.EDIT(entity.linkedEventId, entity.content, entity.messageType)
|
||||
DraftEntity.MODE_QUOTE -> UserDraft.QUOTE(entity.linkedEventId, entity.content, entity.messageType)
|
||||
DraftEntity.MODE_REPLY -> UserDraft.REPLY(entity.linkedEventId, entity.content, entity.messageType)
|
||||
else -> null
|
||||
} ?: UserDraft.REGULAR("")
|
||||
} ?: UserDraft.REGULAR("", MessageType.MSGTYPE_TEXT)
|
||||
}
|
||||
|
||||
fun map(domain: UserDraft): DraftEntity {
|
||||
return when (domain) {
|
||||
is UserDraft.REGULAR -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "")
|
||||
is UserDraft.EDIT -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
|
||||
is UserDraft.QUOTE -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
|
||||
is UserDraft.REPLY -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
|
||||
is UserDraft.REGULAR -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "", messageType = domain.messageType)
|
||||
is UserDraft.EDIT -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
|
||||
is UserDraft.QUOTE -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
|
||||
is UserDraft.REPLY -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,10 +17,12 @@
|
||||
package org.matrix.android.sdk.internal.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
|
||||
internal open class DraftEntity(var content: String = "",
|
||||
var draftMode: String = MODE_REGULAR,
|
||||
var linkedEventId: String = ""
|
||||
var linkedEventId: String = "",
|
||||
var messageType: String = MessageType.MSGTYPE_TEXT
|
||||
|
||||
) : RealmObject() {
|
||||
|
||||
|
@@ -110,10 +110,13 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||
data class RoomUpgradeSuccess(val replacementRoomId: String) : RoomDetailAction()
|
||||
|
||||
// Voice Message
|
||||
data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : RoomDetailAction()
|
||||
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()
|
||||
|
||||
data class OnRoomDetailEntersBackground(val isVoiceMessageActive: Boolean) : RoomDetailAction()
|
||||
}
|
||||
|
@@ -202,6 +202,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
|
||||
@@ -388,7 +389,13 @@ class RoomDetailFragment @Inject constructor(
|
||||
return@onEach
|
||||
}
|
||||
when (mode) {
|
||||
is SendMode.REGULAR -> renderRegularMode(mode.text)
|
||||
is SendMode.REGULAR -> {
|
||||
if (mode.messageType == MessageType.MSGTYPE_AUDIO) {
|
||||
renderVoiceMessageMode(mode.text)
|
||||
} else {
|
||||
renderRegularMode(mode.text)
|
||||
}
|
||||
}
|
||||
is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text)
|
||||
is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text)
|
||||
is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text)
|
||||
@@ -457,6 +464,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects()
|
||||
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
|
||||
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
|
||||
is RoomDetailViewEvents.SaveDraft -> handleSaveDraft(it.defaultContent, it.messageType)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@@ -466,6 +474,15 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderVoiceMessageMode(content: String) {
|
||||
ContentAttachmentData.fromJsonString(content)?.let { audioAttachmentData ->
|
||||
views.voiceMessageRecorderView.isVisible = true
|
||||
roomDetailViewModel.handle(RoomDetailAction.InitializeVoiceRecorder(audioAttachmentData))
|
||||
textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingStateChanged(true))
|
||||
views.voiceMessageRecorderView.initVoiceRecordingViews(isInPlaybackMode = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendButtonVisibilityChanged(event: TextComposerViewEvents.AnimateSendButtonVisibility) {
|
||||
if (event.isVisible) {
|
||||
views.voiceMessageRecorderView.isVisible = false
|
||||
@@ -581,6 +598,20 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSaveDraft(defaultContent: String?, messageType: String) {
|
||||
if (messageType == MessageType.MSGTYPE_AUDIO) {
|
||||
defaultContent?.let {
|
||||
textComposerViewModel.handle(
|
||||
TextComposerAction.SaveDraft(it, MessageType.MSGTYPE_AUDIO)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
textComposerViewModel.handle(
|
||||
TextComposerAction.SaveDraft(views.composerLayout.text.toString(), MessageType.MSGTYPE_TEXT)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
|
||||
val tag = RoomWidgetPermissionBottomSheet::class.java.name
|
||||
val dFrag = childFragmentManager.findFragmentByTag(tag) as? RoomWidgetPermissionBottomSheet
|
||||
@@ -1015,10 +1046,10 @@ class RoomDetailFragment @Inject constructor(
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun renderRegularMode(text: String) {
|
||||
private fun renderRegularMode(content: String) {
|
||||
autoCompleter.exitSpecialMode()
|
||||
views.composerLayout.collapse()
|
||||
views.composerLayout.setTextIfDifferent(text)
|
||||
views.composerLayout.setTextIfDifferent(content)
|
||||
views.composerLayout.views.sendButton.contentDescription = getString(R.string.send)
|
||||
}
|
||||
|
||||
@@ -1102,11 +1133,11 @@ class RoomDetailFragment @Inject constructor(
|
||||
|
||||
notificationDrawerManager.setCurrentRoom(null)
|
||||
|
||||
textComposerViewModel.handle(TextComposerAction.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.
|
||||
roomDetailViewModel.handle(RoomDetailAction.EndAllVoiceActions(deleteRecord = false))
|
||||
views.voiceMessageRecorderView.initVoiceRecordingViews()
|
||||
roomDetailViewModel.handle(
|
||||
RoomDetailAction.OnRoomDetailEntersBackground(
|
||||
isVoiceMessageActive = views.voiceMessageRecorderView.isActive()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private val attachmentFileActivityResultLauncher = registerStartForActivityResult {
|
||||
|
@@ -81,4 +81,6 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||
data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents()
|
||||
object StopChatEffects : RoomDetailViewEvents()
|
||||
object RoomReplacementStarted : RoomDetailViewEvents()
|
||||
|
||||
data class SaveDraft(val defaultContent: String? = null, val messageType: String) : RoomDetailViewEvents()
|
||||
}
|
||||
|
@@ -72,6 +72,7 @@ import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||
@@ -86,6 +87,7 @@ import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||
import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
@@ -342,6 +344,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
|
||||
RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages()
|
||||
RoomDetailAction.ResendAll -> handleResendAll()
|
||||
is RoomDetailAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(action.attachmentData)
|
||||
RoomDetailAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage()
|
||||
is RoomDetailAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled)
|
||||
is RoomDetailAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
|
||||
@@ -354,6 +357,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
_viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
|
||||
}
|
||||
is RoomDetailAction.OnRoomDetailEntersBackground -> handleRoomDetailEntersBackground(action.isVoiceMessageActive)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@@ -611,9 +615,13 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) {
|
||||
voiceMessageHelper.initializeRecorder(room.roomId, attachmentData)
|
||||
}
|
||||
|
||||
private fun handleStartRecordingVoiceMessage() {
|
||||
try {
|
||||
voiceMessageHelper.startRecording()
|
||||
voiceMessageHelper.startRecording(room.roomId)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.Failure(failure))
|
||||
}
|
||||
@@ -657,6 +665,16 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
voiceMessageHelper.stopAllVoiceActions(deleteRecord)
|
||||
}
|
||||
|
||||
private fun handleRoomDetailEntersBackground(isVoiceMessageActive: Boolean) {
|
||||
if (isVoiceMessageActive) {
|
||||
val audioType = voiceMessageHelper.stopAllVoiceActions(deleteRecord = false)
|
||||
val audioJsonString = audioType?.toContentAttachmentData()?.toJsonString()
|
||||
_viewEvents.post(RoomDetailViewEvents.SaveDraft(audioJsonString, MessageType.MSGTYPE_AUDIO))
|
||||
} else {
|
||||
_viewEvents.post(RoomDetailViewEvents.SaveDraft(null, MessageType.MSGTYPE_TEXT))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePauseRecordingVoiceMessage() {
|
||||
voiceMessageHelper.pauseRecording()
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.composer
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class TextComposerAction : VectorViewModelAction {
|
||||
data class SaveDraft(val draft: String) : TextComposerAction()
|
||||
data class SaveDraft(val draft: String, val messageType: 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()
|
||||
|
@@ -451,20 +451,20 @@ class TextComposerViewModel @AssistedInject constructor(
|
||||
copy(
|
||||
// Create a sendMode from a draft and retrieve the TimelineEvent
|
||||
sendMode = when (currentDraft) {
|
||||
is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.text, false)
|
||||
is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.content, false, currentDraft.messageType)
|
||||
is UserDraft.QUOTE -> {
|
||||
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.QUOTE(timelineEvent, currentDraft.text)
|
||||
SendMode.QUOTE(timelineEvent, currentDraft.content)
|
||||
}
|
||||
}
|
||||
is UserDraft.REPLY -> {
|
||||
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.REPLY(timelineEvent, currentDraft.text)
|
||||
SendMode.REPLY(timelineEvent, currentDraft.content)
|
||||
}
|
||||
}
|
||||
is UserDraft.EDIT -> {
|
||||
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
|
||||
SendMode.EDIT(timelineEvent, currentDraft.text)
|
||||
SendMode.EDIT(timelineEvent, currentDraft.content)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
@@ -675,20 +675,20 @@ class TextComposerViewModel @AssistedInject constructor(
|
||||
session.coroutineScope.launch {
|
||||
when {
|
||||
it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
|
||||
setState { copy(sendMode = it.sendMode.copy(action.draft)) }
|
||||
room.saveDraft(UserDraft.REGULAR(action.draft))
|
||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft, messageType = action.messageType)) }
|
||||
room.saveDraft(UserDraft.REGULAR(action.draft, action.messageType))
|
||||
}
|
||||
it.sendMode is SendMode.REPLY -> {
|
||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||
room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft))
|
||||
room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft, action.messageType))
|
||||
}
|
||||
it.sendMode is SendMode.QUOTE -> {
|
||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||
room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft))
|
||||
room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft, action.messageType))
|
||||
}
|
||||
it.sendMode is SendMode.EDIT -> {
|
||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||
room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft))
|
||||
room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft, action.messageType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
|
||||
/**
|
||||
@@ -32,6 +33,7 @@ sealed class SendMode(open val text: String) {
|
||||
data class REGULAR(
|
||||
override val text: String,
|
||||
val fromSharing: Boolean,
|
||||
val messageType: String = MessageType.MSGTYPE_TEXT,
|
||||
// This is necessary for forcing refresh on selectSubscribe
|
||||
private val ts: Long = System.currentTimeMillis()
|
||||
) : SendMode(text)
|
||||
|
@@ -30,6 +30,7 @@ import im.vector.lib.multipicker.entity.MultiPickerAudioType
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
@@ -52,13 +53,22 @@ class VoiceMessageHelper @Inject constructor(
|
||||
private var amplitudeTicker: CountUpTimer? = null
|
||||
private var playbackTicker: CountUpTimer? = null
|
||||
|
||||
fun startRecording() {
|
||||
fun initializeRecorder(roomId: String, attachmentData: ContentAttachmentData) {
|
||||
voiceRecorder.initializeRecord(roomId, attachmentData)
|
||||
amplitudeList.clear()
|
||||
attachmentData.waveform?.let {
|
||||
amplitudeList.addAll(it)
|
||||
playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList)
|
||||
}
|
||||
}
|
||||
|
||||
fun startRecording(roomId: String) {
|
||||
stopPlayback()
|
||||
playbackTracker.makeAllPlaybacksIdle()
|
||||
amplitudeList.clear()
|
||||
|
||||
try {
|
||||
voiceRecorder.startRecord()
|
||||
voiceRecorder.startRecord(roomId)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Unable to start recording")
|
||||
throw VoiceFailure.UnableToRecord(failure)
|
||||
@@ -217,12 +227,13 @@ class VoiceMessageHelper @Inject constructor(
|
||||
playbackTicker = null
|
||||
}
|
||||
|
||||
fun stopAllVoiceActions(deleteRecord: Boolean = true) {
|
||||
stopRecording()
|
||||
fun stopAllVoiceActions(deleteRecord: Boolean = true): MultiPickerAudioType? {
|
||||
val audioType = stopRecording()
|
||||
stopPlayback()
|
||||
if (deleteRecord) {
|
||||
deleteRecording()
|
||||
}
|
||||
playbackTracker.clear()
|
||||
return audioType
|
||||
}
|
||||
}
|
||||
|
@@ -109,16 +109,22 @@ class VoiceMessageRecorderView : ConstraintLayout, VoiceMessagePlaybackTracker.L
|
||||
}
|
||||
}
|
||||
|
||||
fun initVoiceRecordingViews() {
|
||||
recordingState = RecordingState.NONE
|
||||
fun initVoiceRecordingViews(isInPlaybackMode: Boolean = false) {
|
||||
if (isInPlaybackMode) {
|
||||
recordingState = RecordingState.PLAYBACK
|
||||
|
||||
hideRecordingViews(null)
|
||||
stopRecordingTicker()
|
||||
showPlaybackViews()
|
||||
} else {
|
||||
recordingState = RecordingState.NONE
|
||||
|
||||
views.voiceMessageMicButton.isVisible = true
|
||||
views.voiceMessageSendButton.isVisible = false
|
||||
hideRecordingViews(null)
|
||||
stopRecordingTicker()
|
||||
|
||||
views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.recreate() }
|
||||
views.voiceMessageMicButton.isVisible = true
|
||||
views.voiceMessageSendButton.isVisible = false
|
||||
|
||||
views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.recreate() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun initListeners() {
|
||||
@@ -509,8 +515,10 @@ class VoiceMessageRecorderView : ConstraintLayout, VoiceMessagePlaybackTracker.L
|
||||
}
|
||||
|
||||
private fun showPlaybackViews() {
|
||||
views.voiceMessagePlaybackLayout.isVisible = true
|
||||
views.voiceMessagePlaybackTimerIndicator.isVisible = false
|
||||
views.voicePlaybackControlButton.isVisible = true
|
||||
views.voiceMessageSendButton.isVisible = true
|
||||
views.voicePlaybackWaveform.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
callback?.onVoiceRecordingPlaybackModeOn()
|
||||
}
|
||||
|
@@ -19,11 +19,15 @@ package im.vector.app.features.voice
|
||||
import android.content.Context
|
||||
import android.media.MediaRecorder
|
||||
import android.os.Build
|
||||
import im.vector.app.core.intent.getFilenameFromUri
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import org.matrix.android.sdk.internal.util.md5
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
abstract class AbstractVoiceRecorder(
|
||||
context: Context,
|
||||
private val context: Context,
|
||||
private val filenameExt: String
|
||||
) : VoiceRecorder {
|
||||
private val outputDirectory: File by lazy {
|
||||
@@ -48,9 +52,22 @@ abstract class AbstractVoiceRecorder(
|
||||
}
|
||||
}
|
||||
|
||||
override fun startRecord() {
|
||||
override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData) {
|
||||
getFilenameFromUri(context, attachmentData.queryUri)?.let {
|
||||
val voiceMessageFolder = File(outputDirectory, roomId.md5())
|
||||
outputFile = File(voiceMessageFolder, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun startRecord(roomId: String) {
|
||||
init()
|
||||
outputFile = File(outputDirectory, "Voice message.$filenameExt")
|
||||
val fileName = "Voice message.$filenameExt"
|
||||
val outputDirectoryForRoom = File(outputDirectory, roomId.md5()).apply {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
}
|
||||
outputFile = File(outputDirectoryForRoom, fileName)
|
||||
|
||||
val mr = mediaRecorder ?: return
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
@@ -16,13 +16,22 @@
|
||||
|
||||
package im.vector.app.features.voice
|
||||
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import java.io.File
|
||||
|
||||
interface VoiceRecorder {
|
||||
/**
|
||||
* Start the recording
|
||||
* Initialize recording with a pre-recorded file.
|
||||
* @param roomId room id to initialize draft record
|
||||
* @param attachmentData data of the recorded file
|
||||
*/
|
||||
fun startRecord()
|
||||
fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData)
|
||||
|
||||
/**
|
||||
* Start the recording
|
||||
* @param roomId id of the room to start record
|
||||
*/
|
||||
fun startRecord(roomId: String)
|
||||
|
||||
/**
|
||||
* Stop the recording
|
||||
|
Reference in New Issue
Block a user