1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 00:02:48 +02:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Onuray Sahin
5cb49eac5c Merge branch 'develop' into feature/ons/voice_message_draft 2021-11-01 11:38:16 +03:00
Onuray Sahin
26713c449b Code review fixes. 2021-10-25 10:51:58 +03:00
Onuray Sahin
139c2bfe18 Changelog added. 2021-10-21 13:58:25 +03:00
Onuray Sahin
2e078cac86 Do not create redundant temp files. 2021-10-21 13:55:41 +03:00
Onuray Sahin
b54079ad84 Render draft message. 2021-10-21 10:59:21 +03:00
Onuray Sahin
67f25d1938 Save draft with message type. 2021-10-18 13:28:31 +03:00
Onuray Sahin
950c6f2909 Fix draft entity migration. 2021-10-14 13:14:11 +03:00
Onuray Sahin
b356a5c2ad Extend DraftEntity to be able to handle multiple message types. 2021-10-13 16:29:06 +03:00
17 changed files with 176 additions and 51 deletions

1
changelog.d/3922.feature Normal file
View File

@@ -0,0 +1 @@
Voice messages: Persist drafts of voice messages when navigating between rooms

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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)
}
}
}

View File

@@ -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() {

View File

@@ -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()
}

View File

@@ -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 {

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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()

View File

@@ -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))
}
}
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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()
}

View File

@@ -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) {

View File

@@ -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