From b356a5c2ad75545d812bbd71e60ef4ceeb06e270 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 13 Oct 2021 16:29:06 +0300 Subject: [PATCH 1/7] Extend DraftEntity to be able to handle multiple message types. --- .../sdk/api/session/room/send/UserDraft.kt | 12 ++++++------ .../database/RealmSessionStoreMigration.kt | 13 ++++++++++++- .../internal/database/mapper/DraftMapper.kt | 19 ++++++++++--------- .../internal/database/model/DraftEntity.kt | 4 +++- .../home/room/detail/RoomDetailFragment.kt | 3 ++- .../detail/composer/TextComposerAction.kt | 2 +- .../detail/composer/TextComposerViewModel.kt | 16 ++++++++-------- 7 files changed, 42 insertions(+), 27 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt index 9471b3dbcbb..38cee4c7589 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt @@ -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 } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 05137f81052..42cbdbeed37 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -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 @@ -49,7 +51,7 @@ import timber.log.Timber internal object RealmSessionStoreMigration : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 18L + const val SESSION_STORE_SCHEMA_VERSION = 19L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -72,6 +74,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 16) migrateTo17(realm) if (oldVersion <= 17) migrateTo18(realm) + if (oldVersion <= 18) migrateTo19(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -364,4 +367,12 @@ internal object RealmSessionStoreMigration : RealmMigration { realm.schema.get("RoomMemberSummaryEntity") ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity) } + + private fun migrateTo19(realm: DynamicRealm) { + realm.schema.get("DraftEntity") + ?.addField(DraftEntityFields.MESSAGE_TYPE, String::class.java) + ?.transform { + it.setString(DraftEntityFields.MESSAGE_TYPE, MessageType.MSGTYPE_TEXT) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt index 148f727ba71..26b2ed60f12 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt @@ -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 = "") + 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) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt index 15a5d379631..eb3f0fadc8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt @@ -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() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index e9948e6cf4b..476411b7b91 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -201,6 +201,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 @@ -1090,7 +1091,7 @@ class RoomDetailFragment @Inject constructor( notificationDrawerManager.setCurrentRoom(null) - textComposerViewModel.handle(TextComposerAction.SaveDraft(views.composerLayout.text.toString())) + textComposerViewModel.handle(TextComposerAction.SaveDraft(views.composerLayout.text.toString(), MessageType.MSGTYPE_TEXT)) // 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)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt index 77254001875..c4136457bcd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt @@ -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() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 742d2848a1b..7281eeb2310 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -460,20 +460,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) 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 @@ -671,19 +671,19 @@ class TextComposerViewModel @AssistedInject constructor( when { it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { setState { copy(sendMode = it.sendMode.copy(action.draft)) } - room.saveDraft(UserDraft.REGULAR(action.draft)) + 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)) } } } From 950c6f2909771c18ffd968c3ac995f7548c9162d Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 14 Oct 2021 13:14:11 +0300 Subject: [PATCH 2/7] Fix draft entity migration. --- .../android/sdk/internal/database/RealmSessionStoreMigration.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 42cbdbeed37..4040394be9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -371,6 +371,7 @@ internal object RealmSessionStoreMigration : RealmMigration { private fun migrateTo19(realm: DynamicRealm) { 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) } From 67f25d1938519ce723725ef0edc00daffa913bb8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 18 Oct 2021 13:28:31 +0300 Subject: [PATCH 3/7] Save draft with message type. --- .../home/room/detail/RoomDetailAction.kt | 2 ++ .../home/room/detail/RoomDetailFragment.kt | 23 ++++++++++++++++--- .../home/room/detail/RoomDetailViewEvents.kt | 2 ++ .../home/room/detail/RoomDetailViewModel.kt | 11 +++++++++ .../detail/composer/VoiceMessageHelper.kt | 5 ++-- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index a9b9f8000b6..d0d91e196c9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -116,4 +116,6 @@ sealed class RoomDetailAction : VectorViewModelAction { 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() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 476411b7b91..6a590a6e107 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -458,6 +458,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 } @@ -582,6 +583,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 @@ -1091,10 +1106,12 @@ class RoomDetailFragment @Inject constructor( notificationDrawerManager.setCurrentRoom(null) - textComposerViewModel.handle(TextComposerAction.SaveDraft(views.composerLayout.text.toString(), MessageType.MSGTYPE_TEXT)) + roomDetailViewModel.handle( + RoomDetailAction.OnRoomDetailEntersBackground( + isVoiceMessageActive = views.voiceMessageRecorderView.isActive() + ) + ) - // 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() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index 2e7f2bfd638..86ef67e08b8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 0c0e5ee6cd3..e7d4f9f4a0d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -86,6 +86,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 @@ -352,6 +353,7 @@ class RoomDetailViewModel @AssistedInject constructor( } _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true)) } + is RoomDetailAction.OnRoomDetailEntersBackground -> handleRoomDetailEntersBackground(action.isVoiceMessageActive) }.exhaustive } @@ -655,6 +657,15 @@ class RoomDetailViewModel @AssistedInject constructor( voiceMessageHelper.stopAllVoiceActions(deleteRecord) } + private fun handleRoomDetailEntersBackground(isVoiceMessageActive: Boolean) { + if (isVoiceMessageActive) { + val audioType = voiceMessageHelper.stopAllVoiceActions(deleteRecord = false) + _viewEvents.post(RoomDetailViewEvents.SaveDraft(audioType?.contentUri?.toString(), MessageType.MSGTYPE_AUDIO)) + } else { + _viewEvents.post(RoomDetailViewEvents.SaveDraft(null, MessageType.MSGTYPE_TEXT)) + } + } + private fun handlePauseRecordingVoiceMessage() { voiceMessageHelper.pauseRecording() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index adcd6a3008e..c112f446841 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -217,12 +217,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 } } From b54079ad84ca591ed249f159e83d8f755adebb42 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Oct 2021 10:59:21 +0300 Subject: [PATCH 4/7] Render draft message. --- .../session/content/ContentAttachmentData.kt | 11 +++++++++ .../internal/database/mapper/DraftMapper.kt | 2 +- .../home/room/detail/RoomDetailAction.kt | 1 + .../home/room/detail/RoomDetailFragment.kt | 23 ++++++++++++------- .../home/room/detail/RoomDetailViewModel.kt | 9 +++++++- .../detail/composer/TextComposerViewModel.kt | 4 ++-- .../detail/composer/TextComposerViewState.kt | 2 ++ .../detail/composer/VoiceMessageHelper.kt | 10 ++++++++ .../composer/VoiceMessageRecorderView.kt | 22 ++++++++++++------ .../features/voice/AbstractVoiceRecorder.kt | 17 +++++++++++++- .../app/features/voice/VoiceRecorder.kt | 7 ++++++ 11 files changed, 88 insertions(+), 20 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt index 7ee26de8db3..4ffb816f77e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt @@ -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) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt index 26b2ed60f12..71350ef94fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt @@ -37,7 +37,7 @@ internal object DraftMapper { fun map(domain: UserDraft): DraftEntity { return when (domain) { - is UserDraft.REGULAR -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_REGULAR, 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) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index d0d91e196c9..7edb692e527 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -110,6 +110,7 @@ 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() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 6a590a6e107..c1b2358daae 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -389,7 +389,7 @@ class RoomDetailFragment @Inject constructor( return@onEach } when (mode) { - is SendMode.REGULAR -> renderRegularMode(mode.text) + is SendMode.REGULAR -> renderRegularMode(mode.text, mode.messageType) 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) @@ -1019,11 +1019,20 @@ class RoomDetailFragment @Inject constructor( .show() } - private fun renderRegularMode(text: String) { - autoCompleter.exitSpecialMode() - views.composerLayout.collapse() - views.composerLayout.setTextIfDifferent(text) - views.composerLayout.views.sendButton.contentDescription = getString(R.string.send) + private fun renderRegularMode(content: String, messageType: String) { + if (messageType == MessageType.MSGTYPE_AUDIO) { + ContentAttachmentData.fromJsonString(content)?.let { audioAttachmentData -> + views.voiceMessageRecorderView.isVisible = true + roomDetailViewModel.handle(RoomDetailAction.InitializeVoiceRecorder(audioAttachmentData)) + textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingStateChanged(true)) + views.voiceMessageRecorderView.initVoiceRecordingViews(isInPlaybackMode = true) + } + } else { + autoCompleter.exitSpecialMode() + views.composerLayout.collapse() + views.composerLayout.setTextIfDifferent(content) + views.composerLayout.views.sendButton.contentDescription = getString(R.string.send) + } } private fun renderSpecialMode(event: TimelineEvent, @@ -1111,8 +1120,6 @@ class RoomDetailFragment @Inject constructor( isVoiceMessageActive = views.voiceMessageRecorderView.isActive() ) ) - - views.voiceMessageRecorderView.initVoiceRecordingViews() } private val attachmentFileActivityResultLauncher = registerStartForActivityResult { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index e7d4f9f4a0d..1898bb19e7b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -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 @@ -341,6 +342,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) @@ -611,6 +613,10 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) { + voiceMessageHelper.initializeRecorder(attachmentData) + } + private fun handleStartRecordingVoiceMessage() { try { voiceMessageHelper.startRecording() @@ -660,7 +666,8 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handleRoomDetailEntersBackground(isVoiceMessageActive: Boolean) { if (isVoiceMessageActive) { val audioType = voiceMessageHelper.stopAllVoiceActions(deleteRecord = false) - _viewEvents.post(RoomDetailViewEvents.SaveDraft(audioType?.contentUri?.toString(), MessageType.MSGTYPE_AUDIO)) + val audioJsonString = audioType?.toContentAttachmentData()?.toJsonString() + _viewEvents.post(RoomDetailViewEvents.SaveDraft(audioJsonString, MessageType.MSGTYPE_AUDIO)) } else { _viewEvents.post(RoomDetailViewEvents.SaveDraft(null, MessageType.MSGTYPE_TEXT)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 7281eeb2310..76904b42048 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -460,7 +460,7 @@ class TextComposerViewModel @AssistedInject constructor( copy( // Create a sendMode from a draft and retrieve the TimelineEvent sendMode = when (currentDraft) { - is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.content, false) + is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.content, false, currentDraft.messageType) is UserDraft.QUOTE -> { room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent -> SendMode.QUOTE(timelineEvent, currentDraft.content) @@ -670,7 +670,7 @@ class TextComposerViewModel @AssistedInject constructor( session.coroutineScope.launch { when { it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { - setState { copy(sendMode = it.sendMode.copy(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 -> { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt index 3110aa8dc34..dbd886f9ab2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index c112f446841..6e53a12a1c5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -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,6 +53,15 @@ class VoiceMessageHelper @Inject constructor( private var amplitudeTicker: CountUpTimer? = null private var playbackTicker: CountUpTimer? = null + fun initializeRecorder(attachmentData: ContentAttachmentData) { + voiceRecorder.initializeRecord(attachmentData) + amplitudeList.clear() + attachmentData.waveform?.let { + amplitudeList.addAll(it) + playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList) + } + } + fun startRecording() { stopPlayback() playbackTracker.makeAllPlaybacksIdle() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt index f7b8cead37b..b531a3a5099 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index 8a0f829f949..f01ec329644 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -19,11 +19,13 @@ package im.vector.app.features.voice import android.content.Context import android.media.MediaRecorder import android.os.Build +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.content.ContentAttachmentData import java.io.File import java.io.FileOutputStream abstract class AbstractVoiceRecorder( - context: Context, + private val context: Context, private val filenameExt: String ) : VoiceRecorder { private val outputDirectory = File(context.cacheDir, "voice_records") @@ -50,6 +52,19 @@ abstract class AbstractVoiceRecorder( } } + override fun initializeRecord(attachmentData: ContentAttachmentData) { + outputFile = File.createTempFile("Voice message", ".$filenameExt", outputDirectory) + .also { + tryOrNull { + context.contentResolver.openInputStream(attachmentData.queryUri)?.use { inputStream -> + it.outputStream().use { outputStream -> + inputStream.copyTo(outputStream) + } + } + } + } + } + override fun startRecord() { init() outputFile = File(outputDirectory, "Voice message.$filenameExt") diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt index 17e70997b21..5c8793ecf06 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt @@ -16,9 +16,16 @@ package im.vector.app.features.voice +import org.matrix.android.sdk.api.session.content.ContentAttachmentData import java.io.File interface VoiceRecorder { + /** + * Initialize recording with a pre-recorded file. + * @param attachmentData data of the recorded file + */ + fun initializeRecord(attachmentData: ContentAttachmentData) + /** * Start the recording */ From 2e078cac86df87896492c54c91865af913b0d4bc Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Oct 2021 13:55:41 +0300 Subject: [PATCH 5/7] Do not create redundant temp files. --- .../features/voice/AbstractVoiceRecorder.kt | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index f01ec329644..dbd8a296657 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -19,10 +19,11 @@ package im.vector.app.features.voice import android.content.Context import android.media.MediaRecorder import android.os.Build -import org.matrix.android.sdk.api.extensions.tryOrNull +import im.vector.app.core.intent.getFilenameFromUri import org.matrix.android.sdk.api.session.content.ContentAttachmentData import java.io.File import java.io.FileOutputStream +import java.util.UUID abstract class AbstractVoiceRecorder( private val context: Context, @@ -53,21 +54,15 @@ abstract class AbstractVoiceRecorder( } override fun initializeRecord(attachmentData: ContentAttachmentData) { - outputFile = File.createTempFile("Voice message", ".$filenameExt", outputDirectory) - .also { - tryOrNull { - context.contentResolver.openInputStream(attachmentData.queryUri)?.use { inputStream -> - it.outputStream().use { outputStream -> - inputStream.copyTo(outputStream) - } - } - } - } + getFilenameFromUri(context, attachmentData.queryUri)?.let { + outputFile = File(outputDirectory, it) + } } override fun startRecord() { init() - outputFile = File(outputDirectory, "Voice message.$filenameExt") + val fileName = """${UUID.randomUUID()}.$filenameExt""" + outputFile = File(outputDirectory, fileName) val mr = mediaRecorder ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { From 139c2bfe18a4d6b77c1abb9bc45333797180798e Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Oct 2021 13:58:25 +0300 Subject: [PATCH 6/7] Changelog added. --- changelog.d/3922.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3922.feature diff --git a/changelog.d/3922.feature b/changelog.d/3922.feature new file mode 100644 index 00000000000..bf4e0f7467a --- /dev/null +++ b/changelog.d/3922.feature @@ -0,0 +1 @@ +Voice messages: Persist drafts of voice messages when navigating between rooms \ No newline at end of file From 26713c449b07c5706c30938791d9efc851451e45 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 25 Oct 2021 10:51:58 +0300 Subject: [PATCH 7/7] Code review fixes. --- .../home/room/detail/RoomDetailFragment.kt | 36 +++++++++++-------- .../home/room/detail/RoomDetailViewModel.kt | 4 +-- .../detail/composer/VoiceMessageHelper.kt | 8 ++--- .../features/voice/AbstractVoiceRecorder.kt | 17 ++++++--- .../app/features/voice/VoiceRecorder.kt | 6 ++-- 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index c1b2358daae..60d902a16bd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -389,7 +389,13 @@ class RoomDetailFragment @Inject constructor( return@onEach } when (mode) { - is SendMode.REGULAR -> renderRegularMode(mode.text, mode.messageType) + 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) @@ -468,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 @@ -1019,20 +1034,11 @@ class RoomDetailFragment @Inject constructor( .show() } - private fun renderRegularMode(content: String, messageType: String) { - if (messageType == MessageType.MSGTYPE_AUDIO) { - ContentAttachmentData.fromJsonString(content)?.let { audioAttachmentData -> - views.voiceMessageRecorderView.isVisible = true - roomDetailViewModel.handle(RoomDetailAction.InitializeVoiceRecorder(audioAttachmentData)) - textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingStateChanged(true)) - views.voiceMessageRecorderView.initVoiceRecordingViews(isInPlaybackMode = true) - } - } else { - autoCompleter.exitSpecialMode() - views.composerLayout.collapse() - views.composerLayout.setTextIfDifferent(content) - views.composerLayout.views.sendButton.contentDescription = getString(R.string.send) - } + private fun renderRegularMode(content: String) { + autoCompleter.exitSpecialMode() + views.composerLayout.collapse() + views.composerLayout.setTextIfDifferent(content) + views.composerLayout.views.sendButton.contentDescription = getString(R.string.send) } private fun renderSpecialMode(event: TimelineEvent, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 1898bb19e7b..94811c2915e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -614,12 +614,12 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) { - voiceMessageHelper.initializeRecorder(attachmentData) + voiceMessageHelper.initializeRecorder(room.roomId, attachmentData) } private fun handleStartRecordingVoiceMessage() { try { - voiceMessageHelper.startRecording() + voiceMessageHelper.startRecording(room.roomId) } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.Failure(failure)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index 6e53a12a1c5..c993f485a14 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -53,8 +53,8 @@ class VoiceMessageHelper @Inject constructor( private var amplitudeTicker: CountUpTimer? = null private var playbackTicker: CountUpTimer? = null - fun initializeRecorder(attachmentData: ContentAttachmentData) { - voiceRecorder.initializeRecord(attachmentData) + fun initializeRecorder(roomId: String, attachmentData: ContentAttachmentData) { + voiceRecorder.initializeRecord(roomId, attachmentData) amplitudeList.clear() attachmentData.waveform?.let { amplitudeList.addAll(it) @@ -62,13 +62,13 @@ class VoiceMessageHelper @Inject constructor( } } - fun startRecording() { + 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) diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index dbd8a296657..031c355d85a 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -21,6 +21,7 @@ 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 @@ -53,16 +54,22 @@ abstract class AbstractVoiceRecorder( } } - override fun initializeRecord(attachmentData: ContentAttachmentData) { + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData) { getFilenameFromUri(context, attachmentData.queryUri)?.let { - outputFile = File(outputDirectory, it) + val voiceMessageFolder = File(outputDirectory, roomId.md5()) + outputFile = File(voiceMessageFolder, it) } } - override fun startRecord() { + override fun startRecord(roomId: String) { init() - val fileName = """${UUID.randomUUID()}.$filenameExt""" - outputFile = File(outputDirectory, fileName) + 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) { diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt index 5c8793ecf06..7adc3efbd8a 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt @@ -22,14 +22,16 @@ import java.io.File interface VoiceRecorder { /** * Initialize recording with a pre-recorded file. + * @param roomId room id to initialize draft record * @param attachmentData data of the recorded file */ - fun initializeRecord(attachmentData: ContentAttachmentData) + fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData) /** * Start the recording + * @param roomId id of the room to start record */ - fun startRecord() + fun startRecord(roomId: String) /** * Stop the recording