mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Voice message recording view implementations.
This commit is contained in:
parent
cb96886568
commit
5676226f42
21 changed files with 523 additions and 169 deletions
|
@ -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/' }
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)?) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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>
|
12
vector/src/main/res/drawable/bg_voice_playback.xml
Normal file
12
vector/src/main/res/drawable/bg_voice_playback.xml
Normal 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>
|
12
vector/src/main/res/drawable/ic_voice_pause.xml
Normal file
12
vector/src/main/res/drawable/ic_voice_pause.xml
Normal 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>
|
9
vector/src/main/res/drawable/ic_voice_play.xml
Normal file
9
vector/src/main/res/drawable/ic_voice_play.xml
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in a new issue