Voice message recording view implementations.

This commit is contained in:
Onuray Sahin 2021-07-01 10:47:41 +03:00
parent cb96886568
commit 5676226f42
21 changed files with 523 additions and 169 deletions

View file

@ -50,7 +50,7 @@ allprojects {
includeGroupByRegex 'nl\\.dionsegijn'
// Voice RecordView
includeGroupByRegex 'com\\.github\\.3llomi'
includeGroupByRegex 'com\\.github\\.Armen101'
}
}
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }

View file

@ -331,7 +331,7 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0-beta02'
implementation "androidx.sharetarget:sharetarget:1.1.0"
implementation 'androidx.core:core-ktx:1.5.0'
implementation "androidx.media:media:1.3.1"
@ -393,7 +393,7 @@ dependencies {
implementation "androidx.autofill:autofill:$autofill_version"
implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
implementation 'com.github.3llomi:RecordView:3.0.1'
implementation 'com.github.Armen101:AudioRecordView:1.0.5'
// Custom Tab
implementation 'androidx.browser:browser:1.3.0'

View file

@ -20,6 +20,7 @@ import android.content.Context
import android.net.Uri
import android.text.Editable
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
@ -48,9 +49,7 @@ class TextComposerView @JvmOverloads constructor(
fun onCloseRelatedMessage()
fun onSendMessage(text: CharSequence)
fun onAddAttachment()
fun onVoiceRecordingStarted()
fun onVoiceRecordingEnded(recordTime: Long)
fun checkVoiceRecordingPermission(): Boolean
fun onTouchVoiceRecording()
}
val views: ComposerLayoutBinding
@ -78,7 +77,7 @@ class TextComposerView @JvmOverloads constructor(
override fun onTextEmptyStateChanged(isEmpty: Boolean) {
val shouldShowSendButton = currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded || !isEmpty
views.sendButton.isInvisible = !shouldShowSendButton
views.voiceMessageRecorderView.isVisible = !shouldShowSendButton
callback?.onTextEmptyStateChanged(isEmpty)
}
}
views.composerRelatedMessageCloseButton.setOnClickListener {
@ -94,28 +93,6 @@ class TextComposerView @JvmOverloads constructor(
views.attachmentButton.setOnClickListener {
callback?.onAddAttachment()
}
views.voiceMessageRecorderView.callback = object : VoiceMessageRecorderView.Callback {
override fun onVoiceRecordingStarted() {
views.attachmentButton.isVisible = false
views.composerEditText.isVisible = false
views.composerEmojiButton.isVisible = false
views.composerEditTextOuterBorder.isVisible = false
callback?.onVoiceRecordingStarted()
}
override fun onVoiceRecordingEnded(recordTime: Long) {
views.attachmentButton.isVisible = true
views.composerEditText.isVisible = true
views.composerEmojiButton.isVisible = true
views.composerEditTextOuterBorder.isVisible = true
callback?.onVoiceRecordingEnded(recordTime)
}
override fun checkVoiceRecordingPermission(): Boolean {
return callback?.checkVoiceRecordingPermission().orFalse()
}
}
}
fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {
@ -128,7 +105,6 @@ class TextComposerView @JvmOverloads constructor(
val shouldShowSendButton = !views.composerEditText.text.isNullOrEmpty()
views.sendButton.isInvisible = !shouldShowSendButton
views.voiceMessageRecorderView.isVisible = !shouldShowSendButton
}
fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {
@ -139,7 +115,6 @@ class TextComposerView @JvmOverloads constructor(
currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded
applyNewConstraintSet(animate, transitionComplete)
views.sendButton.isInvisible = false
views.voiceMessageRecorderView.isVisible = false
}
private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) {

View file

@ -1,87 +0,0 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.detail.composer
import android.content.Context
import android.media.MediaRecorder
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import im.vector.app.BuildConfig
import im.vector.lib.multipicker.entity.MultiPickerAudioType
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.lang.RuntimeException
import java.util.UUID
import javax.inject.Inject
/**
* Helper class to record audio for voice messages.
*/
class VoiceMessageRecordingHelper @Inject constructor(
private val context: Context
) {
private lateinit var mediaRecorder: MediaRecorder
private val outputDirectory = File(context.cacheDir, "downloads")
private var outputFile: File? = null
init {
if (!outputDirectory.exists()) {
outputDirectory.mkdirs()
}
}
private fun refreshMediaRecorder() {
mediaRecorder = MediaRecorder()
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT)
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG)
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS)
mediaRecorder.setAudioEncodingBitRate(24000)
mediaRecorder.setAudioSamplingRate(48000)
}
fun startRecording() {
outputFile = File(outputDirectory, UUID.randomUUID().toString() + ".ogg")
FileOutputStream(outputFile).use { fos ->
refreshMediaRecorder()
mediaRecorder.setOutputFile(fos.fd)
mediaRecorder.prepare()
mediaRecorder.start()
}
}
fun stopRecording(recordTime: Long): MultiPickerAudioType? {
try {
mediaRecorder.stop()
mediaRecorder.reset()
mediaRecorder.release()
outputFile?.let {
val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it)
return outputFileUri?.toMultiPickerAudioType(context)
} ?: return null
} catch (e: RuntimeException) { // Usually thrown when the record is less than 1 second.
Timber.e(e, "Voice message is not valid. Record time: %s", recordTime)
return null
}
}
fun deleteRecording() {
outputFile?.delete()
}
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.detail.timeline.item
import android.text.format.DateUtils
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.visualizer.amplitude.AudioRecordView
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
@EpoxyAttribute
var mxcUrl: String = ""
@EpoxyAttribute
var duration: Int = 0
@EpoxyAttribute
var waveform: List<Int> = emptyList()
@EpoxyAttribute
var izLocalFile = false
@EpoxyAttribute
var izDownloaded = false
@EpoxyAttribute
lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder
@EpoxyAttribute
lateinit var contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var playbackControlButtonClickListener: ClickListener? = null
@EpoxyAttribute
lateinit var voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker
override fun bind(holder: Holder) {
super.bind(holder)
renderSendState(holder.voiceLayout, null)
if (!attributes.informationData.sendState.hasFailed()) {
contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout)
} else {
holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_cross)
holder.progressLayout.isVisible = false
}
holder.voicePlaybackTime.text = formatPlaybackTime(duration)
holder.voicePlaybackWaveform.post {
holder.voicePlaybackWaveform.recreate()
waveform.forEach { amplitude ->
holder.voicePlaybackWaveform.update(amplitude)
}
}
holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) }
voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener {
override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) {
when (state) {
is VoiceMessagePlaybackTracker.Listener.State.Idle -> handleIdleState(holder, state)
is VoiceMessagePlaybackTracker.Listener.State.Playing -> handlePlayingState(holder, state)
}
}
})
}
private fun handleIdleState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Idle) {
holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_voice_play)
if (state.playbackTime > 0) {
holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime)
} else {
holder.voicePlaybackTime.text = formatPlaybackTime(duration)
}
}
private fun handlePlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing) {
holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_voice_pause)
if (state.playbackTime > 0) {
holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime)
} else {
holder.voicePlaybackTime.text = formatPlaybackTime(duration)
}
}
private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong())
override fun unbind(holder: Holder) {
super.unbind(holder)
contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId)
contentDownloadStateTrackerBinder.unbind(mxcUrl)
}
override fun getViewType() = STUB_ID
class Holder : AbsMessageItem.Holder(STUB_ID) {
val voiceLayout by bind<ViewGroup>(R.id.voiceLayout)
val voicePlaybackControlButton by bind<ImageButton>(R.id.voicePlaybackControlButton)
val voicePlaybackTime by bind<TextView>(R.id.voicePlaybackTime)
val voicePlaybackWaveform by bind<AudioRecordView>(R.id.voicePlaybackWaveform)
val progressLayout by bind<ViewGroup>(R.id.messageFileUploadProgressLayout)
}
companion object {
private const val STUB_ID = R.id.messageContentVoiceStub
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="32dp" android:height="32dp" />
<solid android:color="?vctr_voice_message_play_pause_button_background" />
</shape>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?vctr_voice_message_playback_background" />
<size
android:width="240dp"
android:height="44dp" />
<corners
android:bottomLeftRadius="12dp"
android:bottomRightRadius="12dp"
android:topLeftRadius="12dp"
android:topRightRadius="12dp" />
</shape>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="12dp"
android:viewportWidth="10"
android:viewportHeight="12">
<path
android:pathData="M1,0L2,0A1,1 0,0 1,3 1L3,11A1,1 0,0 1,2 12L1,12A1,1 0,0 1,0 11L0,1A1,1 0,0 1,1 0z"
android:fillColor="#737D8C"/>
<path
android:pathData="M8,0L9,0A1,1 0,0 1,10 1L10,11A1,1 0,0 1,9 12L8,12A1,1 0,0 1,7 11L7,1A1,1 0,0 1,8 0z"
android:fillColor="#737D8C"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="13dp"
android:height="16dp"
android:viewportWidth="13"
android:viewportHeight="16">
<path
android:pathData="M0,14.2104V1.7896C0,1.0072 0.8578,0.5279 1.5241,0.9379L11.6161,7.1483C12.2506,7.5388 12.2506,8.4612 11.6161,8.8517L1.5241,15.0621C0.8578,15.4721 0,14.9928 0,14.2104Z"
android:fillColor="#737D8C"/>
</vector>

View file

@ -131,10 +131,16 @@
android:src="@drawable/ic_send"
tools:ignore="MissingConstraints" />
<im.vector.app.features.home.room.detail.composer.VoiceMessageRecorderView
android:id="@+id/voiceMessageRecorderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints" />
<!--
<ImageButton
android:id="@+id/voiceMessageMicButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:src="@drawable/ic_voice_mic" />
-->
</merge>

View file

@ -178,12 +178,18 @@
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<im.vector.app.features.home.room.detail.composer.VoiceMessageRecorderView
android:id="@+id/voiceMessageRecorderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!--
<ImageButton
android:id="@+id/voiceMessageMicButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:src="@drawable/ic_voice_mic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintEnd_toEndOf="parent" />
-->
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -248,4 +248,12 @@
android:background="?vctr_chat_effect_snow_background"
android:visibility="invisible" />
<im.vector.app.features.home.room.detail.composer.VoiceMessageRecorderView
android:id="@+id/voiceMessageRecorderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -132,6 +132,13 @@
android:layout_marginEnd="56dp"
android:layout="@layout/item_timeline_event_option_buttons_stub" />
<ViewStub
android:id="@+id/messageContentVoiceStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_voice_stub"
android:layout_marginEnd="56dp"
tools:visibility="visible" />
</FrameLayout>
<im.vector.app.core.ui.views.SendStateImageView

View file

@ -10,8 +10,8 @@
android:id="@+id/messageSelectedBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:background="@drawable/highlighted_message_background" />
<View

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/voiceLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/voicePlaybackLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_voice_playback"
android:minHeight="48dp"
android:paddingStart="8dp"
android:paddingTop="6dp"
android:paddingEnd="8dp"
android:paddingBottom="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/voicePlaybackControlButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/bg_voice_play_pause_button"
android:contentDescription="@string/a11y_play_voice_message"
android:paddingStart="3dp"
android:paddingEnd="0dp"
android:src="@drawable/ic_voice_play"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/voicePlaybackTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:lineSpacingExtra="4sp"
android:textColor="@color/palette_gray_200"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/voicePlaybackControlButton"
app:layout_constraintStart_toEndOf="@id/voicePlaybackControlButton"
app:layout_constraintTop_toTopOf="@id/voicePlaybackControlButton"
tools:text="0:23" />
<com.visualizer.amplitude.AudioRecordView
android:id="@+id/voicePlaybackWaveform"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:chunkAlignTo="center"
app:chunkColor="@color/palette_gray_300"
app:chunkMinHeight="1dp"
app:chunkRoundedCorners="true"
app:chunkSoftTransition="true"
app:chunkSpace="2dp"
app:chunkWidth="2dp"
app:direction="leftToRight"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/voicePlaybackTime"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:id="@+id/messageFileUploadProgressLayout"
layout="@layout/media_upload_download_progress_layout"
android:layout_width="0dp"
android:layout_height="46dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/voicePlaybackLayout"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -2,68 +2,217 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/voice_message_recording_layout"
android:minHeight="200dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="2dp">
android:layout_height="match_parent">
<View
android:id="@+id/voiceMessageLockBackground"
android:layout_width="56dp"
android:layout_width="52dp"
android:layout_height="160dp"
android:background="@drawable/bg_voice_message_lock"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
app:layout_constraintTop_toBottomOf="@id/voiceMessageMicButton" />
<com.devlomi.record_view.RecordView
android:id="@+id/voiceMessageRecordView"
android:layout_width="0dp"
<ImageButton
android:id="@+id/voiceMessageMicButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:counter_time_color="@color/palette_gray_200"
app:layout_constraintBottom_toBottomOf="@+id/voiceMessageButton"
app:layout_constraintEnd_toStartOf="@+id/voiceMessageButton"
app:layout_constraintStart_toStartOf="parent"
app:slide_to_cancel_arrow="@drawable/ic_voice_slide_to_cancel_arrow"
app:slide_to_cancel_arrow_color="@color/palette_gray_300"
app:slide_to_cancel_text="@string/voice_message_slide_to_cancel" />
<com.devlomi.record_view.RecordButton
android:id="@+id/voiceMessageButton"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_start_voice_message"
android:scaleType="center"
android:layout_marginBottom="12dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_voice_mic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:mic_icon="@drawable/ic_voice_mic"
app:layout_constraintEnd_toEndOf="parent" />
<ImageButton
android:id="@+id/voiceMessageSendButton"
android:layout_width="56dp"
android:layout_height="56dp"
android:contentDescription="@string/send"
android:background="@drawable/bg_send"
android:scaleType="center"
android:visibility="gone"
tools:visibility="visible"
android:src="@drawable/ic_send"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/voiceMessageTimerIndicator"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="20dp"
android:contentDescription="@string/a11y_recording_voice_message"
android:src="@drawable/circle"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="@id/voiceMessageMicButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/voiceMessageMicButton"
app:tint="@color/palette_vermilion"
tools:ignore="MissingPrefix" />
<TextView
android:id="@+id/voiceMessageTimer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:lineSpacingExtra="4dp"
android:textColor="@color/palette_gray_200"
android:textSize="14sp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="@id/voiceMessageMicButton"
app:layout_constraintStart_toEndOf="@id/voiceMessageTimerIndicator"
app:layout_constraintTop_toTopOf="@id/voiceMessageMicButton"
tools:text="00:03" />
<TextView
android:id="@+id/voiceMessageSlideToCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/voice_message_slide_to_cancel"
android:textColor="@color/palette_gray_300"
android:textSize="14sp"
android:visibility="gone"
tools:visibility="visible"
app:drawableStartCompat="@drawable/ic_voice_slide_to_cancel_arrow"
app:layout_constraintBottom_toBottomOf="@id/voiceMessageMicButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/voiceMessageMicButton" />
<ImageView
android:id="@+id/voiceMessageLockImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:contentDescription="@string/a11y_lock_voice_message"
android:src="@drawable/ic_voice_message_unlocked"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintEnd_toEndOf="@id/voiceMessageLockBackground"
app:layout_constraintStart_toStartOf="@id/voiceMessageLockBackground"
app:layout_constraintTop_toTopOf="@id/voiceMessageLockBackground" />
<ImageView
android:id="@+id/voiceMessageLockArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_voice_lock_arrow"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/voiceMessageButton"
tools:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/voiceMessageMicButton"
app:layout_constraintEnd_toEndOf="@id/voiceMessageLockBackground"
app:layout_constraintStart_toStartOf="@id/voiceMessageLockBackground"
android:layout_marginBottom="24dp"
tools:ignore="ContentDescription"
tools:visibility="visible" />
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/voiceMessageLockImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:contentDescription="@string/a11y_lock_voice_message"
android:src="@drawable/ic_voice_message_unlocked"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/voiceMessagePlaybackLayout"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/voiceMessageLockBackground"
app:layout_constraintStart_toStartOf="@id/voiceMessageLockBackground"
app:layout_constraintTop_toTopOf="@id/voiceMessageLockBackground"
tools:visibility="visible" />
tools:visibility="visible"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/voiceMessageMicButton"
app:layout_constraintStart_toStartOf="parent" >
<ImageButton
android:id="@+id/voiceMessageDeletePlayback"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/a11y_delete_recorded_voice_message"
android:src="@drawable/recv_ic_delete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/palette_gray_200"
tools:ignore="MissingPrefix" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toEndOf="@id/voiceMessageDeletePlayback"
app:layout_constraintEnd_toEndOf="parent"
android:backgroundTint="?vctr_voice_message_recording_playback_background"
android:background="@drawable/bg_voice_playback"
android:layout_marginStart="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:id="@+id/voiceMessagePlaybackTimerIndicator"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="8dp"
android:contentDescription="@string/a11y_recording_voice_message"
android:src="@drawable/circle"
app:layout_goneMarginStart="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/palette_vermilion"
tools:ignore="MissingPrefix" />
<ImageButton
android:id="@+id/voicePlaybackControlButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/bg_voice_play_pause_button"
android:contentDescription="@string/a11y_play_voice_message"
android:paddingStart="3dp"
android:layout_marginStart="8dp"
android:paddingEnd="0dp"
android:src="@drawable/ic_voice_play"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/voicePlaybackTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:lineSpacingExtra="4sp"
android:textColor="@color/palette_gray_200"
app:layout_goneMarginStart="24dp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/voicePlaybackControlButton"
app:layout_constraintStart_toEndOf="@id/voicePlaybackControlButton"
app:layout_constraintTop_toTopOf="@id/voicePlaybackControlButton"
tools:text="0:23" />
<com.visualizer.amplitude.AudioRecordView
android:id="@+id/voicePlaybackWaveform"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:chunkAlignTo="center"
app:chunkColor="@color/palette_gray_300"
app:chunkMinHeight="1dp"
app:chunkRoundedCorners="true"
app:chunkSoftTransition="true"
app:chunkSpace="2dp"
app:chunkWidth="2dp"
app:direction="leftToRight"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/voicePlaybackTime"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -135,6 +135,17 @@
<attr name="vctr_voice_message_lock_background" format="color" />
<color name="vctr_voice_message_lock_background_light">#FFF3F8FD</color>
<color name="vctr_voice_message_lock_background_dark">#22252B</color>
<color name="vctr_voice_message_lock_background_black">#22252B</color>
<attr name="vctr_voice_message_playback_background" format="color" />
<color name="vctr_voice_message_playback_background_light">#FFE3E8F0</color>
<color name="vctr_voice_message_playback_background_dark">#FF394049</color>
<attr name="vctr_voice_message_play_pause_button_background" format="color" />
<color name="vctr_voice_message_play_pause_button_background_light">@android:color/white</color>
<color name="vctr_voice_message_play_pause_button_background_dark">#FF8E99A4</color>
<attr name="vctr_voice_message_recording_playback_background" format="color" />
<color name="vctr_voice_message_recording_playback_background_light">#FFE3E8F0</color>
<color name="vctr_voice_message_recording_playback_background_dark">#FF394049</color>
</resources>

View file

@ -2605,6 +2605,7 @@
<string name="sent_a_video">Video.</string>
<string name="sent_an_image">Image.</string>
<string name="sent_an_audio_file">Audio</string>
<string name="sent_a_voice_message">Voice</string>
<string name="sent_a_file">File</string>
<string name="send_a_sticker">Sticker</string>
<string name="sent_a_poll">Poll</string>
@ -3403,4 +3404,11 @@
<string name="a11y_start_voice_message">Start Voice Message</string>
<string name="voice_message_slide_to_cancel">Slide to cancel</string>
<string name="a11y_lock_voice_message">Voice Message Lock</string>
<string name="a11y_play_voice_message">Play Voice Message</string>
<string name="a11y_pause_voice_message">Pause Voice Message</string>
<string name="a11y_recording_voice_message">Recording voice message</string>
<string name="a11y_delete_recorded_voice_message">Delete recorded voice message</string>
<string name="voice_message_release_to_send_toast">Release to send</string>
<string name="voice_message_n_seconds_warning_toast">%1$ds left</string>
<string name="voice_message_tap_on_waveform_to_stop_toast">Tap on the waveform to stop and playback</string>
</resources>

View file

@ -144,6 +144,12 @@
<item name="vctr_social_login_button_gitlab_style">@style/WidgetButtonSocialLogin.Gitlab.Dark</item>
<item name="actionModeStyle">@style/ActionModeTheme</item>
<!-- Voice Message -->
<item name="vctr_voice_message_lock_background">@color/vctr_voice_message_lock_background_dark</item>
<item name="vctr_voice_message_playback_background">@color/vctr_voice_message_playback_background_dark</item>
<item name="vctr_voice_message_play_pause_button_background">@color/vctr_voice_message_play_pause_button_background_dark</item>
<item name="vctr_voice_message_recording_playback_background">@color/vctr_voice_message_recording_playback_background_dark</item>
</style>
<style name="AppTheme.Dark" parent="AppTheme.Base.Dark" />

View file

@ -146,6 +146,12 @@
<item name="vctr_social_login_button_gitlab_style">@style/WidgetButtonSocialLogin.Gitlab.Light</item>
<item name="actionModeStyle">@style/ActionModeTheme</item>
<!-- Voice Message -->
<item name="vctr_voice_message_lock_background">@color/vctr_voice_message_lock_background_light</item>
<item name="vctr_voice_message_playback_background">@color/vctr_voice_message_playback_background_light</item>
<item name="vctr_voice_message_play_pause_button_background">@color/vctr_voice_message_play_pause_button_background_light</item>
<item name="vctr_voice_message_recording_playback_background">@color/vctr_voice_message_recording_playback_background_light</item>
</style>
<style name="AppTheme.Light" parent="AppTheme.Base.Light" />