mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-05-03 15:26:27 +00:00
Added voice recording, have to do voice record player
This commit is contained in:
parent
3071c079ba
commit
80fe93c6c4
5 changed files with 322 additions and 31 deletions
|
|
@ -93,6 +93,12 @@ class CorePreferences @UiThread constructor(private val context: Context) {
|
||||||
config.setBool("app", "auto_start_call_record", value)
|
config.setBool("app", "auto_start_call_record", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Voice Recordings */
|
||||||
|
|
||||||
|
var voiceRecordingMaxDuration: Int
|
||||||
|
get() = config.getInt("app", "voice_recording_max_duration", 600000) // in ms
|
||||||
|
set(value) = config.setInt("app", "voice_recording_max_duration", value)
|
||||||
|
|
||||||
/** -1 means auto, 0 no, 1 yes */
|
/** -1 means auto, 0 no, 1 yes */
|
||||||
@get:WorkerThread @set:WorkerThread
|
@get:WorkerThread @set:WorkerThread
|
||||||
var darkMode: Int
|
var darkMode: Int
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,27 @@ import androidx.annotation.WorkerThread
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.media.AudioFocusRequestCompat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
|
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||||
import org.linphone.core.ChatMessage
|
import org.linphone.core.ChatMessage
|
||||||
import org.linphone.core.ChatRoom
|
import org.linphone.core.ChatRoom
|
||||||
import org.linphone.core.ChatRoomListenerStub
|
import org.linphone.core.ChatRoomListenerStub
|
||||||
import org.linphone.core.EventLog
|
import org.linphone.core.EventLog
|
||||||
import org.linphone.core.Factory
|
import org.linphone.core.Factory
|
||||||
|
import org.linphone.core.Recorder
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.ui.main.chat.model.ChatMessageModel
|
import org.linphone.ui.main.chat.model.ChatMessageModel
|
||||||
import org.linphone.ui.main.chat.model.FileModel
|
import org.linphone.ui.main.chat.model.FileModel
|
||||||
import org.linphone.ui.main.chat.model.ParticipantModel
|
import org.linphone.ui.main.chat.model.ParticipantModel
|
||||||
|
import org.linphone.utils.AudioRouteUtils
|
||||||
import org.linphone.utils.Event
|
import org.linphone.utils.Event
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
import org.linphone.utils.LinphoneUtils
|
import org.linphone.utils.LinphoneUtils
|
||||||
|
|
@ -62,8 +72,14 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
val isReplyingToMessage = MutableLiveData<String>()
|
val isReplyingToMessage = MutableLiveData<String>()
|
||||||
|
|
||||||
|
val voiceRecording = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
val voiceRecordingInProgress = MutableLiveData<Boolean>()
|
val voiceRecordingInProgress = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val formattedVoiceRecordingDuration = MutableLiveData<String>()
|
||||||
|
|
||||||
|
val isPlayingVoiceRecord = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
val requestKeyboardHidingEvent: MutableLiveData<Event<Boolean>> by lazy {
|
val requestKeyboardHidingEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||||
MutableLiveData<Event<Boolean>>()
|
MutableLiveData<Event<Boolean>>()
|
||||||
}
|
}
|
||||||
|
|
@ -80,6 +96,10 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
private var chatMessageToReplyTo: ChatMessage? = null
|
private var chatMessageToReplyTo: ChatMessage? = null
|
||||||
|
|
||||||
|
private lateinit var voiceMessageRecorder: Recorder
|
||||||
|
|
||||||
|
private var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null
|
||||||
|
|
||||||
private val chatRoomListener = object : ChatRoomListenerStub() {
|
private val chatRoomListener = object : ChatRoomListenerStub() {
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) {
|
override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||||
|
|
@ -94,6 +114,7 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
isEmojiPickerOpen.value = false
|
isEmojiPickerOpen.value = false
|
||||||
|
isPlayingVoiceRecord.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
|
|
@ -105,6 +126,12 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (::voiceMessageRecorder.isInitialized) {
|
||||||
|
if (voiceMessageRecorder.state != Recorder.State.Closed) {
|
||||||
|
voiceMessageRecorder.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
coreContext.postOnCoreThread {
|
coreContext.postOnCoreThread {
|
||||||
if (::chatRoom.isInitialized) {
|
if (::chatRoom.isInitialized) {
|
||||||
chatRoom.removeListener(chatRoomListener)
|
chatRoom.removeListener(chatRoomListener)
|
||||||
|
|
@ -169,26 +196,40 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
message.addUtf8TextContent(toSend)
|
message.addUtf8TextContent(toSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (attachment in attachments.value.orEmpty()) {
|
if (voiceRecording.value == true && voiceMessageRecorder.file != null) {
|
||||||
val content = Factory.instance().createContent()
|
stopVoiceRecorder()
|
||||||
|
val content = voiceMessageRecorder.createContent()
|
||||||
content.type = when (attachment.mimeType) {
|
if (content != null) {
|
||||||
FileUtils.MimeType.Image -> "image"
|
Log.i(
|
||||||
FileUtils.MimeType.Audio -> "audio"
|
"$TAG Voice recording content created, file name is ${content.name} and duration is ${content.fileDuration}"
|
||||||
FileUtils.MimeType.Video -> "video"
|
)
|
||||||
FileUtils.MimeType.Pdf -> "application"
|
message.addContent(content)
|
||||||
FileUtils.MimeType.PlainText -> "text"
|
|
||||||
else -> "file"
|
|
||||||
}
|
|
||||||
content.subtype = if (attachment.mimeType == FileUtils.MimeType.PlainText) {
|
|
||||||
"plain"
|
|
||||||
} else {
|
} else {
|
||||||
FileUtils.getExtensionFromFileName(attachment.fileName)
|
Log.e("$TAG Voice recording content couldn't be created!")
|
||||||
}
|
}
|
||||||
content.name = attachment.fileName
|
} else {
|
||||||
content.filePath = attachment.file // Let the file body handler take care of the upload
|
for (attachment in attachments.value.orEmpty()) {
|
||||||
|
val content = Factory.instance().createContent()
|
||||||
|
|
||||||
message.addFileContent(content)
|
content.type = when (attachment.mimeType) {
|
||||||
|
FileUtils.MimeType.Image -> "image"
|
||||||
|
FileUtils.MimeType.Audio -> "audio"
|
||||||
|
FileUtils.MimeType.Video -> "video"
|
||||||
|
FileUtils.MimeType.Pdf -> "application"
|
||||||
|
FileUtils.MimeType.PlainText -> "text"
|
||||||
|
else -> "file"
|
||||||
|
}
|
||||||
|
content.subtype = if (attachment.mimeType == FileUtils.MimeType.PlainText) {
|
||||||
|
"plain"
|
||||||
|
} else {
|
||||||
|
FileUtils.getExtensionFromFileName(attachment.fileName)
|
||||||
|
}
|
||||||
|
content.name = attachment.fileName
|
||||||
|
// Let the file body handler take care of the upload
|
||||||
|
content.filePath = attachment.file
|
||||||
|
|
||||||
|
message.addFileContent(content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.contents.isNotEmpty()) {
|
if (message.contents.isNotEmpty()) {
|
||||||
|
|
@ -203,6 +244,9 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
isParticipantsListOpen.postValue(false)
|
isParticipantsListOpen.postValue(false)
|
||||||
isEmojiPickerOpen.postValue(false)
|
isEmojiPickerOpen.postValue(false)
|
||||||
|
|
||||||
|
stopVoiceRecorder()
|
||||||
|
voiceRecording.postValue(false)
|
||||||
|
|
||||||
// Warning: do not delete files
|
// Warning: do not delete files
|
||||||
val attachmentsList = arrayListOf<FileModel>()
|
val attachmentsList = arrayListOf<FileModel>()
|
||||||
attachments.postValue(attachmentsList)
|
attachments.postValue(attachmentsList)
|
||||||
|
|
@ -270,24 +314,43 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun startVoiceMessageRecording() {
|
fun startVoiceMessageRecording() {
|
||||||
voiceRecordingInProgress.value = true
|
// TODO: check microphone permission
|
||||||
|
coreContext.postOnCoreThread {
|
||||||
|
voiceRecording.postValue(true)
|
||||||
|
initVoiceRecorder()
|
||||||
|
|
||||||
|
voiceRecordingInProgress.postValue(true)
|
||||||
|
startVoiceRecorder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun stopVoiceMessageRecording() {
|
fun stopVoiceMessageRecording() {
|
||||||
|
coreContext.postOnCoreThread {
|
||||||
|
stopVoiceRecorder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun cancelVoiceMessageRecording() {
|
fun cancelVoiceMessageRecording() {
|
||||||
voiceRecordingInProgress.value = false
|
coreContext.postOnCoreThread {
|
||||||
|
stopVoiceRecorder()
|
||||||
|
|
||||||
|
val path = voiceMessageRecorder.file
|
||||||
|
if (path != null) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
Log.i("$TAG Deleting voice recording file: $path")
|
||||||
|
FileUtils.deleteFile(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
voiceRecording.postValue(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun playVoiceMessageRecording() {
|
fun togglePlayPauseVoiceRecord() {
|
||||||
}
|
isPlayingVoiceRecord.value = isPlayingVoiceRecord.value == false
|
||||||
|
|
||||||
@UiThread
|
|
||||||
fun pauseVoiceMessageRecording() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
|
@ -309,4 +372,104 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
participants.postValue(participantsList)
|
participants.postValue(participantsList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun initVoiceRecorder() {
|
||||||
|
val core = coreContext.core
|
||||||
|
Log.i("$TAG Creating voice message recorder")
|
||||||
|
val recorderParams = core.createRecorderParams()
|
||||||
|
recorderParams.fileFormat = Recorder.FileFormat.Mkv
|
||||||
|
|
||||||
|
val recordingAudioDevice = AudioRouteUtils.getAudioRecordingDeviceIdForVoiceMessage()
|
||||||
|
recorderParams.audioDevice = recordingAudioDevice
|
||||||
|
Log.i(
|
||||||
|
"$TAG Using device ${recorderParams.audioDevice?.id} to make the voice message recording"
|
||||||
|
)
|
||||||
|
|
||||||
|
voiceMessageRecorder = core.createRecorder(recorderParams)
|
||||||
|
Log.i("$TAG Voice message recorder created")
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun startVoiceRecorder() {
|
||||||
|
if (voiceRecordAudioFocusRequest == null) {
|
||||||
|
Log.i("$TAG Requesting audio focus for voice message recording")
|
||||||
|
voiceRecordAudioFocusRequest = AudioRouteUtils.acquireAudioFocusForVoiceRecordingOrPlayback(
|
||||||
|
coreContext.context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (voiceMessageRecorder.state) {
|
||||||
|
Recorder.State.Running -> Log.w("$TAG Recorder is already recording")
|
||||||
|
Recorder.State.Paused -> {
|
||||||
|
Log.w("$TAG Recorder is paused, resuming recording")
|
||||||
|
voiceMessageRecorder.start()
|
||||||
|
}
|
||||||
|
Recorder.State.Closed -> {
|
||||||
|
val extension = when (voiceMessageRecorder.params.fileFormat) {
|
||||||
|
Recorder.FileFormat.Mkv -> "mkv"
|
||||||
|
else -> "wav"
|
||||||
|
}
|
||||||
|
val tempFileName = "voice-recording-${System.currentTimeMillis()}.$extension"
|
||||||
|
val file = FileUtils.getFileStoragePath(tempFileName)
|
||||||
|
Log.w(
|
||||||
|
"$TAG Recorder is closed, starting recording in ${file.absoluteFile}"
|
||||||
|
)
|
||||||
|
voiceMessageRecorder.open(file.absolutePath)
|
||||||
|
voiceMessageRecorder.start()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
val duration = voiceMessageRecorder.duration
|
||||||
|
val formattedDuration = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration) // duration is in ms
|
||||||
|
formattedVoiceRecordingDuration.postValue(formattedDuration)
|
||||||
|
|
||||||
|
val maxVoiceRecordDuration = corePreferences.voiceRecordingMaxDuration
|
||||||
|
tickerFlowRecording().onEach {
|
||||||
|
coreContext.postOnCoreThread {
|
||||||
|
val duration = voiceMessageRecorder.duration
|
||||||
|
val formattedDuration = SimpleDateFormat("mm:ss", Locale.getDefault()).format(
|
||||||
|
duration
|
||||||
|
) // duration is in ms
|
||||||
|
formattedVoiceRecordingDuration.postValue(formattedDuration)
|
||||||
|
|
||||||
|
if (duration >= maxVoiceRecordDuration) {
|
||||||
|
Log.w(
|
||||||
|
"$TAG Max duration for voice recording exceeded (${maxVoiceRecordDuration}ms), stopping."
|
||||||
|
)
|
||||||
|
stopVoiceRecorder()
|
||||||
|
// TOOD: show toast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun stopVoiceRecorder() {
|
||||||
|
if (voiceMessageRecorder.state == Recorder.State.Running) {
|
||||||
|
Log.i("$TAG Closing voice recorder")
|
||||||
|
voiceMessageRecorder.pause()
|
||||||
|
voiceMessageRecorder.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = voiceRecordAudioFocusRequest
|
||||||
|
if (request != null) {
|
||||||
|
Log.i("$TAG Releasing voice recording audio focus request")
|
||||||
|
AudioRouteUtils.releaseAudioFocusForVoiceRecordingOrPlayback(
|
||||||
|
coreContext.context,
|
||||||
|
request
|
||||||
|
)
|
||||||
|
voiceRecordAudioFocusRequest = null
|
||||||
|
}
|
||||||
|
|
||||||
|
voiceRecordingInProgress.postValue(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tickerFlowRecording() = flow {
|
||||||
|
while (voiceRecordingInProgress.value == true) {
|
||||||
|
emit(Unit)
|
||||||
|
delay(500)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,13 @@
|
||||||
*/
|
*/
|
||||||
package org.linphone.utils
|
package org.linphone.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.AudioManager
|
||||||
|
import androidx.annotation.AnyThread
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.media.AudioAttributesCompat
|
||||||
|
import androidx.media.AudioFocusRequestCompat
|
||||||
|
import androidx.media.AudioManagerCompat
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.core.AudioDevice
|
import org.linphone.core.AudioDevice
|
||||||
import org.linphone.core.Call
|
import org.linphone.core.Call
|
||||||
|
|
@ -193,5 +199,73 @@ class AudioRouteUtils {
|
||||||
)
|
)
|
||||||
return headphonesCard ?: bluetoothCard ?: speakerCard ?: earpieceCard
|
return headphonesCard ?: bluetoothCard ?: speakerCard ?: earpieceCard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getAudioRecordingDeviceIdForVoiceMessage(): AudioDevice? {
|
||||||
|
// In case no headset/hearing aid/bluetooth is connected, use microphone sound card
|
||||||
|
// If none are available, default one will be used
|
||||||
|
var headsetCard: AudioDevice? = null
|
||||||
|
var bluetoothCard: AudioDevice? = null
|
||||||
|
var microphoneCard: AudioDevice? = null
|
||||||
|
for (device in coreContext.core.audioDevices) {
|
||||||
|
if (device.hasCapability(AudioDevice.Capabilities.CapabilityRecord)) {
|
||||||
|
when (device.type) {
|
||||||
|
AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> {
|
||||||
|
headsetCard = device
|
||||||
|
}
|
||||||
|
AudioDevice.Type.Bluetooth, AudioDevice.Type.HearingAid -> {
|
||||||
|
bluetoothCard = device
|
||||||
|
}
|
||||||
|
AudioDevice.Type.Microphone -> {
|
||||||
|
microphoneCard = device
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(
|
||||||
|
"$TAG Found headset/headphones/hearingAid sound card [$headsetCard], bluetooth sound card [$bluetoothCard] and microphone card [$microphoneCard]"
|
||||||
|
)
|
||||||
|
return headsetCard ?: bluetoothCard ?: microphoneCard
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun acquireAudioFocusForVoiceRecordingOrPlayback(context: Context): AudioFocusRequestCompat {
|
||||||
|
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
val audioAttrs = AudioAttributesCompat.Builder()
|
||||||
|
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
|
||||||
|
.setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val request =
|
||||||
|
AudioFocusRequestCompat.Builder(
|
||||||
|
AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
|
||||||
|
)
|
||||||
|
.setAudioAttributes(audioAttrs)
|
||||||
|
.setOnAudioFocusChangeListener { }
|
||||||
|
.build()
|
||||||
|
when (AudioManagerCompat.requestAudioFocus(audioManager, request)) {
|
||||||
|
AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
|
||||||
|
Log.i("$TAG Voice recording/playback audio focus request granted")
|
||||||
|
}
|
||||||
|
AudioManager.AUDIOFOCUS_REQUEST_FAILED -> {
|
||||||
|
Log.w("$TAG Voice recording/playback audio focus request failed")
|
||||||
|
}
|
||||||
|
AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
|
||||||
|
Log.w("$TAG Voice recording/playback audio focus request delayed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun releaseAudioFocusForVoiceRecordingOrPlayback(
|
||||||
|
context: Context,
|
||||||
|
request: AudioFocusRequestCompat
|
||||||
|
) {
|
||||||
|
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
AudioManagerCompat.abandonAudioFocusRequest(audioManager, request)
|
||||||
|
Log.i("$TAG Voice recording/playback audio focus request abandoned")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
app/src/main/res/drawable/pause_fill.xml
Normal file
9
app/src/main/res/drawable/pause_fill.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="256"
|
||||||
|
android:viewportHeight="256">
|
||||||
|
<path
|
||||||
|
android:pathData="M216,48V208a16,16 0,0 1,-16 16H160a16,16 0,0 1,-16 -16V48a16,16 0,0 1,16 -16h40A16,16 0,0 1,216 48ZM96,32H56A16,16 0,0 0,40 48V208a16,16 0,0 0,16 16H96a16,16 0,0 0,16 -16V48A16,16 0,0 0,96 32Z"
|
||||||
|
android:fillColor="#4e6074"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -24,14 +24,14 @@
|
||||||
android:id="@+id/voice_recording"
|
android:id="@+id/voice_recording"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="@{viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
|
android:visibility="@{viewModel.voiceRecording ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
app:constraint_referenced_ids="cancel_voice_message, voice_record_progress, stop_recording, voice_recording_length" />
|
app:constraint_referenced_ids="cancel_voice_message, voice_record_progress" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Group
|
<androidx.constraintlayout.widget.Group
|
||||||
android:id="@+id/standard_messages"
|
android:id="@+id/standard_messages"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="@{viewModel.voiceRecordingInProgress ? View.INVISIBLE : View.VISIBLE}"
|
android:visibility="@{viewModel.voiceRecording ? View.INVISIBLE : View.VISIBLE}"
|
||||||
app:constraint_referenced_ids="emoji_picker_toggle, attach_file, message_area_background, message_to_send" />
|
app:constraint_referenced_ids="emoji_picker_toggle, attach_file, message_area_background, message_to_send" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
|
@ -181,6 +181,22 @@
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/stop_fill"
|
android:src="@drawable/stop_fill"
|
||||||
android:background="@drawable/circle_white_button_background"
|
android:background="@drawable/circle_white_button_background"
|
||||||
|
android:visibility="@{viewModel.voiceRecording && viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/voice_record_progress"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/voice_record_progress"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/voice_record_progress"
|
||||||
|
app:tint="@color/orange_main_500" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/play_pause_voice_record"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:onClick="@{() -> viewModel.togglePlayPauseVoiceRecord()}"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@{viewModel.isPlayingVoiceRecord ? @drawable/pause_fill : @drawable/play_fill, default=@drawable/play_fill}"
|
||||||
|
android:background="@drawable/circle_white_button_background"
|
||||||
|
android:visibility="@{viewModel.voiceRecording && !viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/voice_record_progress"
|
app:layout_constraintBottom_toBottomOf="@id/voice_record_progress"
|
||||||
app:layout_constraintStart_toStartOf="@id/voice_record_progress"
|
app:layout_constraintStart_toStartOf="@id/voice_record_progress"
|
||||||
app:layout_constraintTop_toTopOf="@id/voice_record_progress"
|
app:layout_constraintTop_toTopOf="@id/voice_record_progress"
|
||||||
|
|
@ -188,7 +204,7 @@
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
style="@style/default_text_style"
|
style="@style/default_text_style"
|
||||||
android:id="@+id/voice_recording_length"
|
android:id="@+id/voice_recording_duration"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
|
|
@ -196,10 +212,33 @@
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:paddingTop="5dp"
|
android:paddingTop="5dp"
|
||||||
android:paddingBottom="5dp"
|
android:paddingBottom="5dp"
|
||||||
android:text="00:00"
|
android:text="@{viewModel.formattedVoiceRecordingDuration, default=`00:00`}"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textColor="@color/gray_main2_600"
|
android:textColor="@color/gray_main2_600"
|
||||||
android:background="@drawable/shape_squircle_white_r50_background"
|
android:background="@drawable/shape_squircle_white_r50_background"
|
||||||
|
android:visibility="@{viewModel.voiceRecording && viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
|
android:drawableStart="@drawable/record_fill"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
app:drawableTint="@color/red_danger_500"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/voice_record_progress"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/voice_record_progress"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/voice_record_progress"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="@style/default_text_style"
|
||||||
|
android:id="@+id/voice_record_duration"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
|
android:text="@{viewModel.formattedVoiceRecordingDuration, default=`00:00`}"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@color/gray_main2_600"
|
||||||
|
android:background="@drawable/shape_squircle_white_r50_background"
|
||||||
|
android:visibility="@{viewModel.voiceRecording && !viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/voice_record_progress"
|
app:layout_constraintBottom_toBottomOf="@id/voice_record_progress"
|
||||||
app:layout_constraintEnd_toEndOf="@id/voice_record_progress"
|
app:layout_constraintEnd_toEndOf="@id/voice_record_progress"
|
||||||
app:layout_constraintTop_toTopOf="@id/voice_record_progress"/>
|
app:layout_constraintTop_toTopOf="@id/voice_record_progress"/>
|
||||||
|
|
@ -209,7 +248,7 @@
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:visibility="@{viewModel.textToSend.length() > 0 || viewModel.attachments.size() > 0 || viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
|
android:visibility="@{viewModel.textToSend.length() > 0 || viewModel.attachments.size() > 0 || viewModel.voiceRecording ? View.VISIBLE : View.GONE, default=gone}"
|
||||||
android:onClick="@{() -> viewModel.sendMessage()}"
|
android:onClick="@{() -> viewModel.sendMessage()}"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/paper_plane_tilt"
|
android:src="@drawable/paper_plane_tilt"
|
||||||
|
|
@ -223,7 +262,7 @@
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:visibility="@{viewModel.textToSend.length() > 0 || viewModel.attachments.size() > 0 || viewModel.voiceRecordingInProgress ? View.GONE : View.VISIBLE}"
|
android:visibility="@{viewModel.textToSend.length() > 0 || viewModel.attachments.size() > 0 || viewModel.voiceRecording ? View.GONE : View.VISIBLE}"
|
||||||
android:onClick="@{() -> viewModel.startVoiceMessageRecording()}"
|
android:onClick="@{() -> viewModel.startVoiceMessageRecording()}"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/microphone"
|
android:src="@drawable/microphone"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue