|
@@ -23,19 +23,15 @@
|
|
package com.nextcloud.talk.adapters.messages
|
|
package com.nextcloud.talk.adapters.messages
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.annotation.SuppressLint
|
|
-import android.app.Activity
|
|
|
|
import android.content.Context
|
|
import android.content.Context
|
|
import android.graphics.PorterDuff
|
|
import android.graphics.PorterDuff
|
|
-import android.media.MediaPlayer
|
|
|
|
-import android.net.Uri
|
|
|
|
import android.os.Handler
|
|
import android.os.Handler
|
|
import android.util.Log
|
|
import android.util.Log
|
|
import android.view.View
|
|
import android.view.View
|
|
import android.widget.SeekBar
|
|
import android.widget.SeekBar
|
|
import androidx.appcompat.content.res.AppCompatResources
|
|
import androidx.appcompat.content.res.AppCompatResources
|
|
|
|
+import androidx.core.content.ContextCompat
|
|
import androidx.core.view.ViewCompat
|
|
import androidx.core.view.ViewCompat
|
|
-import androidx.work.Data
|
|
|
|
-import androidx.work.OneTimeWorkRequest
|
|
|
|
import androidx.work.WorkInfo
|
|
import androidx.work.WorkInfo
|
|
import androidx.work.WorkManager
|
|
import androidx.work.WorkManager
|
|
import autodagger.AutoInjector
|
|
import autodagger.AutoInjector
|
|
@@ -44,25 +40,21 @@ import com.nextcloud.talk.R
|
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
|
import com.nextcloud.talk.databinding.ItemCustomOutcomingVoiceMessageBinding
|
|
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.ChatMessage
|
|
import com.nextcloud.talk.models.json.chat.ReadStatus
|
|
import com.nextcloud.talk.models.json.chat.ReadStatus
|
|
import com.nextcloud.talk.utils.ApiUtils
|
|
import com.nextcloud.talk.utils.ApiUtils
|
|
import com.nextcloud.talk.utils.DisplayUtils
|
|
import com.nextcloud.talk.utils.DisplayUtils
|
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
|
import com.stfalcon.chatkit.messages.MessageHolders
|
|
import com.stfalcon.chatkit.messages.MessageHolders
|
|
-import java.io.File
|
|
|
|
import java.util.concurrent.ExecutionException
|
|
import java.util.concurrent.ExecutionException
|
|
import javax.inject.Inject
|
|
import javax.inject.Inject
|
|
|
|
|
|
@AutoInjector(NextcloudTalkApplication::class)
|
|
@AutoInjector(NextcloudTalkApplication::class)
|
|
-class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
|
|
|
-.OutcomingTextMessageViewHolder<ChatMessage>(incomingView) {
|
|
|
|
|
|
+class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
|
|
|
|
+.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView) {
|
|
|
|
|
|
private val binding: ItemCustomOutcomingVoiceMessageBinding =
|
|
private val binding: ItemCustomOutcomingVoiceMessageBinding =
|
|
ItemCustomOutcomingVoiceMessageBinding.bind(itemView)
|
|
ItemCustomOutcomingVoiceMessageBinding.bind(itemView)
|
|
- private val realView: View = itemView
|
|
|
|
|
|
|
|
@JvmField
|
|
@JvmField
|
|
@Inject
|
|
@Inject
|
|
@@ -74,12 +66,10 @@ class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
|
|
|
|
|
lateinit var message: ChatMessage
|
|
lateinit var message: ChatMessage
|
|
|
|
|
|
- lateinit var activity: Activity
|
|
|
|
-
|
|
|
|
- var mediaPlayer: MediaPlayer? = null
|
|
|
|
-
|
|
|
|
lateinit var handler: Handler
|
|
lateinit var handler: Handler
|
|
|
|
|
|
|
|
+ lateinit var voiceMessageInterface: VoiceMessageInterface
|
|
|
|
+
|
|
@SuppressLint("SetTextI18n")
|
|
@SuppressLint("SetTextI18n")
|
|
override fun onBind(message: ChatMessage) {
|
|
override fun onBind(message: ChatMessage) {
|
|
super.onBind(message)
|
|
super.onBind(message)
|
|
@@ -94,57 +84,56 @@ class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
|
// parent message handling
|
|
// parent message handling
|
|
setParentMessageDataOnMessageItem(message)
|
|
setParentMessageDataOnMessageItem(message)
|
|
|
|
|
|
- binding.playBtn.setOnClickListener {
|
|
|
|
- openOrDownloadFile(message)
|
|
|
|
|
|
+ updateDownloadState(message)
|
|
|
|
+ binding.seekbar.max = message.voiceMessageDuration
|
|
|
|
+
|
|
|
|
+ if (message.isPlayingVoiceMessage) {
|
|
|
|
+ showPlayButton()
|
|
|
|
+ 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) {
|
|
|
|
+ showVoiceMessageLoading()
|
|
|
|
+ } else {
|
|
|
|
+ binding.progressBar.visibility = View.GONE
|
|
}
|
|
}
|
|
|
|
|
|
- activity = itemView.context as Activity
|
|
|
|
|
|
+ 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
|
|
|
|
+ }
|
|
|
|
|
|
binding.seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
|
binding.seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
|
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
|
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
|
// unused atm
|
|
// unused atm
|
|
}
|
|
}
|
|
|
|
+
|
|
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
|
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
|
// unused atm
|
|
// unused atm
|
|
}
|
|
}
|
|
|
|
+
|
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
|
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) {
|
|
val readStatusDrawableInt = when (message.readStatus) {
|
|
ReadStatus.READ -> R.drawable.ic_check_all
|
|
ReadStatus.READ -> R.drawable.ic_check_all
|
|
ReadStatus.SENT -> R.drawable.ic_check
|
|
ReadStatus.SENT -> R.drawable.ic_check
|
|
@@ -167,6 +156,56 @@ class OutcomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
|
|
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
|
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) {
|
|
|
|
+ showVoiceMessageLoading()
|
|
|
|
+ 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")
|
|
|
|
+ showVoiceMessageLoading()
|
|
|
|
+ }
|
|
|
|
+ WorkInfo.State.SUCCEEDED -> {
|
|
|
|
+ Log.d(TAG, "WorkInfo.State.SUCCEEDED in ViewHolder")
|
|
|
|
+ showPlayButton()
|
|
|
|
+ }
|
|
|
|
+ WorkInfo.State.FAILED -> {
|
|
|
|
+ Log.d(TAG, "WorkInfo.State.FAILED in ViewHolder")
|
|
|
|
+ showPlayButton()
|
|
|
|
+ }
|
|
|
|
+ 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 showPlayButton() {
|
|
|
|
+ binding.playPauseBtn.visibility = View.VISIBLE
|
|
|
|
+ binding.progressBar.visibility = View.GONE
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun showVoiceMessageLoading() {
|
|
|
|
+ binding.playPauseBtn.visibility = View.GONE
|
|
|
|
+ binding.progressBar.visibility = View.VISIBLE
|
|
|
|
+ }
|
|
|
|
+
|
|
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
|
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
|
if (!message.isDeleted && message.parentMessage != null) {
|
|
if (!message.isDeleted && message.parentMessage != null) {
|
|
val parentChatMessage = message.parentMessage
|
|
val parentChatMessage = message.parentMessage
|
|
@@ -224,156 +263,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 {
|
|
companion object {
|
|
private const val TAG = "VoiceOutMessageView"
|
|
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
|
|
private const val SEEKBAR_START: Int = 0
|
|
}
|
|
}
|
|
}
|
|
}
|