mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-18 20:29:10 +03:00
Voice message UI initial implementation.
This commit is contained in:
parent
0d6994dd43
commit
69350ef514
20 changed files with 428 additions and 3 deletions
|
@ -48,6 +48,9 @@ allprojects {
|
|||
// Chat effects
|
||||
includeGroupByRegex 'com\\.github\\.jetradarmobile'
|
||||
includeGroupByRegex 'nl\\.dionsegijn'
|
||||
|
||||
// Voice RecordView
|
||||
includeGroupByRegex 'com\\.github\\.3llomi'
|
||||
}
|
||||
}
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
|
|
|
@ -144,6 +144,8 @@ android {
|
|||
|
||||
buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping"
|
||||
|
||||
buildConfigField "Long", "VOICE_MESSAGE_DURATION_LIMIT_MS", "120000L"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// Keep abiFilter for the universalApk
|
||||
|
@ -391,6 +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'
|
||||
|
||||
// Custom Tab
|
||||
implementation 'androidx.browser:browser:1.3.0'
|
||||
|
|
|
@ -109,4 +109,8 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
|
||||
// Failed messages
|
||||
object RemoveAllFailedMessages : RoomDetailAction()
|
||||
|
||||
// Voice Message
|
||||
object StartRecordingVoiceMessage : RoomDetailAction()
|
||||
data class EndRecordingVoiceMessage(val recordTime: Long) : RoomDetailAction()
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ import im.vector.app.core.ui.views.NotificationAreaView
|
|||
import im.vector.app.core.utils.Debouncer
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.core.utils.KeyboardStateUtils
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.colorizeMatchingText
|
||||
|
@ -1192,6 +1193,18 @@ class RoomDetailFragment @Inject constructor(
|
|||
override fun onTextEmptyStateChanged(isEmpty: Boolean) {
|
||||
// No op
|
||||
}
|
||||
|
||||
override fun onVoiceRecordingStarted() {
|
||||
roomDetailViewModel.handle(RoomDetailAction.StartRecordingVoiceMessage)
|
||||
}
|
||||
|
||||
override fun onVoiceRecordingEnded(recordTime: Long) {
|
||||
roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(recordTime))
|
||||
}
|
||||
|
||||
override fun checkVoiceRecordingPermission(): Boolean {
|
||||
return checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, requireActivity(), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail
|
|||
|
||||
import android.net.Uri
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
|
@ -38,6 +39,7 @@ import im.vector.app.core.extensions.exhaustive
|
|||
import im.vector.app.core.mvrx.runCatchingToAsync
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.attachments.toContentAttachmentData
|
||||
import im.vector.app.features.call.conference.JitsiService
|
||||
import im.vector.app.features.call.dialpad.DialPadLookup
|
||||
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
||||
|
@ -47,6 +49,7 @@ import im.vector.app.features.command.ParsedCommand
|
|||
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
|
||||
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||
import im.vector.app.features.home.room.detail.composer.VoiceMessageRecordingHelper
|
||||
import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator
|
||||
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
||||
|
@ -56,6 +59,7 @@ import im.vector.app.features.home.room.typing.TypingHelper
|
|||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
@ -119,6 +123,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
private val chatEffectManager: ChatEffectManager,
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val jitsiService: JitsiService,
|
||||
private val voiceMessageRecordingHelper: VoiceMessageRecordingHelper,
|
||||
timelineSettingsFactory: TimelineSettingsFactory
|
||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
|
||||
|
@ -324,6 +329,8 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
|
||||
RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages()
|
||||
RoomDetailAction.ResendAll -> handleResendAll()
|
||||
RoomDetailAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage()
|
||||
is RoomDetailAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.recordTime)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
@ -611,6 +618,22 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleStartRecordingVoiceMessage() {
|
||||
voiceMessageRecordingHelper.startRecording()
|
||||
}
|
||||
|
||||
private fun handleEndRecordingVoiceMessage(recordTime: Long) {
|
||||
if (recordTime == 0L) {
|
||||
voiceMessageRecordingHelper.deleteRecording()
|
||||
return
|
||||
}
|
||||
voiceMessageRecordingHelper.stopRecording(recordTime)?.let { audioType ->
|
||||
room.sendMedia(audioType.toContentAttachmentData(), false, emptySet())
|
||||
room
|
||||
//voiceMessageRecordingHelper.deleteRecording()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled()
|
||||
|
||||
fun isMenuItemVisible(@IdRes itemId: Int): Boolean = com.airbnb.mvrx.withState(this) { state ->
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.view.ViewGroup
|
|||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.ChangeBounds
|
||||
import androidx.transition.Fade
|
||||
|
@ -32,6 +33,7 @@ import androidx.transition.TransitionManager
|
|||
import androidx.transition.TransitionSet
|
||||
import im.vector.app.R
|
||||
import im.vector.app.databinding.ComposerLayoutBinding
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
|
||||
/**
|
||||
* Encapsulate the timeline composer UX.
|
||||
|
@ -46,6 +48,9 @@ class TextComposerView @JvmOverloads constructor(
|
|||
fun onCloseRelatedMessage()
|
||||
fun onSendMessage(text: CharSequence)
|
||||
fun onAddAttachment()
|
||||
fun onVoiceRecordingStarted()
|
||||
fun onVoiceRecordingEnded(recordTime: Long)
|
||||
fun checkVoiceRecordingPermission(): Boolean
|
||||
}
|
||||
|
||||
val views: ComposerLayoutBinding
|
||||
|
@ -71,7 +76,9 @@ class TextComposerView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
override fun onTextEmptyStateChanged(isEmpty: Boolean) {
|
||||
views.sendButton.isVisible = currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded || !isEmpty
|
||||
val shouldShowSendButton = currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded || !isEmpty
|
||||
views.sendButton.isInvisible = !shouldShowSendButton
|
||||
views.voiceMessageRecorderView.isVisible = !shouldShowSendButton
|
||||
}
|
||||
}
|
||||
views.composerRelatedMessageCloseButton.setOnClickListener {
|
||||
|
@ -87,6 +94,28 @@ 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) {
|
||||
|
@ -96,7 +125,10 @@ class TextComposerView @JvmOverloads constructor(
|
|||
}
|
||||
currentConstraintSetId = R.layout.composer_layout_constraint_set_compact
|
||||
applyNewConstraintSet(animate, transitionComplete)
|
||||
views.sendButton.isVisible = !views.composerEditText.text.isNullOrEmpty()
|
||||
|
||||
val shouldShowSendButton = !views.composerEditText.text.isNullOrEmpty()
|
||||
views.sendButton.isInvisible = !shouldShowSendButton
|
||||
views.voiceMessageRecorderView.isVisible = !shouldShowSendButton
|
||||
}
|
||||
|
||||
fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {
|
||||
|
@ -106,7 +138,8 @@ class TextComposerView @JvmOverloads constructor(
|
|||
}
|
||||
currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded
|
||||
applyNewConstraintSet(animate, transitionComplete)
|
||||
views.sendButton.isVisible = true
|
||||
views.sendButton.isInvisible = false
|
||||
views.voiceMessageRecorderView.isVisible = false
|
||||
}
|
||||
|
||||
private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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.util.AttributeSet
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.devlomi.record_view.OnRecordListener
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.databinding.ViewVoiceMessageRecorderBinding
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
|
||||
/**
|
||||
* Encapsulates the voice message recording view and animations.
|
||||
*/
|
||||
class VoiceMessageRecorderView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
interface Callback {
|
||||
fun onVoiceRecordingStarted()
|
||||
fun onVoiceRecordingEnded(recordTime: Long)
|
||||
fun checkVoiceRecordingPermission(): Boolean
|
||||
}
|
||||
|
||||
private val views: ViewVoiceMessageRecorderBinding
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.view_voice_message_recorder, this)
|
||||
views = ViewVoiceMessageRecorderBinding.bind(this)
|
||||
|
||||
views.voiceMessageButton.setRecordView(views.voiceMessageRecordView)
|
||||
views.voiceMessageRecordView.timeLimit = BuildConfig.VOICE_MESSAGE_DURATION_LIMIT_MS
|
||||
|
||||
views.voiceMessageRecordView.setRecordPermissionHandler { callback?.checkVoiceRecordingPermission().orFalse() }
|
||||
|
||||
views.voiceMessageRecordView.setOnRecordListener(object : OnRecordListener {
|
||||
override fun onStart() {
|
||||
onVoiceRecordingStarted()
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
onVoiceRecordingEnded(0)
|
||||
}
|
||||
|
||||
override fun onFinish(recordTime: Long, limitReached: Boolean) {
|
||||
onVoiceRecordingEnded(recordTime)
|
||||
}
|
||||
|
||||
override fun onLessThanSecond() {
|
||||
onVoiceRecordingEnded(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun onVoiceRecordingStarted() {
|
||||
views.voiceMessageLockBackground.isVisible = true
|
||||
views.voiceMessageLockArrow.isVisible = true
|
||||
views.voiceMessageLockImage.isVisible = true
|
||||
views.voiceMessageButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_voice_mic_recording))
|
||||
callback?.onVoiceRecordingStarted()
|
||||
}
|
||||
|
||||
private fun onVoiceRecordingEnded(recordTime: Long) {
|
||||
views.voiceMessageLockBackground.isVisible = false
|
||||
views.voiceMessageLockArrow.isVisible = false
|
||||
views.voiceMessageLockImage.isVisible = false
|
||||
views.voiceMessageButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_voice_mic))
|
||||
callback?.onVoiceRecordingEnded(recordTime)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
14
vector/src/main/res/drawable/bg_voice_message_lock.xml
Normal file
14
vector/src/main/res/drawable/bg_voice_message_lock.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<size
|
||||
android:width="56dp"
|
||||
android:height="160dp" />
|
||||
|
||||
<solid android:color="?vctr_voice_message_lock_background" />
|
||||
|
||||
<corners
|
||||
android:bottomLeftRadius="28dp"
|
||||
android:bottomRightRadius="28dp"
|
||||
android:topLeftRadius="28dp"
|
||||
android:topRightRadius="28dp" />
|
||||
</shape>
|
13
vector/src/main/res/drawable/ic_voice_lock_arrow.xml
Normal file
13
vector/src/main/res/drawable/ic_voice_lock_arrow.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,15L12,9L18,15"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#8E99A4"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
5
vector/src/main/res/drawable/ic_voice_locked.xml
Normal file
5
vector/src/main/res/drawable/ic_voice_locked.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#0DBD8B" android:fillType="evenOdd" android:pathData="M11.3333,2C8.3878,2 6,4.3878 6,7.3333V10C4.8954,10 4,10.8954 4,12V20C4,21.1046 4.8954,22 6,22H18C19.1046,22 20,21.1046 20,20V12C20,10.8954 19.1046,10 18,10V7.3333C18,4.3878 15.6122,2 12.6667,2H11.3333ZM15.3333,10V7.3333C15.3333,5.8606 14.1394,4.6667 12.6667,4.6667H11.3333C9.8606,4.6667 8.6667,5.8606 8.6667,7.3333V10H15.3333Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:autoMirrored="true" android:height="16dp"
|
||||
android:viewportHeight="16" android:viewportWidth="16"
|
||||
android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#8E99A4" android:fillType="evenOdd" android:pathData="M7.4444,0C4.9899,0 3,1.9334 3,4.3183V6.1369C2.4115,6.3926 2,6.979 2,7.6615V14.3385C2,15.2561 2.7439,16 3.6615,16H12.3385C13.2561,16 14,15.2561 14,14.3385V7.6615C14,6.7439 13.2561,6 12.3385,6H5.2222V4.3183C5.2222,3.1259 6.2171,2.1592 7.4444,2.1592H8.5556C9.7829,2.1592 10.7778,3.1259 10.7778,4.3183H13C13,1.9334 11.0102,0 8.5556,0H7.4444Z"/>
|
||||
</vector>
|
12
vector/src/main/res/drawable/ic_voice_mic.xml
Normal file
12
vector/src/main/res/drawable/ic_voice_mic.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M10.8,8.2C10.8,5.3281 13.1282,3 16,3C18.8719,3 21.2,5.3281 21.2,8.2V15.9767C21.2,18.8486 18.8719,21.1767 16,21.1767C13.1282,21.1767 10.8,18.8486 10.8,15.9767V8.2Z"
|
||||
android:fillColor="#8D99A5"/>
|
||||
<path
|
||||
android:pathData="M6.8998,14.3167C7.8203,14.3167 8.5665,15.0629 8.5665,15.9834C8.5665,20.0737 11.8818,23.3944 15.98,23.4051C15.9867,23.405 15.9934,23.4049 16.0001,23.4049C16.0068,23.4049 16.0134,23.405 16.0201,23.4051C20.1181,23.3941 23.4332,20.0735 23.4332,15.9834C23.4332,15.0629 24.1793,14.3167 25.0998,14.3167C26.0203,14.3167 26.7665,15.0629 26.7665,15.9834C26.7665,21.3586 22.8201,25.8101 17.6667,26.6103V27.6683C17.6667,28.5888 16.9206,29.335 16.0001,29.335C15.0796,29.335 14.3334,28.5888 14.3334,27.6683V26.6104C9.1798,25.8104 5.2332,21.3587 5.2332,15.9834C5.2332,15.0629 5.9794,14.3167 6.8998,14.3167Z"
|
||||
android:fillColor="#8D99A5"/>
|
||||
</vector>
|
18
vector/src/main/res/drawable/ic_voice_mic_recording.xml
Normal file
18
vector/src/main/res/drawable/ic_voice_mic_recording.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="52dp"
|
||||
android:height="52dp"
|
||||
android:viewportWidth="52"
|
||||
android:viewportHeight="52">
|
||||
<path
|
||||
android:pathData="M26.173,26.1729m-22.7631,0a22.7631,22.7631 0,1 1,45.5262 0a22.7631,22.7631 0,1 1,-45.5262 0"
|
||||
android:fillColor="#0DBD8B"/>
|
||||
<path
|
||||
android:pathData="M26,26m-26,0a26,26 0,1 1,52 0a26,26 0,1 1,-52 0"
|
||||
android:strokeAlpha="0.2"
|
||||
android:fillColor="#0DBD8B"
|
||||
android:fillAlpha="0.2"/>
|
||||
<path
|
||||
android:pathData="M21.2414,18.7749C21.2414,16.051 23.4496,13.8429 26.1734,13.8429C28.8973,13.8429 31.1054,16.051 31.1054,18.7749V26.1509C31.1054,28.8747 28.8973,31.0829 26.1734,31.0829C23.4496,31.0829 21.2414,28.8747 21.2414,26.1509V18.7749ZM17.542,24.2475C18.5968,24.2475 19.4518,25.1025 19.4518,26.1572C19.4518,29.8561 22.4509,32.8596 26.1586,32.8675C26.1637,32.8674 26.1689,32.8674 26.174,32.8674C26.179,32.8674 26.184,32.8674 26.189,32.8675C29.896,32.8589 32.8944,29.8556 32.8944,26.1572C32.8944,25.1025 33.7494,24.2475 34.8041,24.2475C35.8588,24.2475 36.7138,25.1025 36.7138,26.1572C36.7138,31.3227 32.9916,35.6165 28.0837,36.5143V37.24C28.0837,38.2947 27.2287,39.1497 26.174,39.1497C25.1193,39.1497 24.2643,38.2947 24.2643,37.24V36.5147C19.3555,35.6176 15.6323,31.3233 15.6323,26.1572C15.6323,25.1025 16.4873,24.2475 17.542,24.2475Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -0,0 +1,8 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M15,18L9,12L15,6"
|
||||
android:strokeColor="#8E99A4" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
</vector>
|
|
@ -131,4 +131,10 @@
|
|||
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" />
|
||||
|
||||
</merge>
|
||||
|
|
|
@ -178,4 +178,12 @@
|
|||
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"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
69
vector/src/main/res/layout/view_voice_message_recorder.xml
Normal file
69
vector/src/main/res/layout/view_voice_message_recorder.xml
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="2dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/voiceMessageLockBackground"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="160dp"
|
||||
android:background="@drawable/bg_voice_message_lock"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.devlomi.record_view.RecordView
|
||||
android:id="@+id/voiceMessageRecordView"
|
||||
android:layout_width="0dp"
|
||||
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"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:mic_icon="@drawable/ic_voice_mic"
|
||||
tools:ignore="MissingPrefix" />
|
||||
|
||||
<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"
|
||||
app:layout_constraintEnd_toEndOf="@id/voiceMessageLockBackground"
|
||||
app:layout_constraintStart_toStartOf="@id/voiceMessageLockBackground"
|
||||
android:layout_marginBottom="24dp"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<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"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="@id/voiceMessageLockBackground"
|
||||
app:layout_constraintStart_toStartOf="@id/voiceMessageLockBackground"
|
||||
app:layout_constraintTop_toTopOf="@id/voiceMessageLockBackground"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -132,4 +132,9 @@
|
|||
<color name="vctr_chat_effect_snow_background_light">@color/black_alpha</color>
|
||||
<color name="vctr_chat_effect_snow_background_dark">@android:color/transparent</color>
|
||||
|
||||
<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>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -3399,4 +3399,8 @@
|
|||
<string name="this_space_has_no_rooms_admin">Some rooms may be hidden because they’re private and you need an invite.</string>
|
||||
|
||||
<string name="unnamed_room">Unnamed Room</string>
|
||||
|
||||
<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>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Reference in a new issue