mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-19 19:49:33 +01:00
Merge pull request #4579 from nextcloud/issue-4563-abstract-away-media-player
Abstracting away media player, took a bit longer cause of broken GH actions
This commit is contained in:
commit
9b8faf1191
@ -36,7 +36,9 @@ import com.nextcloud.talk.utils.preferences.AppPreferences
|
|||||||
import com.stfalcon.chatkit.messages.MessageHolders
|
import com.stfalcon.chatkit.messages.MessageHolders
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
@ -61,9 +63,8 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var dateUtils: DateUtils
|
lateinit var dateUtils: DateUtils
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@Inject
|
@Inject
|
||||||
var appPreferences: AppPreferences? = null
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
lateinit var message: ChatMessage
|
lateinit var message: ChatMessage
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||||||
sharedApplication!!.componentApplication.inject(this)
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
|
|
||||||
val filename = message.selectedIndividualHashMap!!["name"]
|
val filename = message.selectedIndividualHashMap!!["name"]
|
||||||
val retrieved = appPreferences!!.getWaveFormFromFile(filename)
|
val retrieved = appPreferences.getWaveFormFromFile(filename)
|
||||||
if (retrieved.isNotEmpty() &&
|
if (retrieved.isNotEmpty() &&
|
||||||
message.voiceMessageFloatArray == null ||
|
message.voiceMessageFloatArray == null ||
|
||||||
message.voiceMessageFloatArray?.isEmpty() == true
|
message.voiceMessageFloatArray?.isEmpty() == true
|
||||||
@ -103,7 +104,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||||||
setParentMessageDataOnMessageItem(message)
|
setParentMessageDataOnMessageItem(message)
|
||||||
|
|
||||||
updateDownloadState(message)
|
updateDownloadState(message)
|
||||||
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
|
binding.seekbar.max = MAX
|
||||||
viewThemeUtils.talk.themeWaveFormSeekBar(binding.seekbar)
|
viewThemeUtils.talk.themeWaveFormSeekBar(binding.seekbar)
|
||||||
viewThemeUtils.platform.colorCircularProgressBar(binding.progressBar, ColorRole.ON_SURFACE_VARIANT)
|
viewThemeUtils.platform.colorCircularProgressBar(binding.progressBar, ColorRole.ON_SURFACE_VARIANT)
|
||||||
|
|
||||||
@ -139,9 +140,15 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
voiceMessageInterface.registerMessageToObservePlaybackSpeedPreferences(message.user.id) { speed ->
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
(voiceMessageInterface as ChatActivity).chatViewModel.voiceMessagePlayBackUIFlow.onEach { speed ->
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
binding.playbackSpeedControlBtn.setSpeed(speed)
|
binding.playbackSpeedControlBtn.setSpeed(speed)
|
||||||
}
|
}
|
||||||
|
}.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.playbackSpeedControlBtn.setSpeed(appPreferences.getPreferredPlayback(message.actorId))
|
||||||
|
|
||||||
Reaction().showReactions(
|
Reaction().showReactions(
|
||||||
message,
|
message,
|
||||||
@ -158,9 +165,6 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||||||
|
|
||||||
private fun showVoiceMessageDuration(message: ChatMessage) {
|
private fun showVoiceMessageDuration(message: ChatMessage) {
|
||||||
if (message.voiceMessageDuration > 0) {
|
if (message.voiceMessageDuration > 0) {
|
||||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(
|
|
||||||
message.voiceMessageDuration.toLong()
|
|
||||||
)
|
|
||||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
binding.voiceMessageDuration.visibility = View.INVISIBLE
|
binding.voiceMessageDuration.visibility = View.INVISIBLE
|
||||||
@ -200,7 +204,6 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||||||
val t = message.voiceMessagePlayedSeconds.toLong()
|
val t = message.voiceMessagePlayedSeconds.toLong()
|
||||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
|
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
|
||||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||||
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
|
|
||||||
binding.seekbar.progress = message.voiceMessageSeekbarProgress
|
binding.seekbar.progress = message.voiceMessageSeekbarProgress
|
||||||
} else {
|
} else {
|
||||||
showVoiceMessageDuration(message)
|
showVoiceMessageDuration(message)
|
||||||
@ -372,6 +375,6 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "VoiceInMessageView"
|
private const val TAG = "VoiceInMessageView"
|
||||||
private const val SEEKBAR_START: Int = 0
|
private const val SEEKBAR_START: Int = 0
|
||||||
private const val ONE_SEC: Int = 1000
|
private const val MAX: Int = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,9 @@ import com.nextcloud.talk.utils.preferences.AppPreferences
|
|||||||
import com.stfalcon.chatkit.messages.MessageHolders
|
import com.stfalcon.chatkit.messages.MessageHolders
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
@ -65,9 +67,8 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var dateUtils: DateUtils
|
lateinit var dateUtils: DateUtils
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@Inject
|
@Inject
|
||||||
var appPreferences: AppPreferences? = null
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
lateinit var message: ChatMessage
|
lateinit var message: ChatMessage
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||||||
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
|
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
|
||||||
|
|
||||||
val filename = message.selectedIndividualHashMap!!["name"]
|
val filename = message.selectedIndividualHashMap!!["name"]
|
||||||
val retrieved = appPreferences!!.getWaveFormFromFile(filename)
|
val retrieved = appPreferences.getWaveFormFromFile(filename)
|
||||||
if (retrieved.isNotEmpty() &&
|
if (retrieved.isNotEmpty() &&
|
||||||
message.voiceMessageFloatArray == null ||
|
message.voiceMessageFloatArray == null ||
|
||||||
message.voiceMessageFloatArray?.isEmpty() == true
|
message.voiceMessageFloatArray?.isEmpty() == true
|
||||||
@ -99,6 +100,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||||||
binding.seekbar.setWaveData(message.voiceMessageFloatArray!!)
|
binding.seekbar.setWaveData(message.voiceMessageFloatArray!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.seekbar.max = MAX
|
||||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||||
|
|
||||||
colorizeMessageBubble(message)
|
colorizeMessageBubble(message)
|
||||||
@ -136,9 +138,15 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||||||
|
|
||||||
setReadStatus(message.readStatus)
|
setReadStatus(message.readStatus)
|
||||||
|
|
||||||
voiceMessageInterface.registerMessageToObservePlaybackSpeedPreferences(message.user.id) { speed ->
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
(voiceMessageInterface as ChatActivity).chatViewModel.voiceMessagePlayBackUIFlow.onEach { speed ->
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
binding.playbackSpeedControlBtn.setSpeed(speed)
|
binding.playbackSpeedControlBtn.setSpeed(speed)
|
||||||
}
|
}
|
||||||
|
}.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.playbackSpeedControlBtn.setSpeed(appPreferences.getPreferredPlayback(message.actorId))
|
||||||
|
|
||||||
Reaction().showReactions(
|
Reaction().showReactions(
|
||||||
message,
|
message,
|
||||||
@ -199,9 +207,6 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||||||
|
|
||||||
private fun showVoiceMessageDuration(message: ChatMessage) {
|
private fun showVoiceMessageDuration(message: ChatMessage) {
|
||||||
if (message.voiceMessageDuration > 0) {
|
if (message.voiceMessageDuration > 0) {
|
||||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(
|
|
||||||
message.voiceMessageDuration.toLong()
|
|
||||||
)
|
|
||||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
binding.voiceMessageDuration.visibility = View.INVISIBLE
|
binding.voiceMessageDuration.visibility = View.INVISIBLE
|
||||||
@ -234,7 +239,6 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||||||
val t = message.voiceMessagePlayedSeconds.toLong()
|
val t = message.voiceMessagePlayedSeconds.toLong()
|
||||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
|
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
|
||||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||||
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
|
|
||||||
binding.seekbar.progress = message.voiceMessageSeekbarProgress
|
binding.seekbar.progress = message.voiceMessageSeekbarProgress
|
||||||
} else {
|
} else {
|
||||||
showVoiceMessageDuration(message)
|
showVoiceMessageDuration(message)
|
||||||
@ -377,6 +381,6 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "VoiceOutMessageView"
|
private const val TAG = "VoiceOutMessageView"
|
||||||
private const val SEEKBAR_START: Int = 0
|
private const val SEEKBAR_START: Int = 0
|
||||||
private const val ONE_SEC: Int = 1000
|
private const val MAX = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ import android.database.Cursor
|
|||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.media.MediaMetadataRetriever
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -332,24 +330,12 @@ class ChatActivity :
|
|||||||
private val filesToUpload: MutableList<String> = ArrayList()
|
private val filesToUpload: MutableList<String> = ArrayList()
|
||||||
lateinit var sharedText: String
|
lateinit var sharedText: String
|
||||||
|
|
||||||
var mediaPlayer: MediaPlayer? = null
|
|
||||||
var mediaPlayerHandler: Handler? = null
|
|
||||||
|
|
||||||
private var currentlyPlayedVoiceMessage: ChatMessage? = null
|
|
||||||
|
|
||||||
// messy workaround for a mediaPlayer bug, don't delete
|
|
||||||
private var lastRecordMediaPosition: Int = 0
|
|
||||||
private var lastRecordedSeeked: Boolean = false
|
|
||||||
|
|
||||||
lateinit var participantPermissions: ParticipantPermissions
|
lateinit var participantPermissions: ParticipantPermissions
|
||||||
|
|
||||||
private var videoURI: Uri? = null
|
private var videoURI: Uri? = null
|
||||||
|
|
||||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
if (currentlyPlayedVoiceMessage != null) {
|
|
||||||
stopMediaPlayer(currentlyPlayedVoiceMessage!!)
|
|
||||||
}
|
|
||||||
val intent = Intent(this@ChatActivity, ConversationsListActivity::class.java)
|
val intent = Intent(this@ChatActivity, ConversationsListActivity::class.java)
|
||||||
intent.putExtras(Bundle())
|
intent.putExtras(Bundle())
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
@ -361,22 +347,6 @@ class ChatActivity :
|
|||||||
val typingParticipants = HashMap<String, TypingParticipant>()
|
val typingParticipants = HashMap<String, TypingParticipant>()
|
||||||
|
|
||||||
var callStarted = false
|
var callStarted = false
|
||||||
private var voiceMessageToRestoreId = ""
|
|
||||||
private var voiceMessageToRestoreAudioPosition = 0
|
|
||||||
private var voiceMessageToRestoreWasPlaying = false
|
|
||||||
|
|
||||||
private val playbackSpeedPreferencesObserver: (Map<String, PlaybackSpeed>) -> Unit = { speedPreferenceLiveData ->
|
|
||||||
mediaPlayer?.let { mediaPlayer ->
|
|
||||||
(mediaPlayer.isPlaying == true).also {
|
|
||||||
currentlyPlayedVoiceMessage?.let { message ->
|
|
||||||
mediaPlayer.playbackParams.let { params ->
|
|
||||||
params.setSpeed(chatViewModel.getPlaybackSpeedPreference(message).value)
|
|
||||||
mediaPlayer.playbackParams = params
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener {
|
private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener {
|
||||||
override fun onSwitchTo(token: String?) {
|
override fun onSwitchTo(token: String?) {
|
||||||
@ -458,35 +428,7 @@ class ChatActivity :
|
|||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||||
|
|
||||||
appPreferences.readVoiceMessagePlaybackSpeedPreferences().let { playbackSpeedPreferences ->
|
|
||||||
chatViewModel.applyPlaybackSpeedPreferences(playbackSpeedPreferences)
|
|
||||||
}
|
|
||||||
|
|
||||||
initObservers()
|
initObservers()
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
// Restore value of members from saved state
|
|
||||||
var voiceMessageId = savedInstanceState.getString(CURRENT_AUDIO_MESSAGE_KEY, "")
|
|
||||||
var voiceMessagePosition = savedInstanceState.getInt(CURRENT_AUDIO_POSITION_KEY, 0)
|
|
||||||
var wasAudioPLaying = savedInstanceState.getBoolean(CURRENT_AUDIO_WAS_PLAYING_KEY, false)
|
|
||||||
if (!voiceMessageId.equals("")) {
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "restored voice messageID: " + voiceMessageId)
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "audio position: " + voiceMessagePosition)
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "audio was playing: " + wasAudioPLaying.toString())
|
|
||||||
voiceMessageToRestoreId = voiceMessageId
|
|
||||||
voiceMessageToRestoreAudioPosition = voiceMessagePosition
|
|
||||||
voiceMessageToRestoreWasPlaying = wasAudioPLaying
|
|
||||||
} else {
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "stored voice message id is empty, not resuming audio playing")
|
|
||||||
voiceMessageToRestoreId = ""
|
|
||||||
voiceMessageToRestoreAudioPosition = 0
|
|
||||||
voiceMessageToRestoreWasPlaying = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
voiceMessageToRestoreId = ""
|
|
||||||
voiceMessageToRestoreAudioPosition = 0
|
|
||||||
voiceMessageToRestoreWasPlaying = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMessageInputFragment(): MessageInputFragment {
|
private fun getMessageInputFragment(): MessageInputFragment {
|
||||||
@ -551,17 +493,6 @@ class ChatActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
if (currentlyPlayedVoiceMessage != null) {
|
|
||||||
outState.putString(CURRENT_AUDIO_MESSAGE_KEY, currentlyPlayedVoiceMessage!!.id)
|
|
||||||
outState.putInt(CURRENT_AUDIO_POSITION_KEY, currentlyPlayedVoiceMessage!!.voiceMessagePlayedSeconds)
|
|
||||||
outState.putBoolean(CURRENT_AUDIO_WAS_PLAYING_KEY, currentlyPlayedVoiceMessage!!.isPlayingVoiceMessage)
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "Stored current audio message ID: " + currentlyPlayedVoiceMessage!!.id)
|
|
||||||
Log.d(
|
|
||||||
RESUME_AUDIO_TAG,
|
|
||||||
"Audio Position: " + currentlyPlayedVoiceMessage!!.voiceMessagePlayedSeconds
|
|
||||||
.toString() + " | isPLaying: " + currentlyPlayedVoiceMessage!!.isPlayingVoiceMessage
|
|
||||||
)
|
|
||||||
}
|
|
||||||
chatViewModel.handleOrientationChange()
|
chatViewModel.handleOrientationChange()
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
@ -933,6 +864,12 @@ class ChatActivity :
|
|||||||
}.collect()
|
}.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.lifecycleScope.launch {
|
||||||
|
chatViewModel.mediaPlayerSeekbarObserver.onEach { msg ->
|
||||||
|
adapter?.update(msg)
|
||||||
|
}.collect()
|
||||||
|
}
|
||||||
|
|
||||||
chatViewModel.reactionDeletedViewState.observe(this) { state ->
|
chatViewModel.reactionDeletedViewState.observe(this) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is ChatViewModel.ReactionDeletedSuccessState -> {
|
is ChatViewModel.ReactionDeletedSuccessState -> {
|
||||||
@ -1171,8 +1108,6 @@ class ChatActivity :
|
|||||||
|
|
||||||
setupSwipeToReply()
|
setupSwipeToReply()
|
||||||
|
|
||||||
chatViewModel.voiceMessagePlaybackSpeedPreferences.observe(this, playbackSpeedPreferencesObserver)
|
|
||||||
|
|
||||||
binding.unreadMessagesPopup.setOnClickListener {
|
binding.unreadMessagesPopup.setOnClickListener {
|
||||||
binding.messagesListView.smoothScrollToPosition(0)
|
binding.messagesListView.smoothScrollToPosition(0)
|
||||||
binding.unreadMessagesPopup.visibility = View.GONE
|
binding.unreadMessagesPopup.visibility = View.GONE
|
||||||
@ -1267,13 +1202,15 @@ class ChatActivity :
|
|||||||
val file = File(context.cacheDir, filename!!)
|
val file = File(context.cacheDir, filename!!)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
if (message.isPlayingVoiceMessage) {
|
if (message.isPlayingVoiceMessage) {
|
||||||
pausePlayback(message)
|
chatViewModel.pauseMediaPlayer(true)
|
||||||
|
message.isPlayingVoiceMessage = false
|
||||||
|
adapter?.update(message)
|
||||||
} else {
|
} else {
|
||||||
val retrieved = appPreferences.getWaveFormFromFile(filename)
|
val retrieved = appPreferences.getWaveFormFromFile(filename)
|
||||||
if (retrieved.isEmpty()) {
|
if (retrieved.isEmpty()) {
|
||||||
setUpWaveform(message)
|
setUpWaveform(message)
|
||||||
} else {
|
} else {
|
||||||
startPlayback(message)
|
startPlayback(file, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1286,11 +1223,8 @@ class ChatActivity :
|
|||||||
|
|
||||||
adapter?.registerViewClickListener(R.id.playbackSpeedControlBtn) { button, message ->
|
adapter?.registerViewClickListener(R.id.playbackSpeedControlBtn) { button, message ->
|
||||||
val nextSpeed = (button as PlaybackSpeedControl).getSpeed().next()
|
val nextSpeed = (button as PlaybackSpeedControl).getSpeed().next()
|
||||||
HashMap(appPreferences.readVoiceMessagePlaybackSpeedPreferences()).let { playbackSpeedPreferences ->
|
chatViewModel.setPlayBack(nextSpeed)
|
||||||
playbackSpeedPreferences[message.user.id] = nextSpeed
|
appPreferences.savePreferredPlayback(conversationUser!!.userId, nextSpeed)
|
||||||
chatViewModel.applyPlaybackSpeedPreferences(playbackSpeedPreferences)
|
|
||||||
appPreferences.saveVoiceMessagePlaybackSpeedPreferences(playbackSpeedPreferences)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1305,14 +1239,37 @@ class ChatActivity :
|
|||||||
appPreferences.saveWaveFormForFile(filename, r.toTypedArray())
|
appPreferences.saveWaveFormForFile(filename, r.toTypedArray())
|
||||||
message.voiceMessageFloatArray = r
|
message.voiceMessageFloatArray = r
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
startPlayback(message, thenPlay, backgroundPlayAllowed)
|
startPlayback(file, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startPlayback(message, thenPlay, backgroundPlayAllowed)
|
startPlayback(file, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startPlayback(file: File, message: ChatMessage) {
|
||||||
|
chatViewModel.clearMediaPlayerQueue()
|
||||||
|
chatViewModel.queueInMediaPlayer(file.canonicalPath, message)
|
||||||
|
chatViewModel.startCyclingMediaPlayer()
|
||||||
|
message.isPlayingVoiceMessage = true
|
||||||
|
adapter?.update(message)
|
||||||
|
|
||||||
|
var pos = adapter?.getMessagePositionById(message.id)!! - 1
|
||||||
|
do {
|
||||||
|
if (pos < 0) break
|
||||||
|
val nextItem = (adapter?.items?.get(pos)?.item) ?: break
|
||||||
|
val nextMessage = if (nextItem is ChatMessage) nextItem else break
|
||||||
|
if (!nextMessage.isVoiceMessage) break
|
||||||
|
|
||||||
|
downloadFileToCache(nextMessage, false) {
|
||||||
|
val newFilename = nextMessage.selectedIndividualHashMap!!["name"]
|
||||||
|
val newFile = File(context.cacheDir, newFilename!!)
|
||||||
|
chatViewModel.queueInMediaPlayer(newFile.canonicalPath, nextMessage)
|
||||||
|
}
|
||||||
|
pos--
|
||||||
|
} while (true && pos >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
private fun initMessageHolders(): MessageHolders {
|
private fun initMessageHolders(): MessageHolders {
|
||||||
val messageHolders = MessageHolders()
|
val messageHolders = MessageHolders()
|
||||||
val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!, viewThemeUtils)
|
val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!, viewThemeUtils)
|
||||||
@ -1692,253 +1649,20 @@ class ChatActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod", "Detekt.NestedBlockDepth")
|
override fun updateMediaPlayerProgressBySlider(message: ChatMessage, progress: Int) {
|
||||||
private fun startPlayback(message: ChatMessage, doPlay: Boolean = true, backgroundPlayAllowed: Boolean = false) {
|
chatViewModel.seekToMediaPlayer(progress)
|
||||||
if (!active && !backgroundPlayAllowed) {
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
val id = message.id.toString()
|
|
||||||
val index = adapter?.getMessagePositionById(id) ?: 0
|
|
||||||
|
|
||||||
var nextMessage: ChatMessage? = null
|
|
||||||
for (i in VOICE_MESSAGE_CONTINUOUS_BEFORE..VOICE_MESSAGE_CONTINUOUS_AFTER) {
|
|
||||||
if (index - i < 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (i == 0 || index - i >= (adapter?.items?.size ?: 0)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val curMsg = adapter?.items?.getOrNull(index - i)?.item
|
|
||||||
if (curMsg is ChatMessage) {
|
|
||||||
if (nextMessage == null && i > 0) {
|
|
||||||
nextMessage = curMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curMsg.isVoiceMessage) {
|
|
||||||
if (curMsg.selectedIndividualHashMap == null) {
|
|
||||||
// WORKAROUND TO FETCH FILE INFO:
|
|
||||||
curMsg.getImageUrl()
|
|
||||||
}
|
|
||||||
val filename = curMsg.selectedIndividualHashMap!!["name"]
|
|
||||||
val file = File(context.cacheDir, filename!!)
|
|
||||||
if (!file.exists()) {
|
|
||||||
downloadFileToCache(curMsg, false) {
|
|
||||||
curMsg.isDownloadingVoiceMessage = false
|
|
||||||
curMsg.voiceMessageDuration = try {
|
|
||||||
val retriever = MediaMetadataRetriever()
|
|
||||||
retriever.setDataSource(file.absolutePath) // Set the audio file as the data source
|
|
||||||
val durationStr =
|
|
||||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
|
||||||
retriever.release() // Always release the retriever to free resources
|
|
||||||
(durationStr?.toIntOrNull() ?: 0) / ONE_SECOND_IN_MILLIS // Convert to int (seconds)
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
Log.e(
|
|
||||||
TAG,
|
|
||||||
"An exception occurred while computing " +
|
|
||||||
"voice message duration for " + filename,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
0
|
|
||||||
}
|
|
||||||
adapter?.update(curMsg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (curMsg.voiceMessageDuration == 0) {
|
|
||||||
curMsg.voiceMessageDuration = try {
|
|
||||||
val retriever = MediaMetadataRetriever()
|
|
||||||
retriever.setDataSource(file.absolutePath) // Set the audio file as the data source
|
|
||||||
val durationStr =
|
|
||||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
|
||||||
retriever.release() // Always release the retriever to free resources
|
|
||||||
(durationStr?.toIntOrNull() ?: 0) / ONE_SECOND_IN_MILLIS // Convert to int (seconds)
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
Log.e(
|
|
||||||
TAG,
|
|
||||||
"An exception occurred while computing " +
|
|
||||||
"voice message duration for " + filename,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
0
|
|
||||||
}
|
|
||||||
adapter?.update(curMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasConsecutiveVoiceMessage = if (nextMessage != null) nextMessage.isVoiceMessage else false
|
|
||||||
|
|
||||||
mediaPlayer?.let {
|
|
||||||
if (!it.isPlaying && doPlay) {
|
|
||||||
chatViewModel.audioRequest(true) {
|
|
||||||
it.playbackParams = it.playbackParams.apply {
|
|
||||||
setSpeed(chatViewModel.getPlaybackSpeedPreference(message).value)
|
|
||||||
}
|
|
||||||
it.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaPlayerHandler = Handler()
|
|
||||||
runOnUiThread(object : Runnable {
|
|
||||||
override fun run() {
|
|
||||||
if (mediaPlayer != null) {
|
|
||||||
if (message.isPlayingVoiceMessage) {
|
|
||||||
val pos = mediaPlayer!!.currentPosition.toFloat() / VOICE_MESSAGE_SEEKBAR_BASE
|
|
||||||
if (pos + VOICE_MESSAGE_PLAY_ADD_THRESHOLD < (
|
|
||||||
mediaPlayer!!.duration.toFloat() / VOICE_MESSAGE_SEEKBAR_BASE
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
lastRecordMediaPosition = mediaPlayer!!.currentPosition
|
|
||||||
message.voiceMessagePlayedSeconds = pos.toInt()
|
|
||||||
message.voiceMessageSeekbarProgress = mediaPlayer!!.currentPosition
|
|
||||||
if (mediaPlayer!!.currentPosition * VOICE_MESSAGE_MARK_PLAYED_FACTOR >
|
|
||||||
mediaPlayer!!.duration
|
|
||||||
) {
|
|
||||||
// a voice message is marked as played when the mediaplayer position
|
|
||||||
// is at least at 5% of its duration
|
|
||||||
message.wasPlayedVoiceMessage = true
|
|
||||||
}
|
|
||||||
adapter?.update(message)
|
|
||||||
} else {
|
|
||||||
message.resetVoiceMessage = true
|
|
||||||
message.voiceMessagePlayedSeconds = 0
|
|
||||||
message.voiceMessageSeekbarProgress = 0
|
|
||||||
adapter?.update(message)
|
|
||||||
stopMediaPlayer(message)
|
|
||||||
if (hasConsecutiveVoiceMessage) {
|
|
||||||
val defaultMediaPlayer = MediaPlayer.create(
|
|
||||||
context,
|
|
||||||
R.raw
|
|
||||||
.next_voice_message_doodle
|
|
||||||
)
|
|
||||||
defaultMediaPlayer.setOnCompletionListener {
|
|
||||||
defaultMediaPlayer.release()
|
|
||||||
setUpWaveform(nextMessage as ChatMessage, doPlay, true)
|
|
||||||
}
|
|
||||||
defaultMediaPlayer.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mediaPlayerHandler?.postDelayed(this, MILLISEC_15)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
message.isDownloadingVoiceMessage = false
|
|
||||||
message.isPlayingVoiceMessage = doPlay
|
|
||||||
// message.voiceMessagePlayedSeconds = lastRecordMediaPosition / VOICE_MESSAGE_SEEKBAR_BASE
|
|
||||||
// message.voiceMessageSeekbarProgress = lastRecordMediaPosition
|
|
||||||
// the commented instructions objective was to update audio seekbarprogress
|
|
||||||
// in the case in which audio status is paused when the position is resumed
|
|
||||||
adapter?.update(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pausePlayback(message: ChatMessage) {
|
|
||||||
if (mediaPlayer!!.isPlaying) {
|
|
||||||
chatViewModel.audioRequest(false) {
|
|
||||||
mediaPlayer!!.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message.isPlayingVoiceMessage = false
|
|
||||||
adapter?.update(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
|
||||||
private fun initMediaPlayer(message: ChatMessage) {
|
|
||||||
if (message != currentlyPlayedVoiceMessage) {
|
|
||||||
currentlyPlayedVoiceMessage?.let { stopMediaPlayer(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaPlayer == null) {
|
|
||||||
val fileName = message.selectedIndividualHashMap!!["name"]
|
|
||||||
val absolutePath = context.cacheDir.absolutePath + "/" + fileName
|
|
||||||
|
|
||||||
try {
|
|
||||||
mediaPlayer = MediaPlayer().apply {
|
|
||||||
setDataSource(absolutePath)
|
|
||||||
prepare()
|
|
||||||
setOnPreparedListener {
|
|
||||||
currentlyPlayedVoiceMessage = message
|
|
||||||
message.voiceMessageDuration = mediaPlayer!!.duration / VOICE_MESSAGE_SEEKBAR_BASE
|
|
||||||
lastRecordedSeeked = false
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
setOnMediaTimeDiscontinuityListener { mp, _ ->
|
|
||||||
if (lastRecordMediaPosition > ONE_SECOND_IN_MILLIS && !lastRecordedSeeked) {
|
|
||||||
mp.seekTo(lastRecordMediaPosition)
|
|
||||||
lastRecordedSeeked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// this ensures that audio can be resumed at a given position
|
|
||||||
this.seekTo(lastRecordMediaPosition)
|
|
||||||
}
|
|
||||||
setOnCompletionListener {
|
|
||||||
stopMediaPlayer(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "failed to initialize mediaPlayer", e)
|
|
||||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stopMediaPlayer(message: ChatMessage) {
|
|
||||||
message.isPlayingVoiceMessage = false
|
|
||||||
message.resetVoiceMessage = true
|
|
||||||
adapter?.update(message)
|
|
||||||
|
|
||||||
currentlyPlayedVoiceMessage = null
|
|
||||||
lastRecordMediaPosition = 0 // this ensures that if audio track is changed, then it is played from the beginning
|
|
||||||
|
|
||||||
mediaPlayerHandler?.removeCallbacksAndMessages(null)
|
|
||||||
|
|
||||||
try {
|
|
||||||
mediaPlayer?.let {
|
|
||||||
if (it.isPlaying) {
|
|
||||||
Log.d(TAG, "media player is stopped")
|
|
||||||
chatViewModel.audioRequest(false) {
|
|
||||||
it.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
Log.e(TAG, "mediaPlayer was not initialized", e)
|
|
||||||
} finally {
|
|
||||||
mediaPlayer?.release()
|
|
||||||
mediaPlayer = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateMediaPlayerProgressBySlider(messageWithSlidedProgress: ChatMessage, progress: Int) {
|
|
||||||
if (mediaPlayer != null) {
|
|
||||||
if (messageWithSlidedProgress == currentlyPlayedVoiceMessage) {
|
|
||||||
mediaPlayer!!.seekTo(progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registerMessageToObservePlaybackSpeedPreferences(
|
override fun registerMessageToObservePlaybackSpeedPreferences(
|
||||||
userId: String,
|
userId: String,
|
||||||
listener: (speed: PlaybackSpeed) -> Unit
|
listener: (speed: PlaybackSpeed) -> Unit
|
||||||
) {
|
) {
|
||||||
chatViewModel.voiceMessagePlaybackSpeedPreferences.let { liveData ->
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
liveData.observe(this) { playbackSpeedPreferences ->
|
chatViewModel.voiceMessagePlayBackUIFlow.onEach { speed ->
|
||||||
listener(playbackSpeedPreferences[userId] ?: PlaybackSpeed.NORMAL)
|
withContext(Dispatchers.Main) {
|
||||||
}
|
listener(speed)
|
||||||
liveData.value?.let { playbackSpeedPreferences ->
|
|
||||||
listener(playbackSpeedPreferences[userId] ?: PlaybackSpeed.NORMAL)
|
|
||||||
}
|
}
|
||||||
|
}.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2610,8 +2334,6 @@ class ChatActivity :
|
|||||||
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
||||||
mentionAutocomplete?.dismissPopup()
|
mentionAutocomplete?.dismissPopup()
|
||||||
}
|
}
|
||||||
|
|
||||||
chatViewModel.voiceMessagePlaybackSpeedPreferences.removeObserver(playbackSpeedPreferencesObserver)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isActivityNotChangingConfigurations(): Boolean = !isChangingConfigurations
|
private fun isActivityNotChangingConfigurations(): Boolean = !isChangingConfigurations
|
||||||
@ -2677,8 +2399,7 @@ class ChatActivity :
|
|||||||
actionBar?.setIcon(null)
|
actionBar?.setIcon(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentlyPlayedVoiceMessage?.let { stopMediaPlayer(it) } // FIXME, mediaplayer can sometimes be null here
|
adapter = null
|
||||||
|
|
||||||
disposables.dispose()
|
disposables.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2972,8 +2693,6 @@ class ChatActivity :
|
|||||||
adapter?.addToEnd(chatMessageList, false)
|
adapter?.addToEnd(chatMessageList, false)
|
||||||
}
|
}
|
||||||
scrollToRequestedMessageIfNeeded()
|
scrollToRequestedMessageIfNeeded()
|
||||||
// FENOM: add here audio resume policy
|
|
||||||
resumeAudioPlaybackIfNeeded()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scrollToFirstUnreadMessage() {
|
private fun scrollToFirstUnreadMessage() {
|
||||||
@ -3036,37 +2755,6 @@ class ChatActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* this method must be called after that the adapter has finished loading ChatMessages items
|
|
||||||
* it searches by ID the message that was playing,s
|
|
||||||
* then, if it finds it, it restores audio position
|
|
||||||
* and eventually resumes audio playback
|
|
||||||
* @author Giacomo Pacini
|
|
||||||
*/
|
|
||||||
private fun resumeAudioPlaybackIfNeeded() {
|
|
||||||
if (voiceMessageToRestoreId != "") {
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "begin method to resume audio playback")
|
|
||||||
|
|
||||||
val pair = getItemFromAdapter(voiceMessageToRestoreId)
|
|
||||||
currentlyPlayedVoiceMessage = pair?.first
|
|
||||||
val voiceMessagePosition = pair?.second!!
|
|
||||||
|
|
||||||
lastRecordMediaPosition = voiceMessageToRestoreAudioPosition * ONE_SECOND_IN_MILLIS
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "trying to resume audio")
|
|
||||||
binding.messagesListView.scrollToPosition(voiceMessagePosition)
|
|
||||||
// WORKAROUND TO FETCH FILE INFO:
|
|
||||||
currentlyPlayedVoiceMessage!!.getImageUrl()
|
|
||||||
// see getImageUrl() source code
|
|
||||||
setUpWaveform(currentlyPlayedVoiceMessage!!, voiceMessageToRestoreWasPlaying)
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "resume audio procedure completed")
|
|
||||||
} else {
|
|
||||||
Log.d(RESUME_AUDIO_TAG, "No voice message to restore")
|
|
||||||
}
|
|
||||||
voiceMessageToRestoreId = ""
|
|
||||||
voiceMessageToRestoreAudioPosition = 0
|
|
||||||
voiceMessageToRestoreWasPlaying = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getItemFromAdapter(messageId: String): Pair<ChatMessage, Int>? {
|
private fun getItemFromAdapter(messageId: String): Pair<ChatMessage, Int>? {
|
||||||
if (adapter != null) {
|
if (adapter != null) {
|
||||||
val messagePosition = adapter!!.items!!.indexOfFirst {
|
val messagePosition = adapter!!.items!!.indexOfFirst {
|
||||||
|
@ -16,6 +16,7 @@ import android.widget.SeekBar
|
|||||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import autodagger.AutoInjector
|
import autodagger.AutoInjector
|
||||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
@ -24,6 +25,9 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA
|
|||||||
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
|
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
|
||||||
import com.nextcloud.talk.databinding.FragmentMessageInputVoiceRecordingBinding
|
import com.nextcloud.talk.databinding.FragmentMessageInputVoiceRecordingBinding
|
||||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication::class)
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
@ -60,6 +64,7 @@ class MessageInputVoiceRecordingFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
chatActivity.messageInputViewModel.stopMediaPlayer() // if it wasn't stopped already
|
||||||
this.lifecycle.removeObserver(chatActivity.messageInputViewModel)
|
this.lifecycle.removeObserver(chatActivity.messageInputViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,13 +73,16 @@ class MessageInputVoiceRecordingFragment : Fragment() {
|
|||||||
chatActivity.messageInputViewModel.micInputAudioObserver.observe(viewLifecycleOwner) {
|
chatActivity.messageInputViewModel.micInputAudioObserver.observe(viewLifecycleOwner) {
|
||||||
binding.micInputCloud.setRotationSpeed(it.first, it.second)
|
binding.micInputCloud.setRotationSpeed(it.first, it.second)
|
||||||
}
|
}
|
||||||
chatActivity.messageInputViewModel.mediaPlayerSeekbarObserver.observe(viewLifecycleOwner) { progress ->
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
chatActivity.messageInputViewModel.mediaPlayerSeekbarObserver.onEach { progress ->
|
||||||
if (progress >= SEEK_LIMIT) {
|
if (progress >= SEEK_LIMIT) {
|
||||||
togglePausePlay()
|
togglePausePlay()
|
||||||
binding.seekbar.progress = 0
|
binding.seekbar.progress = 0
|
||||||
} else if (!pause) {
|
} else if (!pause && chatActivity.messageInputViewModel.isVoicePreviewPlaying.value == true) {
|
||||||
binding.seekbar.progress = progress
|
binding.seekbar.progress = progress
|
||||||
}
|
}
|
||||||
|
}.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
chatActivity.messageInputViewModel.getAudioFocusChange.observe(viewLifecycleOwner) { state ->
|
chatActivity.messageInputViewModel.getAudioFocusChange.observe(viewLifecycleOwner) { state ->
|
||||||
@ -107,7 +115,7 @@ class MessageInputVoiceRecordingFragment : Fragment() {
|
|||||||
binding.sendVoiceRecording.setOnClickListener {
|
binding.sendVoiceRecording.setOnClickListener {
|
||||||
chatActivity.chatViewModel.stopAndSendAudioRecording(
|
chatActivity.chatViewModel.stopAndSendAudioRecording(
|
||||||
chatActivity.roomToken,
|
chatActivity.roomToken,
|
||||||
chatActivity.currentConversation!!.displayName!!,
|
chatActivity.currentConversation!!.displayName,
|
||||||
MessageInputFragment.VOICE_MESSAGE_META_DATA
|
MessageInputFragment.VOICE_MESSAGE_META_DATA
|
||||||
)
|
)
|
||||||
clear()
|
clear()
|
||||||
|
@ -9,44 +9,120 @@ package com.nextcloud.talk.chat.data.io
|
|||||||
|
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import com.nextcloud.talk.chat.ChatActivity
|
import com.nextcloud.talk.chat.ChatActivity
|
||||||
|
import com.nextcloud.talk.chat.data.model.ChatMessage
|
||||||
|
import com.nextcloud.talk.ui.PlaybackSpeed
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import kotlin.math.ceil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstraction over the [MediaPlayer](https://developer.android.com/reference/android/media/MediaPlayer) class used
|
* Abstraction over the [MediaPlayer](https://developer.android.com/reference/android/media/MediaPlayer) class used
|
||||||
* to manage the MediaPlayer instance.
|
* to manage the MediaPlayer instance.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("TooManyFunctions", "TooGenericExceptionCaught")
|
||||||
class MediaPlayerManager : LifecycleAwareManager {
|
class MediaPlayerManager : LifecycleAwareManager {
|
||||||
companion object {
|
companion object {
|
||||||
val TAG: String = MediaPlayerManager::class.java.simpleName
|
val TAG: String = MediaPlayerManager::class.java.simpleName
|
||||||
private const val SEEKBAR_UPDATE_DELAY = 15L
|
private const val SEEKBAR_UPDATE_DELAY = 150L
|
||||||
const val DIVIDER = 100f
|
private const val ONE_SEC = 1000
|
||||||
|
private const val DIVIDER = 100f
|
||||||
|
private const val IS_PLAYED_CUTOFF = 5
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private val manager: MediaPlayerManager = MediaPlayerManager()
|
||||||
|
|
||||||
|
fun sharedInstance(preferences: AppPreferences): MediaPlayerManager =
|
||||||
|
manager.apply {
|
||||||
|
appPreferences = preferences
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
|
enum class MediaPlayerManagerState {
|
||||||
|
DEFAULT,
|
||||||
|
SETUP,
|
||||||
|
STARTED,
|
||||||
|
STOPPED,
|
||||||
|
RESUMED,
|
||||||
|
PAUSED,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
val backgroundPlayUIFlow: StateFlow<ChatMessage?>
|
||||||
|
get() = _backgroundPlayUIFlow
|
||||||
|
private val _backgroundPlayUIFlow = MutableStateFlow<ChatMessage?>(null)
|
||||||
|
|
||||||
|
val managerState: Flow<MediaPlayerManagerState>
|
||||||
|
get() = _managerState
|
||||||
|
private val _managerState = MutableStateFlow(MediaPlayerManagerState.DEFAULT)
|
||||||
|
|
||||||
|
private val playQueue = mutableListOf<Pair<String, ChatMessage>>()
|
||||||
|
|
||||||
|
val mediaPlayerSeekBarPositionMsg: Flow<ChatMessage>
|
||||||
|
get() = _mediaPlayerSeekBarPositionMsg
|
||||||
|
private val _mediaPlayerSeekBarPositionMsg: MutableSharedFlow<ChatMessage> = MutableSharedFlow()
|
||||||
|
|
||||||
|
val mediaPlayerSeekBarPosition: Flow<Int>
|
||||||
|
get() = _mediaPlayerSeekBarPosition
|
||||||
|
private val _mediaPlayerSeekBarPosition: MutableSharedFlow<Int> = MutableSharedFlow()
|
||||||
|
|
||||||
private var mediaPlayer: MediaPlayer? = null
|
private var mediaPlayer: MediaPlayer? = null
|
||||||
private var mediaPlayerPosition: Int = 0
|
|
||||||
private var loop = false
|
private var loop = false
|
||||||
private var scope = MainScope()
|
private var scope = MainScope()
|
||||||
|
private var currentCycledMessage: ChatMessage? = null
|
||||||
|
private var currentDataSource: String = ""
|
||||||
var mediaPlayerDuration: Int = 0
|
var mediaPlayerDuration: Int = 0
|
||||||
private val _mediaPlayerSeekBarPosition: MutableLiveData<Int> = MutableLiveData()
|
var mediaPlayerPosition: Int = 0
|
||||||
val mediaPlayerSeekBarPosition: LiveData<Int>
|
|
||||||
get() = _mediaPlayerSeekBarPosition
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts playing audio from the given path, initializes or resumes if the player is already created.
|
* Starts playing audio from the given path, initializes or resumes if the player is already created.
|
||||||
*/
|
*/
|
||||||
fun start(path: String) {
|
fun start(path: String) {
|
||||||
|
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
if (mediaPlayer == null || !scope.isActive) {
|
if (mediaPlayer == null || !scope.isActive) {
|
||||||
init(path)
|
init(path)
|
||||||
} else {
|
} else {
|
||||||
|
_managerState.value = MediaPlayerManagerState.RESUMED
|
||||||
|
mediaPlayer!!.start()
|
||||||
|
loop = true
|
||||||
|
scope.launch { seekbarUpdateObserver() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starting cycling through the playQueue, playing messages automatically unless stop() is called.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun startCycling() {
|
||||||
|
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
val shouldReset = playQueue.first().first != currentDataSource
|
||||||
|
|
||||||
|
if (mediaPlayer == null || !scope.isActive || shouldReset) {
|
||||||
|
initCycling()
|
||||||
|
} else {
|
||||||
|
_managerState.value = MediaPlayerManagerState.RESUMED
|
||||||
mediaPlayer!!.start()
|
mediaPlayer!!.start()
|
||||||
loop = true
|
loop = true
|
||||||
scope.launch { seekbarUpdateObserver() }
|
scope.launch { seekbarUpdateObserver() }
|
||||||
@ -60,19 +136,28 @@ class MediaPlayerManager : LifecycleAwareManager {
|
|||||||
if (mediaPlayer != null) {
|
if (mediaPlayer != null) {
|
||||||
Log.d(TAG, "media player destroyed")
|
Log.d(TAG, "media player destroyed")
|
||||||
loop = false
|
loop = false
|
||||||
|
scope.cancel()
|
||||||
mediaPlayer!!.stop()
|
mediaPlayer!!.stop()
|
||||||
mediaPlayer!!.release()
|
mediaPlayer!!.release()
|
||||||
mediaPlayer = null
|
mediaPlayer = null
|
||||||
|
currentCycledMessage = null
|
||||||
|
_backgroundPlayUIFlow.tryEmit(null)
|
||||||
|
_managerState.value = MediaPlayerManagerState.STOPPED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pauses the player.
|
* Pauses the player.
|
||||||
*/
|
*/
|
||||||
fun pause() {
|
fun pause(notifyUI: Boolean) {
|
||||||
if (mediaPlayer != null) {
|
if (mediaPlayer != null) {
|
||||||
Log.d(TAG, "media player paused")
|
Log.d(TAG, "media player paused")
|
||||||
|
_managerState.value = MediaPlayerManagerState.PAUSED
|
||||||
mediaPlayer!!.pause()
|
mediaPlayer!!.pause()
|
||||||
|
loop = false
|
||||||
|
if (notifyUI) {
|
||||||
|
_backgroundPlayUIFlow.tryEmit(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,14 +174,29 @@ class MediaPlayerManager : LifecycleAwareManager {
|
|||||||
|
|
||||||
private suspend fun seekbarUpdateObserver() {
|
private suspend fun seekbarUpdateObserver() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
currentCycledMessage?.voiceMessageDuration = mediaPlayerDuration / ONE_SEC
|
||||||
|
currentCycledMessage?.resetVoiceMessage = false
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!loop) {
|
if (!loop) {
|
||||||
return@withContext
|
// NOTE: ok so this doesn't stop the loop, but rather stop the update. Wasteful, but minimal
|
||||||
|
delay(SEEKBAR_UPDATE_DELAY)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
|
|
||||||
|
if (mediaPlayer != null && mediaPlayer?.isPlaying == true) {
|
||||||
val pos = mediaPlayer!!.currentPosition
|
val pos = mediaPlayer!!.currentPosition
|
||||||
|
mediaPlayerPosition = pos
|
||||||
val progress = (pos.toFloat() / mediaPlayerDuration) * DIVIDER
|
val progress = (pos.toFloat() / mediaPlayerDuration) * DIVIDER
|
||||||
_mediaPlayerSeekBarPosition.postValue(progress.toInt())
|
val progressI = ceil(progress).toInt()
|
||||||
|
val seconds = (pos / ONE_SEC)
|
||||||
|
_mediaPlayerSeekBarPosition.emit(progressI)
|
||||||
|
currentCycledMessage?.let {
|
||||||
|
it.isPlayingVoiceMessage = true
|
||||||
|
it.voiceMessageSeekbarProgress = progressI
|
||||||
|
it.voiceMessagePlayedSeconds = seconds
|
||||||
|
if (progressI >= IS_PLAYED_CUTOFF) it.wasPlayedVoiceMessage = true
|
||||||
|
_mediaPlayerSeekBarPositionMsg.emit(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(SEEKBAR_UPDATE_DELAY)
|
delay(SEEKBAR_UPDATE_DELAY)
|
||||||
@ -104,35 +204,140 @@ class MediaPlayerManager : LifecycleAwareManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
/**
|
||||||
|
* Adds a audio file to the play queue. for cycling through
|
||||||
|
*
|
||||||
|
* @throws FileNotFoundException if the file is not downloaded to cache first
|
||||||
|
*/
|
||||||
|
fun addToPlayList(path: String, chatMessage: ChatMessage) {
|
||||||
|
val file = File(path)
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw FileNotFoundException("Cannot add to playlist without downloading to cache first for path\n$path")
|
||||||
|
}
|
||||||
|
|
||||||
|
for (pair in playQueue) {
|
||||||
|
if (pair.first == path) return
|
||||||
|
}
|
||||||
|
|
||||||
|
playQueue.add(Pair(path, chatMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearPlayList() {
|
||||||
|
playQueue.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the player speed.
|
||||||
|
*/
|
||||||
|
fun setPlayBackSpeed(speed: PlaybackSpeed) {
|
||||||
|
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
|
||||||
|
mediaPlayer!!.playbackParams.let { params ->
|
||||||
|
params.setSpeed(speed.value)
|
||||||
|
mediaPlayer!!.playbackParams = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun init(path: String) {
|
private fun init(path: String) {
|
||||||
try {
|
try {
|
||||||
mediaPlayer = MediaPlayer().apply {
|
mediaPlayer = MediaPlayer().apply {
|
||||||
|
_managerState.value = MediaPlayerManagerState.SETUP
|
||||||
setDataSource(path)
|
setDataSource(path)
|
||||||
|
currentDataSource = path
|
||||||
prepareAsync()
|
prepareAsync()
|
||||||
setOnPreparedListener {
|
setOnPreparedListener {
|
||||||
mediaPlayerDuration = it.duration
|
onPrepare()
|
||||||
start()
|
|
||||||
loop = true
|
|
||||||
scope = MainScope()
|
|
||||||
scope.launch { seekbarUpdateObserver() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(ChatActivity.TAG, "failed to initialize mediaPlayer", e)
|
Log.e(ChatActivity.TAG, "failed to initialize mediaPlayer", e)
|
||||||
|
_managerState.value = MediaPlayerManagerState.ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initCycling() {
|
||||||
|
try {
|
||||||
|
mediaPlayer = MediaPlayer().apply {
|
||||||
|
_managerState.value = MediaPlayerManagerState.SETUP
|
||||||
|
val pair = playQueue.iterator().next()
|
||||||
|
setDataSource(pair.first)
|
||||||
|
currentDataSource = pair.first
|
||||||
|
currentCycledMessage = pair.second
|
||||||
|
playQueue.removeAt(0)
|
||||||
|
prepareAsync()
|
||||||
|
setOnPreparedListener {
|
||||||
|
onPrepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnCompletionListener {
|
||||||
|
if (playQueue.iterator().hasNext() && playQueue.first().first != currentDataSource) {
|
||||||
|
_managerState.value = MediaPlayerManagerState.SETUP
|
||||||
|
val nextPair = playQueue.iterator().next()
|
||||||
|
playQueue.removeAt(0)
|
||||||
|
mediaPlayer?.reset()
|
||||||
|
mediaPlayer?.setDataSource(nextPair.first)
|
||||||
|
currentCycledMessage = nextPair.second
|
||||||
|
prepare()
|
||||||
|
} else {
|
||||||
|
mediaPlayer?.release()
|
||||||
|
mediaPlayer = null
|
||||||
|
_backgroundPlayUIFlow.tryEmit(null)
|
||||||
|
currentCycledMessage?.let {
|
||||||
|
it.resetVoiceMessage = true
|
||||||
|
it.isPlayingVoiceMessage = false
|
||||||
|
}
|
||||||
|
runBlocking {
|
||||||
|
_mediaPlayerSeekBarPositionMsg.emit(currentCycledMessage!!)
|
||||||
|
}
|
||||||
|
currentCycledMessage = null
|
||||||
|
loop = false
|
||||||
|
_managerState.value = MediaPlayerManagerState.STOPPED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(ChatActivity.TAG, "failed to initialize mediaPlayer", e)
|
||||||
|
_managerState.value = MediaPlayerManagerState.ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MediaPlayer.onPrepare() {
|
||||||
|
mediaPlayerDuration = this.duration
|
||||||
|
|
||||||
|
val playBackSpeed = if (currentCycledMessage?.actorId == null) {
|
||||||
|
PlaybackSpeed.NORMAL.value
|
||||||
|
} else {
|
||||||
|
appPreferences.getPreferredPlayback(currentCycledMessage?.actorId).value
|
||||||
|
}
|
||||||
|
mediaPlayer!!.playbackParams.setSpeed(playBackSpeed)
|
||||||
|
|
||||||
|
start()
|
||||||
|
_managerState.value = MediaPlayerManagerState.STARTED
|
||||||
|
currentCycledMessage?.let {
|
||||||
|
it.isPlayingVoiceMessage = true
|
||||||
|
_backgroundPlayUIFlow.tryEmit(it)
|
||||||
|
}
|
||||||
|
loop = true
|
||||||
|
scope = MainScope()
|
||||||
|
scope.launch { seekbarUpdateObserver() }
|
||||||
|
}
|
||||||
|
|
||||||
override fun handleOnPause() {
|
override fun handleOnPause() {
|
||||||
// unused atm
|
// unused atm
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleOnResume() {
|
override fun handleOnResume() {
|
||||||
// unused atm
|
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
|
||||||
|
loop = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleOnStop() {
|
override fun handleOnStop() {
|
||||||
stop()
|
loop = false
|
||||||
scope.cancel()
|
if (mediaPlayer != null && currentCycledMessage != null && mediaPlayer!!.isPlaying) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
_backgroundPlayUIFlow.tryEmit(currentCycledMessage!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.nextcloud.talk.chat.data.ChatMessageRepository
|
import com.nextcloud.talk.chat.data.ChatMessageRepository
|
||||||
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
|
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
|
||||||
|
import com.nextcloud.talk.chat.data.io.MediaPlayerManager
|
||||||
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
|
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
|
||||||
import com.nextcloud.talk.chat.data.model.ChatMessage
|
import com.nextcloud.talk.chat.data.model.ChatMessage
|
||||||
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
|
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
|
||||||
@ -41,11 +42,15 @@ import com.nextcloud.talk.ui.PlaybackSpeed
|
|||||||
import com.nextcloud.talk.utils.ConversationUtils
|
import com.nextcloud.talk.utils.ConversationUtils
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||||
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
import io.reactivex.Observer
|
import io.reactivex.Observer
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
@ -57,6 +62,7 @@ import javax.inject.Inject
|
|||||||
@Suppress("TooManyFunctions", "LongParameterList")
|
@Suppress("TooManyFunctions", "LongParameterList")
|
||||||
class ChatViewModel @Inject constructor(
|
class ChatViewModel @Inject constructor(
|
||||||
// should be removed here. Use it via RetrofitChatNetwork
|
// should be removed here. Use it via RetrofitChatNetwork
|
||||||
|
private val appPreferences: AppPreferences,
|
||||||
private val chatNetworkDataSource: ChatNetworkDataSource,
|
private val chatNetworkDataSource: ChatNetworkDataSource,
|
||||||
private val chatRepository: ChatMessageRepository,
|
private val chatRepository: ChatMessageRepository,
|
||||||
private val conversationRepository: OfflineConversationsRepository,
|
private val conversationRepository: OfflineConversationsRepository,
|
||||||
@ -73,8 +79,11 @@ class ChatViewModel @Inject constructor(
|
|||||||
STOPPED
|
STOPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val mediaPlayerManager: MediaPlayerManager = MediaPlayerManager.sharedInstance(appPreferences)
|
||||||
lateinit var currentLifeCycleFlag: LifeCycleFlag
|
lateinit var currentLifeCycleFlag: LifeCycleFlag
|
||||||
val disposableSet = mutableSetOf<Disposable>()
|
val disposableSet = mutableSetOf<Disposable>()
|
||||||
|
var mediaPlayerDuration = mediaPlayerManager.mediaPlayerDuration
|
||||||
|
val mediaPlayerPosition = mediaPlayerManager.mediaPlayerPosition
|
||||||
|
|
||||||
fun getChatRepository(): ChatMessageRepository {
|
fun getChatRepository(): ChatMessageRepository {
|
||||||
return chatRepository
|
return chatRepository
|
||||||
@ -85,6 +94,7 @@ class ChatViewModel @Inject constructor(
|
|||||||
currentLifeCycleFlag = LifeCycleFlag.RESUMED
|
currentLifeCycleFlag = LifeCycleFlag.RESUMED
|
||||||
mediaRecorderManager.handleOnResume()
|
mediaRecorderManager.handleOnResume()
|
||||||
chatRepository.handleOnResume()
|
chatRepository.handleOnResume()
|
||||||
|
mediaPlayerManager.handleOnResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause(owner: LifecycleOwner) {
|
override fun onPause(owner: LifecycleOwner) {
|
||||||
@ -94,6 +104,7 @@ class ChatViewModel @Inject constructor(
|
|||||||
disposableSet.clear()
|
disposableSet.clear()
|
||||||
mediaRecorderManager.handleOnPause()
|
mediaRecorderManager.handleOnPause()
|
||||||
chatRepository.handleOnPause()
|
chatRepository.handleOnPause()
|
||||||
|
mediaPlayerManager.handleOnPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop(owner: LifecycleOwner) {
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
@ -101,8 +112,21 @@ class ChatViewModel @Inject constructor(
|
|||||||
currentLifeCycleFlag = LifeCycleFlag.STOPPED
|
currentLifeCycleFlag = LifeCycleFlag.STOPPED
|
||||||
mediaRecorderManager.handleOnStop()
|
mediaRecorderManager.handleOnStop()
|
||||||
chatRepository.handleOnStop()
|
chatRepository.handleOnStop()
|
||||||
|
mediaPlayerManager.handleOnStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val backgroundPlayUIFlow = mediaPlayerManager.backgroundPlayUIFlow
|
||||||
|
|
||||||
|
val mediaPlayerSeekbarObserver: Flow<ChatMessage>
|
||||||
|
get() = mediaPlayerManager.mediaPlayerSeekBarPositionMsg
|
||||||
|
|
||||||
|
val managerStateFlow: Flow<MediaPlayerManager.MediaPlayerManagerState>
|
||||||
|
get() = mediaPlayerManager.managerState
|
||||||
|
|
||||||
|
val voiceMessagePlayBackUIFlow: Flow<PlaybackSpeed>
|
||||||
|
get() = _voiceMessagePlayBackUIFlow
|
||||||
|
private val _voiceMessagePlayBackUIFlow: MutableSharedFlow<PlaybackSpeed> = MutableSharedFlow()
|
||||||
|
|
||||||
val getAudioFocusChange: LiveData<AudioFocusRequestManager.ManagerState>
|
val getAudioFocusChange: LiveData<AudioFocusRequestManager.ManagerState>
|
||||||
get() = audioFocusRequestManager.getManagerState
|
get() = audioFocusRequestManager.getManagerState
|
||||||
|
|
||||||
@ -122,10 +146,6 @@ class ChatViewModel @Inject constructor(
|
|||||||
val outOfOfficeViewState: LiveData<OutOfOfficeUIState>
|
val outOfOfficeViewState: LiveData<OutOfOfficeUIState>
|
||||||
get() = _outOfOfficeViewState
|
get() = _outOfOfficeViewState
|
||||||
|
|
||||||
private val _voiceMessagePlaybackSpeedPreferences: MutableLiveData<Map<String, PlaybackSpeed>> = MutableLiveData()
|
|
||||||
val voiceMessagePlaybackSpeedPreferences: LiveData<Map<String, PlaybackSpeed>>
|
|
||||||
get() = _voiceMessagePlaybackSpeedPreferences
|
|
||||||
|
|
||||||
val getMessageFlow = chatRepository.messageFlow
|
val getMessageFlow = chatRepository.messageFlow
|
||||||
.onEach {
|
.onEach {
|
||||||
_chatMessageViewState.value = if (_chatMessageViewState.value == ChatMessageInitialState) {
|
_chatMessageViewState.value = if (_chatMessageViewState.value == ChatMessageInitialState) {
|
||||||
@ -665,12 +685,34 @@ class ChatViewModel @Inject constructor(
|
|||||||
emit(message.first())
|
emit(message.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyPlaybackSpeedPreferences(speeds: Map<String, PlaybackSpeed>) {
|
fun setPlayBack(speed: PlaybackSpeed) {
|
||||||
_voiceMessagePlaybackSpeedPreferences.postValue(speeds)
|
mediaPlayerManager.setPlayBackSpeed(speed)
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
_voiceMessagePlayBackUIFlow.emit(speed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlaybackSpeedPreference(message: ChatMessage) =
|
fun startMediaPlayer(path: String) {
|
||||||
_voiceMessagePlaybackSpeedPreferences.value?.get(message.user.id) ?: PlaybackSpeed.NORMAL
|
audioRequest(true) {
|
||||||
|
mediaPlayerManager.start(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startCyclingMediaPlayer() = audioRequest(true, mediaPlayerManager::startCycling)
|
||||||
|
|
||||||
|
fun pauseMediaPlayer(notifyUI: Boolean) {
|
||||||
|
audioRequest(false) {
|
||||||
|
mediaPlayerManager.pause(notifyUI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun seekToMediaPlayer(progress: Int) = mediaPlayerManager.seekTo(progress)
|
||||||
|
|
||||||
|
fun stopMediaPlayer() = audioRequest(false, mediaPlayerManager::stop)
|
||||||
|
|
||||||
|
fun queueInMediaPlayer(path: String, msg: ChatMessage) = mediaPlayerManager.addToPlayList(path, msg)
|
||||||
|
|
||||||
|
fun clearMediaPlayerQueue() = mediaPlayerManager.clearPlayList()
|
||||||
|
|
||||||
inner class JoinRoomObserver : Observer<ConversationModel> {
|
inner class JoinRoomObserver : Observer<ConversationModel> {
|
||||||
override fun onSubscribe(d: Disposable) {
|
override fun onSubscribe(d: Disposable) {
|
||||||
|
@ -24,6 +24,7 @@ import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
|
|||||||
import com.nextcloud.talk.utils.message.SendMessageUtils
|
import com.nextcloud.talk.utils.message.SendMessageUtils
|
||||||
import com.stfalcon.chatkit.commons.models.IMessage
|
import com.stfalcon.chatkit.commons.models.IMessage
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ class MessageInputViewModel @Inject constructor(
|
|||||||
val micInputAudioObserver: LiveData<Pair<Float, Float>>
|
val micInputAudioObserver: LiveData<Pair<Float, Float>>
|
||||||
get() = audioRecorderManager.getAudioValues
|
get() = audioRecorderManager.getAudioValues
|
||||||
|
|
||||||
val mediaPlayerSeekbarObserver: LiveData<Int>
|
val mediaPlayerSeekbarObserver: Flow<Int>
|
||||||
get() = mediaPlayerManager.mediaPlayerSeekBarPosition
|
get() = mediaPlayerManager.mediaPlayerSeekBarPosition
|
||||||
|
|
||||||
private val _getEditChatMessage: MutableLiveData<IMessage?> = MutableLiveData()
|
private val _getEditChatMessage: MutableLiveData<IMessage?> = MutableLiveData()
|
||||||
@ -231,7 +232,7 @@ class MessageInputViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun pauseMediaPlayer() {
|
fun pauseMediaPlayer() {
|
||||||
audioFocusRequestManager.audioFocusRequest(false) {
|
audioFocusRequestManager.audioFocusRequest(false) {
|
||||||
mediaPlayerManager.pause()
|
mediaPlayerManager.pause(false)
|
||||||
_isVoicePreviewPlaying.postValue(false)
|
_isVoicePreviewPlaying.postValue(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ import androidx.activity.OnBackPressedCallback
|
|||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||||
import androidx.core.view.MenuItemCompat
|
import androidx.core.view.MenuItemCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
@ -80,6 +81,7 @@ import com.nextcloud.talk.api.NcApi
|
|||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
|
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
|
||||||
import com.nextcloud.talk.chat.ChatActivity
|
import com.nextcloud.talk.chat.ChatActivity
|
||||||
|
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
|
||||||
import com.nextcloud.talk.contacts.ContactsActivityCompose
|
import com.nextcloud.talk.contacts.ContactsActivityCompose
|
||||||
import com.nextcloud.talk.contacts.ContactsUiState
|
import com.nextcloud.talk.contacts.ContactsUiState
|
||||||
import com.nextcloud.talk.contacts.ContactsViewModel
|
import com.nextcloud.talk.contacts.ContactsViewModel
|
||||||
@ -104,6 +106,7 @@ import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
|
|||||||
import com.nextcloud.talk.models.json.participants.Participant
|
import com.nextcloud.talk.models.json.participants.Participant
|
||||||
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
|
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
|
||||||
import com.nextcloud.talk.settings.SettingsActivity
|
import com.nextcloud.talk.settings.SettingsActivity
|
||||||
|
import com.nextcloud.talk.ui.BackgroundVoiceMessageCard
|
||||||
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
|
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
|
||||||
import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
|
import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
|
||||||
import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
|
import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
|
||||||
@ -151,6 +154,7 @@ import org.apache.commons.lang3.builder.CompareToBuilder
|
|||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
|
import java.io.File
|
||||||
import java.util.Objects
|
import java.util.Objects
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -185,6 +189,9 @@ class ConversationsListActivity :
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var networkMonitor: NetworkMonitor
|
lateinit var networkMonitor: NetworkMonitor
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var chatViewModel: ChatViewModel
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var contactsViewModel: ContactsViewModel
|
lateinit var contactsViewModel: ContactsViewModel
|
||||||
|
|
||||||
@ -283,7 +290,7 @@ class ConversationsListActivity :
|
|||||||
if (adapter == null) {
|
if (adapter == null) {
|
||||||
adapter = FlexibleAdapter(conversationItems, this, true)
|
adapter = FlexibleAdapter(conversationItems, this, true)
|
||||||
} else {
|
} else {
|
||||||
binding.loadingContent?.visibility = View.GONE
|
binding.loadingContent.visibility = View.GONE
|
||||||
}
|
}
|
||||||
adapter!!.addListener(this)
|
adapter!!.addListener(this)
|
||||||
prepareViews()
|
prepareViews()
|
||||||
@ -455,6 +462,55 @@ class ConversationsListActivity :
|
|||||||
}
|
}
|
||||||
}.collect()
|
}.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
chatViewModel.backgroundPlayUIFlow.onEach { msg ->
|
||||||
|
binding.composeViewForBackgroundPlay.apply {
|
||||||
|
// Dispose of the Composition when the view's LifecycleOwner is destroyed
|
||||||
|
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||||
|
setContent {
|
||||||
|
msg?.let {
|
||||||
|
val duration = chatViewModel.mediaPlayerDuration
|
||||||
|
val position = chatViewModel.mediaPlayerPosition
|
||||||
|
val offset = position.toFloat() / duration
|
||||||
|
val imageURI = ApiUtils.getUrlForAvatar(
|
||||||
|
currentUser?.baseUrl,
|
||||||
|
msg.actorId,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
val conversationImageURI = ApiUtils.getUrlForConversationAvatar(
|
||||||
|
ApiUtils.API_V1,
|
||||||
|
currentUser?.baseUrl,
|
||||||
|
msg.token
|
||||||
|
)
|
||||||
|
|
||||||
|
if (duration > 0) {
|
||||||
|
BackgroundVoiceMessageCard(
|
||||||
|
msg.actorDisplayName!!,
|
||||||
|
duration - position,
|
||||||
|
offset,
|
||||||
|
imageURI,
|
||||||
|
conversationImageURI,
|
||||||
|
viewThemeUtils,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
.GetView({ isPaused ->
|
||||||
|
if (isPaused) {
|
||||||
|
chatViewModel.pauseMediaPlayer(false)
|
||||||
|
} else {
|
||||||
|
val filename = msg.selectedIndividualHashMap!!["name"]
|
||||||
|
val file = File(context.cacheDir, filename!!)
|
||||||
|
chatViewModel.startMediaPlayer(file.canonicalPath)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
chatViewModel.stopMediaPlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setConversationList(list: List<ConversationModel>) {
|
private fun setConversationList(list: List<ConversationModel>) {
|
||||||
@ -770,8 +826,8 @@ class ConversationsListActivity :
|
|||||||
initSearchDisposable()
|
initSearchDisposable()
|
||||||
adapter?.setHeadersShown(true)
|
adapter?.setHeadersShown(true)
|
||||||
if (!hasFilterEnabled()) filterableConversationItems = searchableConversationItems
|
if (!hasFilterEnabled()) filterableConversationItems = searchableConversationItems
|
||||||
adapter?.updateDataSet(filterableConversationItems, false)
|
adapter!!.updateDataSet(filterableConversationItems, false)
|
||||||
adapter?.showAllHeaders()
|
adapter!!.showAllHeaders()
|
||||||
binding.swipeRefreshLayoutView?.isEnabled = false
|
binding.swipeRefreshLayoutView?.isEnabled = false
|
||||||
searchBehaviorSubject.onNext(true)
|
searchBehaviorSubject.onNext(true)
|
||||||
return true
|
return true
|
||||||
@ -786,9 +842,9 @@ class ConversationsListActivity :
|
|||||||
// cancel any pending searches
|
// cancel any pending searches
|
||||||
searchHelper!!.cancelSearch()
|
searchHelper!!.cancelSearch()
|
||||||
}
|
}
|
||||||
binding.swipeRefreshLayoutView?.isRefreshing = false
|
binding.swipeRefreshLayoutView.isRefreshing = false
|
||||||
searchBehaviorSubject.onNext(false)
|
searchBehaviorSubject.onNext(false)
|
||||||
binding.swipeRefreshLayoutView?.isEnabled = true
|
binding.swipeRefreshLayoutView.isEnabled = true
|
||||||
searchView!!.onActionViewCollapsed()
|
searchView!!.onActionViewCollapsed()
|
||||||
|
|
||||||
binding.conversationListAppbar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
|
binding.conversationListAppbar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
|
||||||
@ -801,7 +857,7 @@ class ConversationsListActivity :
|
|||||||
viewThemeUtils.platform.resetStatusBar(this@ConversationsListActivity)
|
viewThemeUtils.platform.resetStatusBar(this@ConversationsListActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
val layoutManager = binding.recyclerView?.layoutManager as SmoothScrollLinearLayoutManager?
|
val layoutManager = binding.recyclerView.layoutManager as SmoothScrollLinearLayoutManager?
|
||||||
layoutManager?.scrollToPositionWithOffset(0, 0)
|
layoutManager?.scrollToPositionWithOffset(0, 0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -894,18 +950,18 @@ class ConversationsListActivity :
|
|||||||
|
|
||||||
private fun initOverallLayout(isConversationListNotEmpty: Boolean) {
|
private fun initOverallLayout(isConversationListNotEmpty: Boolean) {
|
||||||
if (isConversationListNotEmpty) {
|
if (isConversationListNotEmpty) {
|
||||||
if (binding.emptyLayout?.visibility != View.GONE) {
|
if (binding.emptyLayout.visibility != View.GONE) {
|
||||||
binding.emptyLayout?.visibility = View.GONE
|
binding.emptyLayout.visibility = View.GONE
|
||||||
}
|
}
|
||||||
if (binding.swipeRefreshLayoutView?.visibility != View.VISIBLE) {
|
if (binding.swipeRefreshLayoutView.visibility != View.VISIBLE) {
|
||||||
binding.swipeRefreshLayoutView?.visibility = View.VISIBLE
|
binding.swipeRefreshLayoutView.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (binding.emptyLayout?.visibility != View.VISIBLE) {
|
if (binding.emptyLayout.visibility != View.VISIBLE) {
|
||||||
binding.emptyLayout?.visibility = View.VISIBLE
|
binding.emptyLayout.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
if (binding.swipeRefreshLayoutView?.visibility != View.GONE) {
|
if (binding.swipeRefreshLayoutView.visibility != View.GONE) {
|
||||||
binding.swipeRefreshLayoutView?.visibility = View.GONE
|
binding.swipeRefreshLayoutView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1092,24 +1148,24 @@ class ConversationsListActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
binding.recyclerView?.setOnTouchListener { v: View, _: MotionEvent? ->
|
binding.recyclerView.setOnTouchListener { v: View, _: MotionEvent? ->
|
||||||
if (!isDestroyed) {
|
if (!isDestroyed) {
|
||||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
imm.hideSoftInputFromWindow(v.windowToken, 0)
|
imm.hideSoftInputFromWindow(v.windowToken, 0)
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
binding.swipeRefreshLayoutView?.setOnRefreshListener {
|
binding.swipeRefreshLayoutView.setOnRefreshListener {
|
||||||
fetchRooms()
|
fetchRooms()
|
||||||
fetchPendingInvitations()
|
fetchPendingInvitations()
|
||||||
}
|
}
|
||||||
binding.swipeRefreshLayoutView?.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it) }
|
binding.swipeRefreshLayoutView.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it) }
|
||||||
binding.emptyLayout?.setOnClickListener { showNewConversationsScreen() }
|
binding.emptyLayout.setOnClickListener { showNewConversationsScreen() }
|
||||||
binding.floatingActionButton?.setOnClickListener {
|
binding.floatingActionButton.setOnClickListener {
|
||||||
run(context)
|
run(context)
|
||||||
showNewConversationsScreen()
|
showNewConversationsScreen()
|
||||||
}
|
}
|
||||||
binding.floatingActionButton?.let { viewThemeUtils.material.themeFAB(it) }
|
binding.floatingActionButton.let { viewThemeUtils.material.themeFAB(it) }
|
||||||
|
|
||||||
binding.switchAccountButton.setOnClickListener {
|
binding.switchAccountButton.setOnClickListener {
|
||||||
if (resources != null && resources!!.getBoolean(R.bool.multiaccount_support)) {
|
if (resources != null && resources!!.getBoolean(R.bool.multiaccount_support)) {
|
||||||
@ -1284,7 +1340,7 @@ class ConversationsListActivity :
|
|||||||
|
|
||||||
@SuppressLint("CheckResult") // handled by helper
|
@SuppressLint("CheckResult") // handled by helper
|
||||||
private fun startMessageSearch(search: String?) {
|
private fun startMessageSearch(search: String?) {
|
||||||
binding.swipeRefreshLayoutView?.isRefreshing = true
|
binding.swipeRefreshLayoutView.isRefreshing = true
|
||||||
searchHelper?.startMessageSearch(search!!)
|
searchHelper?.startMessageSearch(search!!)
|
||||||
?.subscribeOn(Schedulers.io())
|
?.subscribeOn(Schedulers.io())
|
||||||
?.observeOn(AndroidSchedulers.mainThread())
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
@ -1539,8 +1595,8 @@ class ConversationsListActivity :
|
|||||||
filesToShare?.forEach {
|
filesToShare?.forEach {
|
||||||
UploadAndShareFilesWorker.upload(
|
UploadAndShareFilesWorker.upload(
|
||||||
it,
|
it,
|
||||||
selectedConversation!!.token!!,
|
selectedConversation!!.token,
|
||||||
selectedConversation!!.displayName!!,
|
selectedConversation!!.displayName,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2016,7 +2072,7 @@ class ConversationsListActivity :
|
|||||||
binding.recyclerView?.scrollToPosition(0)
|
binding.recyclerView?.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.swipeRefreshLayoutView?.isRefreshing = false
|
binding.swipeRefreshLayoutView.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onMessageSearchError(throwable: Throwable) {
|
private fun onMessageSearchError(throwable: Throwable) {
|
||||||
|
@ -12,6 +12,7 @@ import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
|
|||||||
import com.nextcloud.talk.chat.data.io.AudioRecorderManager
|
import com.nextcloud.talk.chat.data.io.AudioRecorderManager
|
||||||
import com.nextcloud.talk.chat.data.io.MediaPlayerManager
|
import com.nextcloud.talk.chat.data.io.MediaPlayerManager
|
||||||
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
|
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
||||||
@ -29,8 +30,10 @@ class ManagerModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideMediaPlayerManager(): MediaPlayerManager {
|
fun provideMediaPlayerManager(preferences: AppPreferences): MediaPlayerManager {
|
||||||
return MediaPlayerManager()
|
return MediaPlayerManager().apply {
|
||||||
|
appPreferences = preferences
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -10,8 +10,8 @@ package com.nextcloud.talk.dagger.modules
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
|
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
|
||||||
import com.nextcloud.talk.contacts.ContactsViewModel
|
|
||||||
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
|
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
|
||||||
|
import com.nextcloud.talk.contacts.ContactsViewModel
|
||||||
import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
|
import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
|
||||||
import com.nextcloud.talk.conversationcreation.ConversationCreationViewModel
|
import com.nextcloud.talk.conversationcreation.ConversationCreationViewModel
|
||||||
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
|
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
|
||||||
@ -125,8 +125,6 @@ abstract class ViewModelModule {
|
|||||||
@ViewModelKey(MessageInputViewModel::class)
|
@ViewModelKey(MessageInputViewModel::class)
|
||||||
abstract fun messageInputViewModel(viewModel: MessageInputViewModel): ViewModel
|
abstract fun messageInputViewModel(viewModel: MessageInputViewModel): ViewModel
|
||||||
|
|
||||||
// TODO I had a merge conflict here that went weird. choose their version
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(ConversationInfoViewModel::class)
|
@ViewModelKey(ConversationInfoViewModel::class)
|
||||||
|
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk - Android Client
|
||||||
|
*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.ui
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.contacts.loadImage
|
||||||
|
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
class BackgroundVoiceMessageCard(
|
||||||
|
val name: String,
|
||||||
|
val duration: Int,
|
||||||
|
private val offset: Float,
|
||||||
|
private val imageURI: String,
|
||||||
|
private val conversationImageURI: String,
|
||||||
|
private var viewThemeUtils: ViewThemeUtils,
|
||||||
|
private var context: Context
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val progressState = mutableFloatStateOf(0.0f)
|
||||||
|
private val animator = ValueAnimator.ofFloat(offset, 1.0f)
|
||||||
|
|
||||||
|
init {
|
||||||
|
animator.duration = duration.toLong()
|
||||||
|
animator.addUpdateListener { animation ->
|
||||||
|
progressState.floatValue = animation.animatedValue as Float
|
||||||
|
}
|
||||||
|
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ACCOUNT_WEIGHT = .8f
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("FunctionNaming", "LongMethod")
|
||||||
|
@Composable
|
||||||
|
fun GetView(onPlayPaused: (isPaused: Boolean) -> Unit, onClosed: () -> Unit) {
|
||||||
|
MaterialTheme(colorScheme = viewThemeUtils.getColorScheme(context)) {
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp, 0.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(8.dp))
|
||||||
|
.fillMaxWidth(progressState.floatValue)
|
||||||
|
.height(4.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
) {
|
||||||
|
var isPausedIcon by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
isPausedIcon = !isPausedIcon
|
||||||
|
onPlayPaused(isPausedIcon)
|
||||||
|
if (isPausedIcon) {
|
||||||
|
animator.pause()
|
||||||
|
} else {
|
||||||
|
animator.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isPausedIcon) {
|
||||||
|
Icons.Filled.PlayArrow
|
||||||
|
} else {
|
||||||
|
ImageVector.vectorResource(R.drawable.ic_baseline_pause_voice_message_24)
|
||||||
|
},
|
||||||
|
contentDescription = "contentDescription",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.size(16.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(ACCOUNT_WEIGHT)
|
||||||
|
.align(Alignment.CenterVertically),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Box {
|
||||||
|
val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
|
||||||
|
val loadedImage = loadImage(imageURI, context, errorPlaceholderImage)
|
||||||
|
val conversationImage = loadImage(
|
||||||
|
conversationImageURI,
|
||||||
|
context,
|
||||||
|
errorPlaceholderImage
|
||||||
|
)
|
||||||
|
AsyncImage(
|
||||||
|
model = conversationImage,
|
||||||
|
contentDescription = stringResource(R.string.user_avatar),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(width = 45.dp, height = 45.dp)
|
||||||
|
.padding(8.dp)
|
||||||
|
.offset(10.dp, 10.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
AsyncImage(
|
||||||
|
model = loadedImage,
|
||||||
|
contentDescription = stringResource(R.string.user_avatar),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(width = 45.dp, height = 45.dp)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
onClosed()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Close,
|
||||||
|
contentDescription = "contentDescription",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.padding(2.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,12 +11,8 @@ package com.nextcloud.talk.utils.preferences;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel;
|
|
||||||
import com.nextcloud.talk.ui.PlaybackSpeed;
|
import com.nextcloud.talk.ui.PlaybackSpeed;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@SuppressLint("NonConstantResourceId")
|
@SuppressLint("NonConstantResourceId")
|
||||||
public interface AppPreferences {
|
public interface AppPreferences {
|
||||||
|
|
||||||
@ -175,9 +171,11 @@ public interface AppPreferences {
|
|||||||
|
|
||||||
int getLastKnownId(String internalConversationId, int defaultValue);
|
int getLastKnownId(String internalConversationId, int defaultValue);
|
||||||
|
|
||||||
void saveVoiceMessagePlaybackSpeedPreferences(Map<String, PlaybackSpeed> speeds);
|
void deleteAllMessageQueuesFor(String userId);
|
||||||
|
|
||||||
Map<String, PlaybackSpeed> readVoiceMessagePlaybackSpeedPreferences();
|
void savePreferredPlayback(String userId, PlaybackSpeed speed);
|
||||||
|
|
||||||
|
PlaybackSpeed getPreferredPlayback(String userId);
|
||||||
|
|
||||||
Long getNotificationWarningLastPostponedDate();
|
Long getNotificationWarningLastPostponedDate();
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
package com.nextcloud.talk.utils.preferences
|
package com.nextcloud.talk.utils.preferences
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
@ -24,9 +23,6 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.serialization.SerializationException
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
@Suppress("TooManyFunctions", "DeferredResultUnused", "EmptyFunctionBlock")
|
@Suppress("TooManyFunctions", "DeferredResultUnused", "EmptyFunctionBlock")
|
||||||
@ -500,25 +496,41 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
|
|||||||
return if (lastReadId.isNotEmpty()) lastReadId.toInt() else defaultValue
|
return if (lastReadId.isNotEmpty()) lastReadId.toInt() else defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveVoiceMessagePlaybackSpeedPreferences(speeds: Map<String, PlaybackSpeed>) {
|
override fun deleteAllMessageQueuesFor(userId: String) {
|
||||||
Json.encodeToString(speeds).let {
|
runBlocking {
|
||||||
runBlocking<Unit> { async { writeString(VOICE_MESSAGE_PLAYBACK_SPEEDS, it) } }
|
async {
|
||||||
|
val keyList = mutableListOf<Preferences.Key<*>>()
|
||||||
|
val preferencesMap = context.dataStore.data.first().asMap()
|
||||||
|
for (preference in preferencesMap) {
|
||||||
|
if (preference.key.name.contains("$userId@")) {
|
||||||
|
keyList.add(preference.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readVoiceMessagePlaybackSpeedPreferences(): Map<String, PlaybackSpeed> {
|
for (key in keyList) {
|
||||||
return runBlocking {
|
context.dataStore.edit {
|
||||||
async { readString(VOICE_MESSAGE_PLAYBACK_SPEEDS, "{}").first() }
|
it.remove(key)
|
||||||
}.getCompleted().let {
|
|
||||||
try {
|
|
||||||
Json.decodeFromString<HashMap<String, String>>(it)
|
|
||||||
.map { entry -> entry.key to PlaybackSpeed.byName(entry.value) }.toMap()
|
|
||||||
} catch (e: SerializationException) {
|
|
||||||
Log.e(TAG, "ignoring invalid json format in voice message playback speed preferences", e)
|
|
||||||
emptyMap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun savePreferredPlayback(userId: String, speed: PlaybackSpeed) {
|
||||||
|
runBlocking<Unit> {
|
||||||
|
async {
|
||||||
|
writeString(userId + PLAY_BACK, speed.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreferredPlayback(userId: String): PlaybackSpeed =
|
||||||
|
runBlocking {
|
||||||
|
async {
|
||||||
|
val name = readString(userId + PLAY_BACK).first()
|
||||||
|
return@async if (name == "") PlaybackSpeed.NORMAL else PlaybackSpeed.byName(name)
|
||||||
|
}
|
||||||
|
}.getCompleted()
|
||||||
|
|
||||||
override fun getNotificationWarningLastPostponedDate(): Long =
|
override fun getNotificationWarningLastPostponedDate(): Long =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
@ -609,6 +621,8 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
|
|||||||
const val DB_ROOM_MIGRATED = "db_room_migrated"
|
const val DB_ROOM_MIGRATED = "db_room_migrated"
|
||||||
const val PHONE_BOOK_INTEGRATION_LAST_RUN = "phone_book_integration_last_run"
|
const val PHONE_BOOK_INTEGRATION_LAST_RUN = "phone_book_integration_last_run"
|
||||||
const val TYPING_STATUS = "typing_status"
|
const val TYPING_STATUS = "typing_status"
|
||||||
|
const val MESSAGE_QUEUE = "@message_queue"
|
||||||
|
const val PLAY_BACK = "_playback"
|
||||||
const val VOICE_MESSAGE_PLAYBACK_SPEEDS = "voice_message_playback_speeds"
|
const val VOICE_MESSAGE_PLAYBACK_SPEEDS = "voice_message_playback_speeds"
|
||||||
const val SHOW_REGULAR_NOTIFICATION_WARNING = "show_regular_notification_warning"
|
const val SHOW_REGULAR_NOTIFICATION_WARNING = "show_regular_notification_warning"
|
||||||
const val LAST_NOTIFICATION_WARNING = "last_notification_warning"
|
const val LAST_NOTIFICATION_WARNING = "last_notification_warning"
|
||||||
|
@ -143,6 +143,11 @@
|
|||||||
app:popupTheme="@style/appActionBarPopupMenu"
|
app:popupTheme="@style/appActionBarPopupMenu"
|
||||||
app:titleTextColor="@color/fontAppbar"
|
app:titleTextColor="@color/fontAppbar"
|
||||||
tools:title="@string/nc_app_product_name" />
|
tools:title="@string/nc_app_product_name" />
|
||||||
|
|
||||||
|
<androidx.compose.ui.platform.ComposeView
|
||||||
|
android:id="@+id/composeViewForBackgroundPlay"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -272,8 +272,6 @@ How to translate with transifex:
|
|||||||
<string name="nc_contacts_done">Done</string>
|
<string name="nc_contacts_done">Done</string>
|
||||||
<string name="user_avatar">User avatar</string>
|
<string name="user_avatar">User avatar</string>
|
||||||
<string name="back_button">Back button</string>
|
<string name="back_button">Back button</string>
|
||||||
<string name="new_conversation_creation_icon">New Conversation Creation Icon</string>
|
|
||||||
<string name="join_open_conversations_icon">Join Open Conversations Icon</string>
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
<string name="nc_permissions_rationale_dialog_title">Please allow permissions</string>
|
<string name="nc_permissions_rationale_dialog_title">Please allow permissions</string>
|
||||||
<string name="nc_permissions_denied">Some permissions were denied.</string>
|
<string name="nc_permissions_denied">Some permissions were denied.</string>
|
||||||
|
@ -1339,6 +1339,8 @@ vCeonVI7Q1CkIHt8u7eMgzfEkaiPLZlI0l0RpfT4pnNieqg=
|
|||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
pub BAC30622339994C4
|
pub BAC30622339994C4
|
||||||
|
uid Chris Povirk <cpovirk@google.com>
|
||||||
|
|
||||||
sub FC9BDC25FB378008
|
sub FC9BDC25FB378008
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
@ -1347,20 +1349,21 @@ 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
|
||||||
i8/b+PwSW+nW3EpQZdLqZtOU
|
QzLSAVn44AHSomlgzecMR6RZk/fwbzSSiOc3rW1mNFOG70iLz9v4/BJb6dbcSlBl
|
||||||
=2H2i
|
0upm05Q=
|
||||||
|
=Gf3Y
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
pub BCF4173966770193
|
pub BCF4173966770193
|
||||||
@ -1449,42 +1452,6 @@ lQyC8nl8P5PgkEZ5CHcGymZlpzihR3ECrPJTk39Sb7D3SxCW4WrChV3kVfmLgvc=
|
|||||||
=WqT9
|
=WqT9
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
pub C020E96222A31FB3
|
|
||||||
uid Eric Li <eric@swiftzer.net>
|
|
||||||
|
|
||||||
sub 55CDD67958ACCA47
|
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
mQGNBGAephsBDADH0j84tkTcmvOYskQWjA3M8hLNJI5QdagcYviR2yDTBq7paSP6
|
|
||||||
hLDrcCwTfvCNIatYI4hGau31RlkNKJHZumMZzF8OarWuKKkwwik3Z8pulMHgC8kR
|
|
||||||
SX5QM8k4wIeZLbD0UzW22gr0oLDilb3cFr86Y9T4AD6Ke0JATFRU+TrMAT5e5iYe
|
|
||||||
iVcNGuRQMvjncJobNIt4AeP12GV14p0GhAlka8Hwq24dTue5xwBJ9GwYWwPz6dbo
|
|
||||||
k83dZJdhLDfvL6ojG4umByeqCn/ARuJCuI0YABLO7BoqsAJvMfCMciP6Upu4iVT8
|
|
||||||
DduCd7mV7YaByqtktRJDzaNiJa36riYOnzAVsKB1QbnWD2tP2kcR0N37104+WtkH
|
|
||||||
GYkSfnZujfvmoHf4hws+6oUgfPs+1vMMYj0AnlotcDVez8sHSAwQN+rzfqqii8lj
|
|
||||||
9DdpScq+yamQratHBHIkdyx1xyd+Xy00Vs9NY03gIeFFM2Rjat+XDfK+uO/Mpkkx
|
|
||||||
Gcf9d6lC+OGUO88AEQEAAbQbRXJpYyBMaSA8ZXJpY0Bzd2lmdHplci5uZXQ+uQGN
|
|
||||||
BGAephsBDACvxELzqfLQrmLHOlpJru6cCqQgPCE6DIQnNYJ6nTyAow6tBLQ7b2MP
|
|
||||||
ACn1yqRskE1qzh415B2tcZqN8IpguN9NssqINyKdxcOYogmcdfnhN8TWYYUCKBD9
|
|
||||||
DssMhz24bq2GjcLxyPagrvACI5O+k+LYE3TQbLE/t/6oG3grgkWHJWKLA/ou812+
|
|
||||||
eDOI+/HaME/1uT3DwsGv57zoZaIwADWmdotoEyU/d+cK+5A2727PCS0hfDleDJ1T
|
|
||||||
rJoT2YRN9YJhGTl3xq/XmhfmcYX3KlTKakENZXsi/x9n2sxpCpE1xMa1kIB6SmZi
|
|
||||||
l88oxa7+zunRVNK3ymGxVxnLlltTyO/VyxQh1AJFhI6n35ls3l4BSEmUjNRi76VX
|
|
||||||
cuFH0TnyKEIHxZYv1K5FjTF84yukKrLbWKgWsvr8exHQuKjz+iU5NvmhY0g2CLwa
|
|
||||||
9P2fjA+nGWuKhLZyDh8M0+sXEuVTbiHgq8dkr94yBwqCs8quqC4PHW4bSivZj5ml
|
|
||||||
nrgujB35H/EAEQEAAYkBtgQYAQgAIBYhBNvV4c9Kf/PYynRZ2MAg6WIiox+zBQJg
|
|
||||||
HqYbAhsMAAoJEMAg6WIiox+zi/UL/1/OT875lTWbpoPIIi63ymL/dpkinRZQMQWY
|
|
||||||
jEsMd6Ea2/tVCbYt6zFXZNBIbJ3WmPN1/ZWFh+PWHla/GlUhksPuSFt8Jf3YL0QQ
|
|
||||||
0vHwErdulWBLssWMGmlQmISeRVYPkjha1gpBcbKCaWHhXRuf/FsrYpb0NqkArRf1
|
|
||||||
+fdhRdsOdy9avioy5l+/Ld+puaJWMKJbet2ARzQ9lOWCyOK6JoxO8U11jupKOMfa
|
|
||||||
gp5iowztQHcZ53IvIFJGPDCe0pb8l5owFpG8rmOrLuPVlRTMvR/n+MnI3hkswg+4
|
|
||||||
2Y9hslJKenF2utD0q0eU6VYnsquSHbsypDjx6zwUZvaa6olCIxNZoVJw/wSv1ZDe
|
|
||||||
/8U0TEL7OXe9jA6QLEDYAPytSF/mVIqSp4dgAPrADXSt8UOvq3jQoNMTESbJWWX8
|
|
||||||
169pc1yMD6HBdGShunnJ+slCQ/nJ6zFSMKLTJgOp3pRJYxfDlWoZVm5mSLsif8yD
|
|
||||||
yc0PGiCz66h9jPMKXJJ1o9MGQKvydw==
|
|
||||||
=+Ahq
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
pub C21CE653B639E41A
|
pub C21CE653B639E41A
|
||||||
sub 4F80368F9034B8D0
|
sub 4F80368F9034B8D0
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
@ -5670,13 +5637,15 @@ xOcUt3JhIGtKwRMO4mte4wmT6Ko+Nj4uy6tFjbTfN2eBins/1F9qLU4YJUqC4QD4
|
|||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
pub 7721F63BD38B4796
|
pub 7721F63BD38B4796
|
||||||
sub 4EB27DB2A3B88B8B
|
uid Google Inc. (Linux Packages Signing Authority) <linux-packages-keymaster@google.com>
|
||||||
sub 1397BC53640DB551
|
|
||||||
sub 78BD65473CB3BD13
|
|
||||||
sub 6494C6D6997C215E
|
|
||||||
sub FD533C07C264648F
|
sub FD533C07C264648F
|
||||||
sub 32EE5355A6BC6E42
|
sub 32EE5355A6BC6E42
|
||||||
sub E88979FB9B30ACF2
|
sub E88979FB9B30ACF2
|
||||||
|
sub 1397BC53640DB551
|
||||||
|
sub 6494C6D6997C215E
|
||||||
|
sub 78BD65473CB3BD13
|
||||||
|
sub 4EB27DB2A3B88B8B
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
mQINBFcMjNMBEAC6Wr5QuLIFgz1V1EFPlg8ty2TsjQEl4VWftUAqWlMevJFWvYEx
|
mQINBFcMjNMBEAC6Wr5QuLIFgz1V1EFPlg8ty2TsjQEl4VWftUAqWlMevJFWvYEx
|
||||||
@ -5690,295 +5659,411 @@ xeqTWDlzONUpOs5yBjF1cfJSdVxsfshvln2JXUwgIdKl4DLbZybuNFXnPffNLb2v
|
|||||||
PtRJHO48O2UbeXS8n27PcuMoLRd7+r7TsqG2vBH4t/cB/1vsvWMbqnQlaJ5VsjeW
|
PtRJHO48O2UbeXS8n27PcuMoLRd7+r7TsqG2vBH4t/cB/1vsvWMbqnQlaJ5VsjeW
|
||||||
Tp8Gv9FJiKuU8PKiWsF4EGR/kAFyCB8QbJeQ6HrOT0CXLOaYHRu2TvJ4taY9doXn
|
Tp8Gv9FJiKuU8PKiWsF4EGR/kAFyCB8QbJeQ6HrOT0CXLOaYHRu2TvJ4taY9doXn
|
||||||
98TgU03XTLcYoSp49cdkkis4K+9hd2dUqARVCG7UVd9PY60VVCKi47BVKQARAQAB
|
98TgU03XTLcYoSp49cdkkis4K+9hd2dUqARVCG7UVd9PY60VVCKi47BVKQARAQAB
|
||||||
uQINBGF4DJ8BEACk2Gwau+s/pKmOTnGLMnB3ybQsiVGLRhsw2SqSTvSyBthAyW1U
|
tFRHb29nbGUgSW5jLiAoTGludXggUGFja2FnZXMgU2lnbmluZyBBdXRob3JpdHkp
|
||||||
AqdRqNA8/FdMlvVuppG8+vCLXPmpP63C+9M2tyQeOR2aVQp+u1EIwN4lPu4wrh6v
|
IDxsaW51eC1wYWNrYWdlcy1rZXltYXN0ZXJAZ29vZ2xlLmNvbT65Ag0EZ31b7QEQ
|
||||||
dtgSRim8uxBdLIHG16z0xxVhE2rM/Ot/gucfkpoEw289VaR7sPmIxfVTm1QcqCGi
|
AMEWM0g1KKIxE7q8JK7QblKom7++NYn92E3suHv3WxqzrhRT9tYNDSaoOazQP+ha
|
||||||
FQl3rZnma6Bz8UOXJoE8wO+LK5WkcdmFz6+Z3BLSb5IL9lhsArFToNq5dN2SSTbC
|
NA2BqkdFcB7G4jKdtK2VLFc7RBpcR2rnQEJWgpeP03DHrdZYFdpH9zABoFsotgZR
|
||||||
TdHRzrRuoCdefYHdxoLCM4kJfggRRgWhKoEJro+ZipESq1T5yHV/iAJy+3DuC8Lb
|
KwwTOoxdm6XtV+47LEY9yAefPrt1gPJQ4h1SKwWIFSRPChQ1cThBz2QD2LaPAGtj
|
||||||
YLvsjt9VZYARw8xIGb90Vj3ThWuMoVr/IVmKT7foC5Whe0PTI/b2frNaWCxxC4cR
|
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
|
||||||
c8LNGrDaCFdXnOdlNV/zT9VvBk/RkV+Tl/Lk4g==
|
mzvdN5YoV9ohJoa1Bxj7qialTT/Su1Eb/toOOkOlqQ7B+1NBXzv9FmiBntC4afyk
|
||||||
=AP/d
|
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
|
||||||
|
Y7Iyz4ygr8etweBPnEIvjwpAZxOu
|
||||||
|
=wwl/
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
pub 79752DB6C966F0B8
|
pub 79752DB6C966F0B8
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
<trust group="org.javassist" name="javassist" version="3.26.0-GA" reason="java assist"/>
|
<trust group="org.javassist" name="javassist" version="3.26.0-GA" reason="java assist"/>
|
||||||
<trust file=".*-sources[.]jar" regex="true"/>
|
<trust file=".*-sources[.]jar" regex="true"/>
|
||||||
</trusted-artifacts>
|
</trusted-artifacts>
|
||||||
|
<ignored-keys>
|
||||||
|
<ignored-key id="C020E96222A31FB3" reason="Key couldn't be downloaded from any key server"/>
|
||||||
|
</ignored-keys>
|
||||||
<trusted-keys>
|
<trusted-keys>
|
||||||
<trusted-key id="02A36B6DB7056EB5E6FFEF893DA731F041734930" group="org.parceler"/>
|
<trusted-key id="02A36B6DB7056EB5E6FFEF893DA731F041734930" group="org.parceler"/>
|
||||||
<trusted-key id="03D5EBC6C81161316CF21CEE1592D9DA6586CF26" group="^com[.]afollestad($|([.].*))" regex="true"/>
|
<trusted-key id="03D5EBC6C81161316CF21CEE1592D9DA6586CF26" group="^com[.]afollestad($|([.].*))" regex="true"/>
|
||||||
@ -10244,10 +10247,10 @@
|
|||||||
</component>
|
</component>
|
||||||
<component group="net.swiftzer.semver" name="semver" version="1.1.1">
|
<component group="net.swiftzer.semver" name="semver" version="1.1.1">
|
||||||
<artifact name="semver-1.1.1.jar">
|
<artifact name="semver-1.1.1.jar">
|
||||||
<sha256 value="f028546d70326b93314a4f448cfd217b50b801ec20313cd46028a94604f46d66" origin="Generated by Gradle"/>
|
<sha256 value="f028546d70326b93314a4f448cfd217b50b801ec20313cd46028a94604f46d66" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
<artifact name="semver-1.1.1.pom">
|
<artifact name="semver-1.1.1.pom">
|
||||||
<sha256 value="320c1150c2c5dc40937423f9e888e10d9e8b2d4e1346c349864816ba2afe39ff" origin="Generated by Gradle"/>
|
<sha256 value="320c1150c2c5dc40937423f9e888e10d9e8b2d4e1346c349864816ba2afe39ff" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
<component group="net.zetetic" name="android-database-sqlcipher" version="4.5.4">
|
<component group="net.zetetic" name="android-database-sqlcipher" version="4.5.4">
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
DO NOT TOUCH; GENERATED BY DRONE
|
DO NOT TOUCH; GENERATED BY DRONE
|
||||||
<span class="mdl-layout-title">Lint Report: 9 errors and 104 warnings</span>
|
<span class="mdl-layout-title">Lint Report: 9 errors and 103 warnings</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user