mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +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)
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
@get:WorkerThread @set:WorkerThread
|
||||
var darkMode: Int
|
||||
|
|
|
|||
|
|
@ -24,17 +24,27 @@ import androidx.annotation.WorkerThread
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
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 org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.core.ChatRoomListenerStub
|
||||
import org.linphone.core.EventLog
|
||||
import org.linphone.core.Factory
|
||||
import org.linphone.core.Recorder
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.chat.model.ChatMessageModel
|
||||
import org.linphone.ui.main.chat.model.FileModel
|
||||
import org.linphone.ui.main.chat.model.ParticipantModel
|
||||
import org.linphone.utils.AudioRouteUtils
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.FileUtils
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
|
@ -62,8 +72,14 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val isReplyingToMessage = MutableLiveData<String>()
|
||||
|
||||
val voiceRecording = MutableLiveData<Boolean>()
|
||||
|
||||
val voiceRecordingInProgress = MutableLiveData<Boolean>()
|
||||
|
||||
val formattedVoiceRecordingDuration = MutableLiveData<String>()
|
||||
|
||||
val isPlayingVoiceRecord = MutableLiveData<Boolean>()
|
||||
|
||||
val requestKeyboardHidingEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
|
@ -80,6 +96,10 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
private var chatMessageToReplyTo: ChatMessage? = null
|
||||
|
||||
private lateinit var voiceMessageRecorder: Recorder
|
||||
|
||||
private var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null
|
||||
|
||||
private val chatRoomListener = object : ChatRoomListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||
|
|
@ -94,6 +114,7 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
init {
|
||||
isEmojiPickerOpen.value = false
|
||||
isPlayingVoiceRecord.value = false
|
||||
}
|
||||
|
||||
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 {
|
||||
if (::chatRoom.isInitialized) {
|
||||
chatRoom.removeListener(chatRoomListener)
|
||||
|
|
@ -169,26 +196,40 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
message.addUtf8TextContent(toSend)
|
||||
}
|
||||
|
||||
for (attachment in attachments.value.orEmpty()) {
|
||||
val content = Factory.instance().createContent()
|
||||
|
||||
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"
|
||||
if (voiceRecording.value == true && voiceMessageRecorder.file != null) {
|
||||
stopVoiceRecorder()
|
||||
val content = voiceMessageRecorder.createContent()
|
||||
if (content != null) {
|
||||
Log.i(
|
||||
"$TAG Voice recording content created, file name is ${content.name} and duration is ${content.fileDuration}"
|
||||
)
|
||||
message.addContent(content)
|
||||
} else {
|
||||
FileUtils.getExtensionFromFileName(attachment.fileName)
|
||||
Log.e("$TAG Voice recording content couldn't be created!")
|
||||
}
|
||||
content.name = attachment.fileName
|
||||
content.filePath = attachment.file // Let the file body handler take care of the upload
|
||||
} else {
|
||||
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()) {
|
||||
|
|
@ -203,6 +244,9 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
isParticipantsListOpen.postValue(false)
|
||||
isEmojiPickerOpen.postValue(false)
|
||||
|
||||
stopVoiceRecorder()
|
||||
voiceRecording.postValue(false)
|
||||
|
||||
// Warning: do not delete files
|
||||
val attachmentsList = arrayListOf<FileModel>()
|
||||
attachments.postValue(attachmentsList)
|
||||
|
|
@ -270,24 +314,43 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
@UiThread
|
||||
fun startVoiceMessageRecording() {
|
||||
voiceRecordingInProgress.value = true
|
||||
// TODO: check microphone permission
|
||||
coreContext.postOnCoreThread {
|
||||
voiceRecording.postValue(true)
|
||||
initVoiceRecorder()
|
||||
|
||||
voiceRecordingInProgress.postValue(true)
|
||||
startVoiceRecorder()
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun stopVoiceMessageRecording() {
|
||||
coreContext.postOnCoreThread {
|
||||
stopVoiceRecorder()
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
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
|
||||
fun playVoiceMessageRecording() {
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun pauseVoiceMessageRecording() {
|
||||
fun togglePlayPauseVoiceRecord() {
|
||||
isPlayingVoiceRecord.value = isPlayingVoiceRecord.value == false
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
@ -309,4 +372,104 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import androidx.annotation.AnyThread
|
||||
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.core.AudioDevice
|
||||
import org.linphone.core.Call
|
||||
|
|
@ -193,5 +199,73 @@ class AudioRouteUtils {
|
|||
)
|
||||
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:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="@{viewModel.voiceRecordingInProgress ? View.VISIBLE : View.GONE, default=gone}"
|
||||
app:constraint_referenced_ids="cancel_voice_message, voice_record_progress, stop_recording, voice_recording_length" />
|
||||
android:visibility="@{viewModel.voiceRecording ? View.VISIBLE : View.GONE, default=gone}"
|
||||
app:constraint_referenced_ids="cancel_voice_message, voice_record_progress" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/standard_messages"
|
||||
android:layout_width="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" />
|
||||
|
||||
<include
|
||||
|
|
@ -181,6 +181,22 @@
|
|||
android:padding="8dp"
|
||||
android:src="@drawable/stop_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_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_constraintStart_toStartOf="@id/voice_record_progress"
|
||||
app:layout_constraintTop_toTopOf="@id/voice_record_progress"
|
||||
|
|
@ -188,7 +204,7 @@
|
|||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/voice_recording_length"
|
||||
android:id="@+id/voice_recording_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
|
|
@ -196,10 +212,33 @@
|
|||
android:paddingEnd="12dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:text="00:00"
|
||||
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}"
|
||||
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_constraintEnd_toEndOf="@id/voice_record_progress"
|
||||
app:layout_constraintTop_toTopOf="@id/voice_record_progress"/>
|
||||
|
|
@ -209,7 +248,7 @@
|
|||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
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:padding="8dp"
|
||||
android:src="@drawable/paper_plane_tilt"
|
||||
|
|
@ -223,7 +262,7 @@
|
|||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
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:padding="8dp"
|
||||
android:src="@drawable/microphone"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue