mirror of
https://github.com/nextcloud/talk-android
synced 2025-03-06 06:15:12 +00:00
move mediaPlayer and download-logic for voicemessages to ChatController
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
f9449db82a
commit
d47deb42c8
@ -29,19 +29,14 @@ import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Handler
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import autodagger.AutoInjector
|
||||
@ -51,14 +46,11 @@ import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
|
||||
import com.nextcloud.talk.models.database.CapabilitiesUtil
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import com.stfalcon.chatkit.messages.MessageHolders
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -81,9 +73,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
||||
|
||||
lateinit var activity: Activity
|
||||
|
||||
var mediaPlayer: MediaPlayer? = null
|
||||
|
||||
lateinit var handler: Handler
|
||||
lateinit var voiceMessageInterface: VoiceMessageInterface
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
@ -101,47 +91,94 @@ class IncomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
||||
// parent message handling
|
||||
setParentMessageDataOnMessageItem(message)
|
||||
|
||||
binding.playBtn.setOnClickListener {
|
||||
openOrDownloadFile(message)
|
||||
|
||||
updateDownloadState(message)
|
||||
binding.seekbar.max = message.voiceMessageDuration
|
||||
|
||||
if (message.isPlayingVoiceMessage) {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.icon =
|
||||
ContextCompat.getDrawable(context!!, R.drawable.ic_baseline_pause_voice_message_24)
|
||||
binding.seekbar.progress = message.voiceMessagePlayedSeconds
|
||||
} else {
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context!!, R.drawable
|
||||
.ic_baseline_play_arrow_voice_message_24
|
||||
)
|
||||
}
|
||||
|
||||
binding.pauseBtn.setOnClickListener {
|
||||
pausePlayback()
|
||||
if (message.isDownloadingVoiceMessage) {
|
||||
binding.playPauseBtn.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (message.resetVoiceMessage) {
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context!!, R.drawable
|
||||
.ic_baseline_play_arrow_voice_message_24
|
||||
)
|
||||
binding.seekbar.progress = SEEKBAR_START
|
||||
message.resetVoiceMessage = false
|
||||
}
|
||||
|
||||
activity = itemView.context as Activity
|
||||
|
||||
binding.seekbar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||
|
||||
binding.seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (mediaPlayer != null && fromUser) {
|
||||
mediaPlayer!!.seekTo(progress * SEEKBAR_BASE)
|
||||
if (fromUser) {
|
||||
voiceMessageInterface.updateMediaPlayerProgressBySlider(message, progress)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateDownloadState(message: ChatMessage) {
|
||||
// check if download worker is already running
|
||||
val fileId = message.getSelectedIndividualHashMap()["id"]
|
||||
val workers = WorkManager.getInstance(
|
||||
context!!
|
||||
).getWorkInfosByTag(fileId!!)
|
||||
val workers = WorkManager.getInstance(context!!).getWorkInfosByTag(fileId!!)
|
||||
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
binding.playBtn.visibility = View.GONE
|
||||
binding.playPauseBtn.visibility = View.GONE
|
||||
WorkManager.getInstance(context!!).getWorkInfoByIdLiveData(workInfo.id)
|
||||
.observeForever { info: WorkInfo? ->
|
||||
if (info != null) {
|
||||
updateViewsByProgress(
|
||||
info
|
||||
)
|
||||
|
||||
when (info.state) {
|
||||
WorkInfo.State.RUNNING -> {
|
||||
Log.d(TAG, "WorkInfo.State.RUNNING in ViewHolder")
|
||||
binding.playPauseBtn.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
}
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
Log.d(TAG, "WorkInfo.State.SUCCEEDED in ViewHolder")
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
WorkInfo.State.FAILED -> {
|
||||
Log.d(TAG, "WorkInfo.State.FAILED in ViewHolder")
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,155 +286,12 @@ class IncomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
||||
}
|
||||
}
|
||||
|
||||
private fun openOrDownloadFile(message: ChatMessage) {
|
||||
val filename = message.getSelectedIndividualHashMap()["name"]
|
||||
val file = File(context!!.cacheDir, filename!!)
|
||||
if (file.exists()) {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
startPlayback(message)
|
||||
} else {
|
||||
binding.playBtn.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
downloadFileToCache(message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPlayback(message: ChatMessage) {
|
||||
initMediaPlayer(message)
|
||||
|
||||
if (!mediaPlayer!!.isPlaying) {
|
||||
mediaPlayer!!.start()
|
||||
}
|
||||
|
||||
handler = Handler()
|
||||
activity.runOnUiThread(object : Runnable {
|
||||
override fun run() {
|
||||
if (mediaPlayer != null) {
|
||||
val currentPosition: Int = mediaPlayer!!.currentPosition / SEEKBAR_BASE
|
||||
binding.seekbar.progress = currentPosition
|
||||
}
|
||||
handler.postDelayed(this, SECOND)
|
||||
}
|
||||
})
|
||||
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.playBtn.visibility = View.GONE
|
||||
binding.pauseBtn.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun pausePlayback() {
|
||||
if (mediaPlayer!!.isPlaying) {
|
||||
mediaPlayer!!.pause()
|
||||
}
|
||||
|
||||
binding.playBtn.visibility = View.VISIBLE
|
||||
binding.pauseBtn.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun initMediaPlayer(message: ChatMessage) {
|
||||
val fileName = message.getSelectedIndividualHashMap()["name"]
|
||||
val absolutePath = context!!.cacheDir.absolutePath + "/" + fileName
|
||||
|
||||
if (mediaPlayer == null) {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setDataSource(absolutePath)
|
||||
prepare()
|
||||
}
|
||||
}
|
||||
|
||||
binding.seekbar.max = mediaPlayer!!.duration / SEEKBAR_BASE
|
||||
|
||||
mediaPlayer!!.setOnCompletionListener {
|
||||
binding.playBtn.visibility = View.VISIBLE
|
||||
binding.pauseBtn.visibility = View.GONE
|
||||
binding.seekbar.progress = SEEKBAR_START
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
mediaPlayer?.stop()
|
||||
mediaPlayer?.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private fun downloadFileToCache(message: ChatMessage) {
|
||||
val baseUrl = message.activeUser.baseUrl
|
||||
val userId = message.activeUser.userId
|
||||
val attachmentFolder = CapabilitiesUtil.getAttachmentFolder(message.activeUser)
|
||||
val fileName = message.getSelectedIndividualHashMap()["name"]
|
||||
var size = message.getSelectedIndividualHashMap()["size"]
|
||||
if (size == null) {
|
||||
size = "-1"
|
||||
}
|
||||
val fileSize = Integer.valueOf(size)
|
||||
val fileId = message.getSelectedIndividualHashMap()["id"]
|
||||
val path = message.getSelectedIndividualHashMap()["path"]
|
||||
|
||||
// check if download worker is already running
|
||||
val workers = WorkManager.getInstance(
|
||||
context!!
|
||||
).getWorkInfosByTag(fileId!!)
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
Log.d(TAG, "Download worker for " + fileId + " is already running or scheduled")
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
}
|
||||
|
||||
val data: Data = Data.Builder()
|
||||
.putString(DownloadFileToCacheWorker.KEY_BASE_URL, baseUrl)
|
||||
.putString(DownloadFileToCacheWorker.KEY_USER_ID, userId)
|
||||
.putString(DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER, attachmentFolder)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileName)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)
|
||||
.putInt(DownloadFileToCacheWorker.KEY_FILE_SIZE, fileSize)
|
||||
.build()
|
||||
|
||||
val downloadWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(DownloadFileToCacheWorker::class.java)
|
||||
.setInputData(data)
|
||||
.addTag(fileId)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueue(downloadWorker)
|
||||
|
||||
WorkManager.getInstance(context!!).getWorkInfoByIdLiveData(downloadWorker.id)
|
||||
.observeForever { workInfo: WorkInfo ->
|
||||
updateViewsByProgress(
|
||||
workInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateViewsByProgress(workInfo: WorkInfo) {
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.RUNNING -> {
|
||||
val progress = workInfo.progress.getInt(DownloadFileToCacheWorker.PROGRESS, -1)
|
||||
if (progress > -1) {
|
||||
binding.playBtn.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
startPlayback(message)
|
||||
}
|
||||
WorkInfo.State.FAILED -> {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.playBtn.visibility = View.VISIBLE
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
fun assignAdapter(voiceMessageInterface: VoiceMessageInterface) {
|
||||
this.voiceMessageInterface = voiceMessageInterface
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "VoiceInMessageView"
|
||||
private const val SECOND: Long = 1000
|
||||
private const val SEEKBAR_BASE: Int = 1000
|
||||
private const val SEEKBAR_START: Int = 0
|
||||
}
|
||||
}
|
||||
|
@ -27,15 +27,13 @@ import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.PorterDuff
|
||||
import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.SeekBar
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import autodagger.AutoInjector
|
||||
@ -44,21 +42,18 @@ import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.databinding.ItemCustomOutcomingVoiceMessageBinding
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
|
||||
import com.nextcloud.talk.models.database.CapabilitiesUtil
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.models.json.chat.ReadStatus
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import com.stfalcon.chatkit.messages.MessageHolders
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
||||
.OutcomingTextMessageViewHolder<ChatMessage>(incomingView) {
|
||||
class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
|
||||
.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView) {
|
||||
|
||||
private val binding: ItemCustomOutcomingVoiceMessageBinding =
|
||||
ItemCustomOutcomingVoiceMessageBinding.bind(itemView)
|
||||
@ -80,6 +75,8 @@ class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
||||
|
||||
lateinit var handler: Handler
|
||||
|
||||
lateinit var voiceMessageInterface: VoiceMessageInterface
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
@ -94,57 +91,59 @@ class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
||||
// parent message handling
|
||||
setParentMessageDataOnMessageItem(message)
|
||||
|
||||
binding.playBtn.setOnClickListener {
|
||||
openOrDownloadFile(message)
|
||||
updateDownloadState(message)
|
||||
binding.seekbar.max = message.voiceMessageDuration
|
||||
|
||||
if (message.isPlayingVoiceMessage) {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.icon =
|
||||
ContextCompat.getDrawable(context!!, R.drawable.ic_baseline_pause_voice_message_24)
|
||||
binding.seekbar.progress = message.voiceMessagePlayedSeconds
|
||||
} else {
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context!!, R.drawable
|
||||
.ic_baseline_play_arrow_voice_message_24
|
||||
)
|
||||
}
|
||||
|
||||
binding.pauseBtn.setOnClickListener {
|
||||
pausePlayback()
|
||||
if (message.isDownloadingVoiceMessage) {
|
||||
binding.playPauseBtn.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (message.resetVoiceMessage) {
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context!!, R.drawable
|
||||
.ic_baseline_play_arrow_voice_message_24
|
||||
)
|
||||
binding.seekbar.progress = SEEKBAR_START
|
||||
message.resetVoiceMessage = false
|
||||
}
|
||||
|
||||
activity = itemView.context as Activity
|
||||
|
||||
|
||||
binding.seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (mediaPlayer != null && fromUser) {
|
||||
mediaPlayer!!.seekTo(progress * SEEKBAR_BASE)
|
||||
if (fromUser) {
|
||||
voiceMessageInterface.updateMediaPlayerProgressBySlider(message, progress)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// check if download worker is already running
|
||||
val fileId = message.getSelectedIndividualHashMap()["id"]
|
||||
val workers = WorkManager.getInstance(
|
||||
context!!
|
||||
).getWorkInfosByTag(fileId!!)
|
||||
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
binding.playBtn.visibility = View.GONE
|
||||
WorkManager.getInstance(context!!).getWorkInfoByIdLiveData(workInfo.id)
|
||||
.observeForever { info: WorkInfo? ->
|
||||
if (info != null) {
|
||||
updateViewsByProgress(
|
||||
info
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
}
|
||||
|
||||
val readStatusDrawableInt = when (message.readStatus) {
|
||||
ReadStatus.READ -> R.drawable.ic_check_all
|
||||
ReadStatus.SENT -> R.drawable.ic_check
|
||||
@ -167,6 +166,50 @@ class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
||||
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
||||
}
|
||||
|
||||
private fun updateDownloadState(message: ChatMessage) {
|
||||
// check if download worker is already running
|
||||
val fileId = message.getSelectedIndividualHashMap()["id"]
|
||||
val workers = WorkManager.getInstance(context!!).getWorkInfosByTag(fileId!!)
|
||||
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.visibility = View.GONE
|
||||
WorkManager.getInstance(context!!).getWorkInfoByIdLiveData(workInfo.id)
|
||||
.observeForever { info: WorkInfo? ->
|
||||
if (info != null) {
|
||||
|
||||
when (info.state) {
|
||||
WorkInfo.State.RUNNING -> {
|
||||
Log.d(TAG, "WorkInfo.State.RUNNING in ViewHolder")
|
||||
binding.playPauseBtn.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
}
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
Log.d(TAG, "WorkInfo.State.SUCCEEDED in ViewHolder")
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
WorkInfo.State.FAILED -> {
|
||||
Log.d(TAG, "WorkInfo.State.FAILED in ViewHolder")
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (!message.isDeleted && message.parentMessage != null) {
|
||||
val parentChatMessage = message.parentMessage
|
||||
@ -224,156 +267,12 @@ class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
||||
}
|
||||
}
|
||||
|
||||
private fun openOrDownloadFile(message: ChatMessage) {
|
||||
val filename = message.getSelectedIndividualHashMap()["name"]
|
||||
val file = File(context!!.cacheDir, filename!!)
|
||||
|
||||
if (file.exists()) {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
startPlayback(message)
|
||||
} else {
|
||||
binding.playBtn.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
downloadFileToCache(message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPlayback(message: ChatMessage) {
|
||||
initMediaPlayer(message)
|
||||
|
||||
if (!mediaPlayer!!.isPlaying) {
|
||||
mediaPlayer!!.start()
|
||||
}
|
||||
|
||||
handler = Handler()
|
||||
activity.runOnUiThread(object : Runnable {
|
||||
override fun run() {
|
||||
if (mediaPlayer != null) {
|
||||
val currentPosition: Int = mediaPlayer!!.currentPosition / SEEKBAR_BASE
|
||||
binding.seekbar.progress = currentPosition
|
||||
}
|
||||
handler.postDelayed(this, SECOND)
|
||||
}
|
||||
})
|
||||
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.playBtn.visibility = View.GONE
|
||||
binding.pauseBtn.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun pausePlayback() {
|
||||
if (mediaPlayer!!.isPlaying) {
|
||||
mediaPlayer!!.pause()
|
||||
}
|
||||
|
||||
binding.playBtn.visibility = View.VISIBLE
|
||||
binding.pauseBtn.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun initMediaPlayer(message: ChatMessage) {
|
||||
val fileName = message.getSelectedIndividualHashMap()["name"]
|
||||
val absolutePath = context!!.cacheDir.absolutePath + "/" + fileName
|
||||
|
||||
if (mediaPlayer == null) {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setDataSource(context!!, Uri.parse(absolutePath))
|
||||
prepare()
|
||||
}
|
||||
}
|
||||
|
||||
binding.seekbar.max = mediaPlayer!!.duration / SEEKBAR_BASE
|
||||
|
||||
mediaPlayer!!.setOnCompletionListener {
|
||||
binding.playBtn.visibility = View.VISIBLE
|
||||
binding.pauseBtn.visibility = View.GONE
|
||||
binding.seekbar.progress = SEEKBAR_START
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
mediaPlayer?.stop()
|
||||
mediaPlayer?.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private fun downloadFileToCache(message: ChatMessage) {
|
||||
val baseUrl = message.activeUser.baseUrl
|
||||
val userId = message.activeUser.userId
|
||||
val attachmentFolder = CapabilitiesUtil.getAttachmentFolder(message.activeUser)
|
||||
val fileName = message.getSelectedIndividualHashMap()["name"]
|
||||
var size = message.getSelectedIndividualHashMap()["size"]
|
||||
if (size == null) {
|
||||
size = "-1"
|
||||
}
|
||||
val fileSize = Integer.valueOf(size)
|
||||
val fileId = message.getSelectedIndividualHashMap()["id"]
|
||||
val path = message.getSelectedIndividualHashMap()["path"]
|
||||
|
||||
// check if download worker is already running
|
||||
val workers = WorkManager.getInstance(
|
||||
context!!
|
||||
).getWorkInfosByTag(fileId!!)
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
Log.d(TAG, "Download worker for " + fileId + " is already running or scheduled")
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
}
|
||||
|
||||
val data: Data = Data.Builder()
|
||||
.putString(DownloadFileToCacheWorker.KEY_BASE_URL, baseUrl)
|
||||
.putString(DownloadFileToCacheWorker.KEY_USER_ID, userId)
|
||||
.putString(DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER, attachmentFolder)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileName)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)
|
||||
.putInt(DownloadFileToCacheWorker.KEY_FILE_SIZE, fileSize)
|
||||
.build()
|
||||
|
||||
val downloadWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(DownloadFileToCacheWorker::class.java)
|
||||
.setInputData(data)
|
||||
.addTag(fileId)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueue(downloadWorker)
|
||||
|
||||
WorkManager.getInstance(context!!).getWorkInfoByIdLiveData(downloadWorker.id)
|
||||
.observeForever { workInfo: WorkInfo ->
|
||||
updateViewsByProgress(
|
||||
workInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateViewsByProgress(workInfo: WorkInfo) {
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.RUNNING -> {
|
||||
val progress = workInfo.progress.getInt(DownloadFileToCacheWorker.PROGRESS, -1)
|
||||
if (progress > -1) {
|
||||
binding.playBtn.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
startPlayback(message)
|
||||
}
|
||||
WorkInfo.State.FAILED -> {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.playBtn.visibility = View.VISIBLE
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
fun assignAdapter(voiceMessageInterface: VoiceMessageInterface) {
|
||||
this.voiceMessageInterface = voiceMessageInterface
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "VoiceOutMessageView"
|
||||
private const val SECOND: Long = 1000
|
||||
private const val SEEKBAR_BASE: Int = 1000
|
||||
private const val SEEKBAR_START: Int = 0
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,9 @@
|
||||
|
||||
package com.nextcloud.talk.adapters.messages;
|
||||
|
||||
import com.nextcloud.talk.controllers.ChatController;
|
||||
import com.stfalcon.chatkit.commons.ImageLoader;
|
||||
import com.stfalcon.chatkit.commons.ViewHolder;
|
||||
import com.stfalcon.chatkit.commons.models.IMessage;
|
||||
import com.stfalcon.chatkit.messages.MessageHolders;
|
||||
import com.stfalcon.chatkit.messages.MessagesListAdapter;
|
||||
@ -28,12 +30,32 @@ import com.stfalcon.chatkit.messages.MessagesListAdapter;
|
||||
import java.util.List;
|
||||
|
||||
public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAdapter<M> {
|
||||
private final ChatController chatController;
|
||||
|
||||
public TalkMessagesListAdapter(String senderId, MessageHolders holders, ImageLoader imageLoader) {
|
||||
public TalkMessagesListAdapter(
|
||||
String senderId,
|
||||
MessageHolders holders,
|
||||
ImageLoader imageLoader,
|
||||
ChatController chatController) {
|
||||
super(senderId, holders, imageLoader);
|
||||
this.chatController = chatController;
|
||||
}
|
||||
|
||||
public List<MessagesListAdapter.Wrapper> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
|
||||
if (holder instanceof IncomingVoiceMessageViewHolder) {
|
||||
((IncomingVoiceMessageViewHolder) holder).assignAdapter(chatController);
|
||||
}
|
||||
|
||||
if (holder instanceof OutcomingVoiceMessageViewHolder) {
|
||||
((OutcomingVoiceMessageViewHolder) holder).assignAdapter(chatController);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package com.nextcloud.talk.adapters.messages
|
||||
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
|
||||
interface VoiceMessageInterface {
|
||||
fun updateMediaPlayerProgressBySlider(message : ChatMessage, progress : Int)
|
||||
}
|
@ -34,6 +34,7 @@ import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.media.MediaPlayer
|
||||
import android.media.MediaRecorder
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@ -76,6 +77,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import autodagger.AutoInjector
|
||||
import coil.load
|
||||
@ -102,6 +104,7 @@ import com.nextcloud.talk.adapters.messages.OutcomingLocationMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter
|
||||
import com.nextcloud.talk.adapters.messages.VoiceMessageInterface
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
|
||||
@ -112,6 +115,7 @@ import com.nextcloud.talk.controllers.util.viewBinding
|
||||
import com.nextcloud.talk.databinding.ControllerChatBinding
|
||||
import com.nextcloud.talk.events.UserMentionClickEvent
|
||||
import com.nextcloud.talk.events.WebSocketCommunicationEvent
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
|
||||
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
|
||||
import com.nextcloud.talk.models.database.CapabilitiesUtil
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
@ -175,6 +179,7 @@ import java.util.ArrayList
|
||||
import java.util.Date
|
||||
import java.util.HashMap
|
||||
import java.util.Objects
|
||||
import java.util.concurrent.ExecutionException
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
@ -186,7 +191,8 @@ class ChatController(args: Bundle) :
|
||||
MessagesListAdapter.OnLoadMoreListener,
|
||||
MessagesListAdapter.Formatter<Date>,
|
||||
MessagesListAdapter.OnMessageViewLongClickListener<IMessage>,
|
||||
ContentChecker<ChatMessage> {
|
||||
ContentChecker<ChatMessage>, VoiceMessageInterface {
|
||||
|
||||
private val binding: ControllerChatBinding by viewBinding(ControllerChatBinding::bind)
|
||||
|
||||
@Inject
|
||||
@ -247,6 +253,10 @@ class ChatController(args: Bundle) :
|
||||
|
||||
private var recorder: MediaRecorder? = null
|
||||
|
||||
var mediaPlayer: MediaPlayer? = null
|
||||
lateinit var mediaPlayerHandler: Handler
|
||||
var currentlyPlayedVoiceMessage: ChatMessage? = null
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
@ -488,7 +498,7 @@ class ChatController(args: Bundle) :
|
||||
.setAutoPlayAnimations(true)
|
||||
.build()
|
||||
imageView.controller = draweeController
|
||||
}
|
||||
}, this
|
||||
)
|
||||
} else {
|
||||
binding.messagesListView.visibility = View.VISIBLE
|
||||
@ -499,6 +509,22 @@ class ChatController(args: Bundle) :
|
||||
adapter?.setDateHeadersFormatter { format(it) }
|
||||
adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) }
|
||||
|
||||
adapter?.registerViewClickListener(
|
||||
R.id.playPauseBtn
|
||||
) { view, message ->
|
||||
val filename = message.getSelectedIndividualHashMap()["name"]
|
||||
val file = File(context!!.cacheDir, filename!!)
|
||||
if (file.exists()) {
|
||||
if (message.isPlayingVoiceMessage) {
|
||||
pausePlayback(message)
|
||||
} else {
|
||||
startPlayback(message)
|
||||
}
|
||||
} else {
|
||||
downloadFileToCache(message)
|
||||
}
|
||||
}
|
||||
|
||||
if (context != null) {
|
||||
val messageSwipeController = MessageSwipeCallback(
|
||||
activity!!,
|
||||
@ -749,6 +775,151 @@ class ChatController(args: Bundle) :
|
||||
super.onViewBound(view)
|
||||
}
|
||||
|
||||
private fun startPlayback(message: ChatMessage) {
|
||||
|
||||
if (!this.isAttached) {
|
||||
// don't begin to play voice message if screen is not visible anymore.
|
||||
// this situation might happen if file is downloading but user already left the chatview.
|
||||
// If user returns to chatview, the old chatview instance is not attached anymore
|
||||
// and he has to click the play button again (which is considered to be okay)
|
||||
return
|
||||
}
|
||||
|
||||
initMediaPlayer(message)
|
||||
|
||||
if (!mediaPlayer!!.isPlaying) {
|
||||
mediaPlayer!!.start()
|
||||
}
|
||||
|
||||
mediaPlayerHandler = Handler()
|
||||
activity?.runOnUiThread(object : Runnable {
|
||||
override fun run() {
|
||||
if (mediaPlayer != null) {
|
||||
val currentPosition: Int = mediaPlayer!!.currentPosition / VOICE_MESSAGE_SEEKBAR_BASE
|
||||
message.voiceMessagePlayedSeconds = currentPosition
|
||||
adapter?.update(message)
|
||||
}
|
||||
mediaPlayerHandler.postDelayed(this, SECOND)
|
||||
}
|
||||
})
|
||||
|
||||
message.isDownloadingVoiceMessage = false
|
||||
message.isPlayingVoiceMessage = true
|
||||
adapter?.update(message)
|
||||
}
|
||||
|
||||
private fun pausePlayback(message: ChatMessage) {
|
||||
if (mediaPlayer!!.isPlaying) {
|
||||
mediaPlayer!!.pause()
|
||||
}
|
||||
|
||||
message.isPlayingVoiceMessage = false
|
||||
adapter?.update(message)
|
||||
}
|
||||
|
||||
private fun initMediaPlayer(message: ChatMessage) {
|
||||
if (message != currentlyPlayedVoiceMessage) {
|
||||
currentlyPlayedVoiceMessage?.let { stopMediaPlayer(it) }
|
||||
}
|
||||
|
||||
if (mediaPlayer == null) {
|
||||
val fileName = message.getSelectedIndividualHashMap()["name"]
|
||||
val absolutePath = context!!.cacheDir.absolutePath + "/" + fileName
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setDataSource(absolutePath)
|
||||
prepare()
|
||||
}
|
||||
currentlyPlayedVoiceMessage = message
|
||||
message.voiceMessageDuration = mediaPlayer!!.duration / VOICE_MESSAGE_SEEKBAR_BASE
|
||||
|
||||
mediaPlayer!!.setOnCompletionListener {
|
||||
stopMediaPlayer(message)
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "mediaPlayer was not null. This should not happen!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopMediaPlayer(message: ChatMessage) {
|
||||
message.isPlayingVoiceMessage = false
|
||||
message.resetVoiceMessage = true
|
||||
adapter?.update(message)
|
||||
|
||||
currentlyPlayedVoiceMessage = null
|
||||
|
||||
mediaPlayerHandler.removeCallbacksAndMessages(null)
|
||||
|
||||
mediaPlayer?.stop()
|
||||
mediaPlayer?.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
|
||||
override fun updateMediaPlayerProgressBySlider(messageWithSlidedProgress: ChatMessage, progress: Int) {
|
||||
if (mediaPlayer != null) {
|
||||
if (messageWithSlidedProgress == currentlyPlayedVoiceMessage) {
|
||||
mediaPlayer!!.seekTo(progress * VOICE_MESSAGE_SEEKBAR_BASE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private fun downloadFileToCache(message: ChatMessage) {
|
||||
message.isDownloadingVoiceMessage = true
|
||||
adapter?.update(message)
|
||||
|
||||
val baseUrl = message.activeUser.baseUrl
|
||||
val userId = message.activeUser.userId
|
||||
val attachmentFolder = CapabilitiesUtil.getAttachmentFolder(message.activeUser)
|
||||
val fileName = message.getSelectedIndividualHashMap()["name"]
|
||||
var size = message.getSelectedIndividualHashMap()["size"]
|
||||
if (size == null) {
|
||||
size = "-1"
|
||||
}
|
||||
val fileSize = Integer.valueOf(size)
|
||||
val fileId = message.getSelectedIndividualHashMap()["id"]
|
||||
val path = message.getSelectedIndividualHashMap()["path"]
|
||||
|
||||
// check if download worker is already running
|
||||
val workers = WorkManager.getInstance(
|
||||
context!!
|
||||
).getWorkInfosByTag(fileId!!)
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
Log.d(TAG, "Download worker for " + fileId + " is already running or scheduled")
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
}
|
||||
|
||||
val data: Data = Data.Builder()
|
||||
.putString(DownloadFileToCacheWorker.KEY_BASE_URL, baseUrl)
|
||||
.putString(DownloadFileToCacheWorker.KEY_USER_ID, userId)
|
||||
.putString(DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER, attachmentFolder)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileName)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)
|
||||
.putInt(DownloadFileToCacheWorker.KEY_FILE_SIZE, fileSize)
|
||||
.build()
|
||||
|
||||
val downloadWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(DownloadFileToCacheWorker::class.java)
|
||||
.setInputData(data)
|
||||
.addTag(fileId)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueue(downloadWorker)
|
||||
|
||||
WorkManager.getInstance(context!!).getWorkInfoByIdLiveData(downloadWorker.id)
|
||||
.observeForever { workInfo: WorkInfo ->
|
||||
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
|
||||
startPlayback(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private fun setVoiceRecordFileName() {
|
||||
val pattern = "yyyy-MM-dd HH-mm-ss"
|
||||
@ -1265,6 +1436,8 @@ class ChatController(args: Bundle) :
|
||||
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
||||
mentionAutocomplete?.dismissPopup()
|
||||
}
|
||||
|
||||
currentlyPlayedVoiceMessage?.let { stopMediaPlayer(it) }
|
||||
}
|
||||
|
||||
override val title: String
|
||||
@ -2345,5 +2518,7 @@ class ChatController(args: Bundle) :
|
||||
private const val SHORT_VIBRATE: Long = 20
|
||||
private const val FULLY_OPAQUE_INT: Int = 255
|
||||
private const val SEMI_TRANSPARENT_INT: Int = 99
|
||||
private const val VOICE_MESSAGE_SEEKBAR_BASE: Int = 1000
|
||||
private const val SECOND: Long = 1000
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,22 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
@JsonField(name = "messageType")
|
||||
public String messageType;
|
||||
|
||||
|
||||
public boolean isDownloadingVoiceMessage;
|
||||
public boolean resetVoiceMessage;
|
||||
public boolean isPlayingVoiceMessage;
|
||||
public int voiceMessageDuration;
|
||||
public int voiceMessagePlayedSeconds;
|
||||
public VoiceMessageDownloadState voiceMessageDownloadState;
|
||||
public int voiceMessageDownloadProgress;
|
||||
|
||||
public enum VoiceMessageDownloadState {
|
||||
NOT_STARTED,
|
||||
RUNNING,
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
List<MessageType> messageTypesToIgnore = Arrays.asList(
|
||||
MessageType.REGULAR_TEXT_MESSAGE,
|
||||
@ -100,6 +116,8 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE,
|
||||
MessageType.VOICE_MESSAGE);
|
||||
|
||||
|
||||
|
||||
public boolean hasFileAttachment() {
|
||||
if (messageParameters != null && messageParameters.size() > 0) {
|
||||
for (HashMap.Entry<String, HashMap<String, String>> entry : messageParameters.entrySet()) {
|
||||
@ -133,8 +151,6 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
@Nullable
|
||||
@Override
|
||||
public String getImageUrl() {
|
||||
|
||||
|
||||
if (messageParameters != null && messageParameters.size() > 0) {
|
||||
for (HashMap.Entry<String, HashMap<String, String>> entry : messageParameters.entrySet()) {
|
||||
Map<String, String> individualHashMap = entry.getValue();
|
||||
|
@ -79,7 +79,7 @@
|
||||
android:visibility="gone"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/playBtn"
|
||||
android:id="@+id/playPauseBtn"
|
||||
style="@style/Widget.AppTheme.Button.IconButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
@ -90,18 +90,6 @@
|
||||
app:iconSize="40dp"
|
||||
app:iconTint="@color/nc_incoming_text_default" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pauseBtn"
|
||||
style="@style/Widget.AppTheme.Button.IconButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:contentDescription="@string/pause_voice_message"
|
||||
android:visibility="gone"
|
||||
app:cornerRadius="@dimen/button_corner_radius"
|
||||
app:icon="@drawable/ic_baseline_pause_voice_message_24"
|
||||
app:iconSize="40dp"
|
||||
app:iconTint="@color/nc_incoming_text_default" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekbar"
|
||||
android:layout_width="200dp"
|
||||
|
@ -63,7 +63,7 @@
|
||||
android:visibility="gone"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/playBtn"
|
||||
android:id="@+id/playPauseBtn"
|
||||
style="@style/Widget.AppTheme.Button.IconButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
@ -75,19 +75,6 @@
|
||||
app:iconSize="40dp"
|
||||
app:iconTint="@color/nc_outcoming_text_default" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pauseBtn"
|
||||
style="@style/Widget.AppTheme.Button.IconButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:contentDescription="@string/pause_voice_message"
|
||||
android:visibility="gone"
|
||||
app:rippleColor="#1FFFFFFF"
|
||||
app:cornerRadius="@dimen/button_corner_radius"
|
||||
app:icon="@drawable/ic_baseline_pause_voice_message_24"
|
||||
app:iconSize="40dp"
|
||||
app:iconTint="@color/nc_outcoming_text_default" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekbar"
|
||||
style="@style/Nextcloud.Material.Outgoing.SeekBar"
|
||||
|
Loading…
Reference in New Issue
Block a user