mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-18 19:19:33 +01:00
Abstracting away media player functionality to MediaPlayerManager
- Most code removed from ChatActivity - Most work in MediaPlayerManager - Added BackgroundVoiceMessageCard Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
This commit is contained in:
parent
459913d5d2
commit
d26697b932
@ -36,7 +36,9 @@ import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import com.stfalcon.chatkit.messages.MessageHolders
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ExecutionException
|
||||
@ -61,9 +63,8 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
||||
@Inject
|
||||
lateinit var dateUtils: DateUtils
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var appPreferences: AppPreferences? = null
|
||||
lateinit var appPreferences: AppPreferences
|
||||
|
||||
lateinit var message: ChatMessage
|
||||
|
||||
@ -83,7 +84,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
val filename = message.selectedIndividualHashMap!!["name"]
|
||||
val retrieved = appPreferences!!.getWaveFormFromFile(filename)
|
||||
val retrieved = appPreferences.getWaveFormFromFile(filename)
|
||||
if (retrieved.isNotEmpty() &&
|
||||
message.voiceMessageFloatArray == null ||
|
||||
message.voiceMessageFloatArray?.isEmpty() == true
|
||||
@ -103,7 +104,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
||||
setParentMessageDataOnMessageItem(message)
|
||||
|
||||
updateDownloadState(message)
|
||||
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
|
||||
binding.seekbar.max = MAX
|
||||
viewThemeUtils.talk.themeWaveFormSeekBar(binding.seekbar)
|
||||
viewThemeUtils.platform.colorCircularProgressBar(binding.progressBar, ColorRole.ON_SURFACE_VARIANT)
|
||||
|
||||
@ -139,10 +140,16 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
||||
}
|
||||
})
|
||||
|
||||
voiceMessageInterface.registerMessageToObservePlaybackSpeedPreferences(message.user.id) { speed ->
|
||||
binding.playbackSpeedControlBtn.setSpeed(speed)
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
(voiceMessageInterface as ChatActivity).chatViewModel.voiceMessagePlayBackUIFlow.onEach { speed ->
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.playbackSpeedControlBtn.setSpeed(speed)
|
||||
}
|
||||
}.collect()
|
||||
}
|
||||
|
||||
binding.playbackSpeedControlBtn.setSpeed(appPreferences.getPreferredPlayback(message.actorId))
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
@ -158,9 +165,6 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
||||
|
||||
private fun showVoiceMessageDuration(message: ChatMessage) {
|
||||
if (message.voiceMessageDuration > 0) {
|
||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(
|
||||
message.voiceMessageDuration.toLong()
|
||||
)
|
||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.voiceMessageDuration.visibility = View.INVISIBLE
|
||||
@ -200,7 +204,6 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
||||
val t = message.voiceMessagePlayedSeconds.toLong()
|
||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
|
||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
|
||||
binding.seekbar.progress = message.voiceMessageSeekbarProgress
|
||||
} else {
|
||||
showVoiceMessageDuration(message)
|
||||
@ -372,6 +375,6 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
||||
companion object {
|
||||
private const val TAG = "VoiceInMessageView"
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ExecutionException
|
||||
@ -65,9 +67,8 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
||||
@Inject
|
||||
lateinit var dateUtils: DateUtils
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var appPreferences: AppPreferences? = null
|
||||
lateinit var appPreferences: AppPreferences
|
||||
|
||||
lateinit var message: ChatMessage
|
||||
|
||||
@ -90,7 +91,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
||||
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
|
||||
|
||||
val filename = message.selectedIndividualHashMap!!["name"]
|
||||
val retrieved = appPreferences!!.getWaveFormFromFile(filename)
|
||||
val retrieved = appPreferences.getWaveFormFromFile(filename)
|
||||
if (retrieved.isNotEmpty() &&
|
||||
message.voiceMessageFloatArray == null ||
|
||||
message.voiceMessageFloatArray?.isEmpty() == true
|
||||
@ -99,6 +100,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
||||
binding.seekbar.setWaveData(message.voiceMessageFloatArray!!)
|
||||
}
|
||||
|
||||
binding.seekbar.max = MAX
|
||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||
|
||||
colorizeMessageBubble(message)
|
||||
@ -136,10 +138,16 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
||||
|
||||
setReadStatus(message.readStatus)
|
||||
|
||||
voiceMessageInterface.registerMessageToObservePlaybackSpeedPreferences(message.user.id) { speed ->
|
||||
binding.playbackSpeedControlBtn.setSpeed(speed)
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
(voiceMessageInterface as ChatActivity).chatViewModel.voiceMessagePlayBackUIFlow.onEach { speed ->
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.playbackSpeedControlBtn.setSpeed(speed)
|
||||
}
|
||||
}.collect()
|
||||
}
|
||||
|
||||
binding.playbackSpeedControlBtn.setSpeed(appPreferences.getPreferredPlayback(message.actorId))
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
@ -199,9 +207,6 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
||||
|
||||
private fun showVoiceMessageDuration(message: ChatMessage) {
|
||||
if (message.voiceMessageDuration > 0) {
|
||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(
|
||||
message.voiceMessageDuration.toLong()
|
||||
)
|
||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.voiceMessageDuration.visibility = View.INVISIBLE
|
||||
@ -234,7 +239,6 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
||||
val t = message.voiceMessagePlayedSeconds.toLong()
|
||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
|
||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
|
||||
binding.seekbar.progress = message.voiceMessageSeekbarProgress
|
||||
} else {
|
||||
showVoiceMessageDuration(message)
|
||||
@ -377,6 +381,6 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
||||
companion object {
|
||||
private const val TAG = "VoiceOutMessageView"
|
||||
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.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@ -332,24 +330,12 @@ class ChatActivity :
|
||||
private val filesToUpload: MutableList<String> = ArrayList()
|
||||
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
|
||||
|
||||
private var videoURI: Uri? = null
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (currentlyPlayedVoiceMessage != null) {
|
||||
stopMediaPlayer(currentlyPlayedVoiceMessage!!)
|
||||
}
|
||||
val intent = Intent(this@ChatActivity, ConversationsListActivity::class.java)
|
||||
intent.putExtras(Bundle())
|
||||
startActivity(intent)
|
||||
@ -361,22 +347,6 @@ class ChatActivity :
|
||||
val typingParticipants = HashMap<String, TypingParticipant>()
|
||||
|
||||
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 {
|
||||
override fun onSwitchTo(token: String?) {
|
||||
@ -458,35 +428,7 @@ class ChatActivity :
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
|
||||
appPreferences.readVoiceMessagePlaybackSpeedPreferences().let { playbackSpeedPreferences ->
|
||||
chatViewModel.applyPlaybackSpeedPreferences(playbackSpeedPreferences)
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -551,17 +493,6 @@ class ChatActivity :
|
||||
}
|
||||
|
||||
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()
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
@ -933,6 +864,12 @@ class ChatActivity :
|
||||
}.collect()
|
||||
}
|
||||
|
||||
this.lifecycleScope.launch {
|
||||
chatViewModel.mediaPlayerSeekbarObserver.onEach { msg ->
|
||||
adapter?.update(msg)
|
||||
}.collect()
|
||||
}
|
||||
|
||||
chatViewModel.reactionDeletedViewState.observe(this) { state ->
|
||||
when (state) {
|
||||
is ChatViewModel.ReactionDeletedSuccessState -> {
|
||||
@ -1171,8 +1108,6 @@ class ChatActivity :
|
||||
|
||||
setupSwipeToReply()
|
||||
|
||||
chatViewModel.voiceMessagePlaybackSpeedPreferences.observe(this, playbackSpeedPreferencesObserver)
|
||||
|
||||
binding.unreadMessagesPopup.setOnClickListener {
|
||||
binding.messagesListView.smoothScrollToPosition(0)
|
||||
binding.unreadMessagesPopup.visibility = View.GONE
|
||||
@ -1267,13 +1202,15 @@ class ChatActivity :
|
||||
val file = File(context.cacheDir, filename!!)
|
||||
if (file.exists()) {
|
||||
if (message.isPlayingVoiceMessage) {
|
||||
pausePlayback(message)
|
||||
chatViewModel.pauseMediaPlayer(true)
|
||||
message.isPlayingVoiceMessage = false
|
||||
adapter?.update(message)
|
||||
} else {
|
||||
val retrieved = appPreferences.getWaveFormFromFile(filename)
|
||||
if (retrieved.isEmpty()) {
|
||||
setUpWaveform(message)
|
||||
} else {
|
||||
startPlayback(message)
|
||||
startPlayback(file, message)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -1286,11 +1223,8 @@ class ChatActivity :
|
||||
|
||||
adapter?.registerViewClickListener(R.id.playbackSpeedControlBtn) { button, message ->
|
||||
val nextSpeed = (button as PlaybackSpeedControl).getSpeed().next()
|
||||
HashMap(appPreferences.readVoiceMessagePlaybackSpeedPreferences()).let { playbackSpeedPreferences ->
|
||||
playbackSpeedPreferences[message.user.id] = nextSpeed
|
||||
chatViewModel.applyPlaybackSpeedPreferences(playbackSpeedPreferences)
|
||||
appPreferences.saveVoiceMessagePlaybackSpeedPreferences(playbackSpeedPreferences)
|
||||
}
|
||||
chatViewModel.setPlayBack(nextSpeed)
|
||||
appPreferences.savePreferredPlayback(conversationUser!!.userId, nextSpeed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1305,14 +1239,37 @@ class ChatActivity :
|
||||
appPreferences.saveWaveFormForFile(filename, r.toTypedArray())
|
||||
message.voiceMessageFloatArray = r
|
||||
withContext(Dispatchers.Main) {
|
||||
startPlayback(message, thenPlay, backgroundPlayAllowed)
|
||||
startPlayback(file, message)
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
val messageHolders = MessageHolders()
|
||||
val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!, viewThemeUtils)
|
||||
@ -1692,253 +1649,20 @@ class ChatActivity :
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod", "Detekt.NestedBlockDepth")
|
||||
private fun startPlayback(message: ChatMessage, doPlay: Boolean = true, backgroundPlayAllowed: Boolean = false) {
|
||||
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 updateMediaPlayerProgressBySlider(message: ChatMessage, progress: Int) {
|
||||
chatViewModel.seekToMediaPlayer(progress)
|
||||
}
|
||||
|
||||
override fun registerMessageToObservePlaybackSpeedPreferences(
|
||||
userId: String,
|
||||
listener: (speed: PlaybackSpeed) -> Unit
|
||||
) {
|
||||
chatViewModel.voiceMessagePlaybackSpeedPreferences.let { liveData ->
|
||||
liveData.observe(this) { playbackSpeedPreferences ->
|
||||
listener(playbackSpeedPreferences[userId] ?: PlaybackSpeed.NORMAL)
|
||||
}
|
||||
liveData.value?.let { playbackSpeedPreferences ->
|
||||
listener(playbackSpeedPreferences[userId] ?: PlaybackSpeed.NORMAL)
|
||||
}
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
chatViewModel.voiceMessagePlayBackUIFlow.onEach { speed ->
|
||||
withContext(Dispatchers.Main) {
|
||||
listener(speed)
|
||||
}
|
||||
}.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -2610,8 +2334,6 @@ class ChatActivity :
|
||||
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
||||
mentionAutocomplete?.dismissPopup()
|
||||
}
|
||||
|
||||
chatViewModel.voiceMessagePlaybackSpeedPreferences.removeObserver(playbackSpeedPreferencesObserver)
|
||||
}
|
||||
|
||||
private fun isActivityNotChangingConfigurations(): Boolean = !isChangingConfigurations
|
||||
@ -2677,8 +2399,7 @@ class ChatActivity :
|
||||
actionBar?.setIcon(null)
|
||||
}
|
||||
|
||||
currentlyPlayedVoiceMessage?.let { stopMediaPlayer(it) } // FIXME, mediaplayer can sometimes be null here
|
||||
|
||||
adapter = null
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
@ -2972,8 +2693,6 @@ class ChatActivity :
|
||||
adapter?.addToEnd(chatMessageList, false)
|
||||
}
|
||||
scrollToRequestedMessageIfNeeded()
|
||||
// FENOM: add here audio resume policy
|
||||
resumeAudioPlaybackIfNeeded()
|
||||
}
|
||||
|
||||
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>? {
|
||||
if (adapter != null) {
|
||||
val messagePosition = adapter!!.items!!.indexOfFirst {
|
||||
|
@ -16,6 +16,7 @@ import android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||
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.databinding.FragmentMessageInputVoiceRecordingBinding
|
||||
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
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
@ -60,6 +64,7 @@ class MessageInputVoiceRecordingFragment : Fragment() {
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
chatActivity.messageInputViewModel.stopMediaPlayer() // if it wasn't stopped already
|
||||
this.lifecycle.removeObserver(chatActivity.messageInputViewModel)
|
||||
}
|
||||
|
||||
@ -68,13 +73,16 @@ class MessageInputVoiceRecordingFragment : Fragment() {
|
||||
chatActivity.messageInputViewModel.micInputAudioObserver.observe(viewLifecycleOwner) {
|
||||
binding.micInputCloud.setRotationSpeed(it.first, it.second)
|
||||
}
|
||||
chatActivity.messageInputViewModel.mediaPlayerSeekbarObserver.observe(viewLifecycleOwner) { progress ->
|
||||
if (progress >= SEEK_LIMIT) {
|
||||
togglePausePlay()
|
||||
binding.seekbar.progress = 0
|
||||
} else if (!pause) {
|
||||
binding.seekbar.progress = progress
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
chatActivity.messageInputViewModel.mediaPlayerSeekbarObserver.onEach { progress ->
|
||||
if (progress >= SEEK_LIMIT) {
|
||||
togglePausePlay()
|
||||
binding.seekbar.progress = 0
|
||||
} else if (!pause && chatActivity.messageInputViewModel.isVoicePreviewPlaying.value == true) {
|
||||
binding.seekbar.progress = progress
|
||||
}
|
||||
}.collect()
|
||||
}
|
||||
|
||||
chatActivity.messageInputViewModel.getAudioFocusChange.observe(viewLifecycleOwner) { state ->
|
||||
@ -107,7 +115,7 @@ class MessageInputVoiceRecordingFragment : Fragment() {
|
||||
binding.sendVoiceRecording.setOnClickListener {
|
||||
chatActivity.chatViewModel.stopAndSendAudioRecording(
|
||||
chatActivity.roomToken,
|
||||
chatActivity.currentConversation!!.displayName!!,
|
||||
chatActivity.currentConversation!!.displayName,
|
||||
MessageInputFragment.VOICE_MESSAGE_META_DATA
|
||||
)
|
||||
clear()
|
||||
|
@ -9,44 +9,120 @@ package com.nextcloud.talk.chat.data.io
|
||||
|
||||
import android.media.MediaPlayer
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
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.MainScope
|
||||
import kotlinx.coroutines.cancel
|
||||
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.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
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
|
||||
* to manage the MediaPlayer instance.
|
||||
*/
|
||||
@Suppress("TooManyFunctions", "TooGenericExceptionCaught")
|
||||
class MediaPlayerManager : LifecycleAwareManager {
|
||||
companion object {
|
||||
val TAG: String = MediaPlayerManager::class.java.simpleName
|
||||
private const val SEEKBAR_UPDATE_DELAY = 15L
|
||||
const val DIVIDER = 100f
|
||||
private const val SEEKBAR_UPDATE_DELAY = 150L
|
||||
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 mediaPlayerPosition: Int = 0
|
||||
private var loop = false
|
||||
private var scope = MainScope()
|
||||
private var currentCycledMessage: ChatMessage? = null
|
||||
private var currentDataSource: String = ""
|
||||
var mediaPlayerDuration: Int = 0
|
||||
private val _mediaPlayerSeekBarPosition: MutableLiveData<Int> = MutableLiveData()
|
||||
val mediaPlayerSeekBarPosition: LiveData<Int>
|
||||
get() = _mediaPlayerSeekBarPosition
|
||||
var mediaPlayerPosition: Int = 0
|
||||
|
||||
/**
|
||||
* Starts playing audio from the given path, initializes or resumes if the player is already created.
|
||||
*/
|
||||
fun start(path: String) {
|
||||
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
|
||||
stop()
|
||||
}
|
||||
|
||||
if (mediaPlayer == null || !scope.isActive) {
|
||||
init(path)
|
||||
} 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()
|
||||
loop = true
|
||||
scope.launch { seekbarUpdateObserver() }
|
||||
@ -60,19 +136,28 @@ class MediaPlayerManager : LifecycleAwareManager {
|
||||
if (mediaPlayer != null) {
|
||||
Log.d(TAG, "media player destroyed")
|
||||
loop = false
|
||||
scope.cancel()
|
||||
mediaPlayer!!.stop()
|
||||
mediaPlayer!!.release()
|
||||
mediaPlayer = null
|
||||
currentCycledMessage = null
|
||||
_backgroundPlayUIFlow.tryEmit(null)
|
||||
_managerState.value = MediaPlayerManagerState.STOPPED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the player.
|
||||
*/
|
||||
fun pause() {
|
||||
fun pause(notifyUI: Boolean) {
|
||||
if (mediaPlayer != null) {
|
||||
Log.d(TAG, "media player paused")
|
||||
_managerState.value = MediaPlayerManagerState.PAUSED
|
||||
mediaPlayer!!.pause()
|
||||
loop = false
|
||||
if (notifyUI) {
|
||||
_backgroundPlayUIFlow.tryEmit(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,14 +174,29 @@ class MediaPlayerManager : LifecycleAwareManager {
|
||||
|
||||
private suspend fun seekbarUpdateObserver() {
|
||||
withContext(Dispatchers.IO) {
|
||||
currentCycledMessage?.voiceMessageDuration = mediaPlayerDuration / ONE_SEC
|
||||
currentCycledMessage?.resetVoiceMessage = false
|
||||
while (true) {
|
||||
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
|
||||
mediaPlayerPosition = pos
|
||||
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)
|
||||
@ -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) {
|
||||
try {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
_managerState.value = MediaPlayerManagerState.SETUP
|
||||
setDataSource(path)
|
||||
currentDataSource = path
|
||||
prepareAsync()
|
||||
setOnPreparedListener {
|
||||
mediaPlayerDuration = it.duration
|
||||
start()
|
||||
loop = true
|
||||
scope = MainScope()
|
||||
scope.launch { seekbarUpdateObserver() }
|
||||
onPrepare()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
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() {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun handleOnResume() {
|
||||
// unused atm
|
||||
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
|
||||
loop = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleOnStop() {
|
||||
stop()
|
||||
scope.cancel()
|
||||
loop = false
|
||||
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 com.nextcloud.talk.chat.data.ChatMessageRepository
|
||||
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.model.ChatMessage
|
||||
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.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
@ -57,6 +62,7 @@ import javax.inject.Inject
|
||||
@Suppress("TooManyFunctions", "LongParameterList")
|
||||
class ChatViewModel @Inject constructor(
|
||||
// should be removed here. Use it via RetrofitChatNetwork
|
||||
private val appPreferences: AppPreferences,
|
||||
private val chatNetworkDataSource: ChatNetworkDataSource,
|
||||
private val chatRepository: ChatMessageRepository,
|
||||
private val conversationRepository: OfflineConversationsRepository,
|
||||
@ -73,8 +79,11 @@ class ChatViewModel @Inject constructor(
|
||||
STOPPED
|
||||
}
|
||||
|
||||
private val mediaPlayerManager: MediaPlayerManager = MediaPlayerManager.sharedInstance(appPreferences)
|
||||
lateinit var currentLifeCycleFlag: LifeCycleFlag
|
||||
val disposableSet = mutableSetOf<Disposable>()
|
||||
var mediaPlayerDuration = mediaPlayerManager.mediaPlayerDuration
|
||||
val mediaPlayerPosition = mediaPlayerManager.mediaPlayerPosition
|
||||
|
||||
fun getChatRepository(): ChatMessageRepository {
|
||||
return chatRepository
|
||||
@ -85,6 +94,7 @@ class ChatViewModel @Inject constructor(
|
||||
currentLifeCycleFlag = LifeCycleFlag.RESUMED
|
||||
mediaRecorderManager.handleOnResume()
|
||||
chatRepository.handleOnResume()
|
||||
mediaPlayerManager.handleOnResume()
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
@ -94,6 +104,7 @@ class ChatViewModel @Inject constructor(
|
||||
disposableSet.clear()
|
||||
mediaRecorderManager.handleOnPause()
|
||||
chatRepository.handleOnPause()
|
||||
mediaPlayerManager.handleOnPause()
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
@ -101,8 +112,21 @@ class ChatViewModel @Inject constructor(
|
||||
currentLifeCycleFlag = LifeCycleFlag.STOPPED
|
||||
mediaRecorderManager.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>
|
||||
get() = audioFocusRequestManager.getManagerState
|
||||
|
||||
@ -122,10 +146,6 @@ class ChatViewModel @Inject constructor(
|
||||
val outOfOfficeViewState: LiveData<OutOfOfficeUIState>
|
||||
get() = _outOfOfficeViewState
|
||||
|
||||
private val _voiceMessagePlaybackSpeedPreferences: MutableLiveData<Map<String, PlaybackSpeed>> = MutableLiveData()
|
||||
val voiceMessagePlaybackSpeedPreferences: LiveData<Map<String, PlaybackSpeed>>
|
||||
get() = _voiceMessagePlaybackSpeedPreferences
|
||||
|
||||
val getMessageFlow = chatRepository.messageFlow
|
||||
.onEach {
|
||||
_chatMessageViewState.value = if (_chatMessageViewState.value == ChatMessageInitialState) {
|
||||
@ -665,12 +685,34 @@ class ChatViewModel @Inject constructor(
|
||||
emit(message.first())
|
||||
}
|
||||
|
||||
fun applyPlaybackSpeedPreferences(speeds: Map<String, PlaybackSpeed>) {
|
||||
_voiceMessagePlaybackSpeedPreferences.postValue(speeds)
|
||||
fun setPlayBack(speed: PlaybackSpeed) {
|
||||
mediaPlayerManager.setPlayBackSpeed(speed)
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
_voiceMessagePlayBackUIFlow.emit(speed)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlaybackSpeedPreference(message: ChatMessage) =
|
||||
_voiceMessagePlaybackSpeedPreferences.value?.get(message.user.id) ?: PlaybackSpeed.NORMAL
|
||||
fun startMediaPlayer(path: String) {
|
||||
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> {
|
||||
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.stfalcon.chatkit.commons.models.IMessage
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -82,7 +83,7 @@ class MessageInputViewModel @Inject constructor(
|
||||
val micInputAudioObserver: LiveData<Pair<Float, Float>>
|
||||
get() = audioRecorderManager.getAudioValues
|
||||
|
||||
val mediaPlayerSeekbarObserver: LiveData<Int>
|
||||
val mediaPlayerSeekbarObserver: Flow<Int>
|
||||
get() = mediaPlayerManager.mediaPlayerSeekBarPosition
|
||||
|
||||
private val _getEditChatMessage: MutableLiveData<IMessage?> = MutableLiveData()
|
||||
@ -231,7 +232,7 @@ class MessageInputViewModel @Inject constructor(
|
||||
|
||||
fun pauseMediaPlayer() {
|
||||
audioFocusRequestManager.audioFocusRequest(false) {
|
||||
mediaPlayerManager.pause()
|
||||
mediaPlayerManager.pause(false)
|
||||
_isVoicePreviewPlaying.postValue(false)
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.core.view.MenuItemCompat
|
||||
import androidx.core.view.isVisible
|
||||
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.arbitrarystorage.ArbitraryStorageManager
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
|
||||
import com.nextcloud.talk.contacts.ContactsActivityCompose
|
||||
import com.nextcloud.talk.contacts.ContactsUiState
|
||||
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.repositories.unifiedsearch.UnifiedSearchRepository
|
||||
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.ChooseAccountShareToDialogFragment
|
||||
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.ThreadMode
|
||||
import retrofit2.HttpException
|
||||
import java.io.File
|
||||
import java.util.Objects
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
@ -185,6 +189,9 @@ class ConversationsListActivity :
|
||||
@Inject
|
||||
lateinit var networkMonitor: NetworkMonitor
|
||||
|
||||
@Inject
|
||||
lateinit var chatViewModel: ChatViewModel
|
||||
|
||||
@Inject
|
||||
lateinit var contactsViewModel: ContactsViewModel
|
||||
|
||||
@ -283,7 +290,7 @@ class ConversationsListActivity :
|
||||
if (adapter == null) {
|
||||
adapter = FlexibleAdapter(conversationItems, this, true)
|
||||
} else {
|
||||
binding.loadingContent?.visibility = View.GONE
|
||||
binding.loadingContent.visibility = View.GONE
|
||||
}
|
||||
adapter!!.addListener(this)
|
||||
prepareViews()
|
||||
@ -455,6 +462,55 @@ class ConversationsListActivity :
|
||||
}
|
||||
}.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>) {
|
||||
@ -770,8 +826,8 @@ class ConversationsListActivity :
|
||||
initSearchDisposable()
|
||||
adapter?.setHeadersShown(true)
|
||||
if (!hasFilterEnabled()) filterableConversationItems = searchableConversationItems
|
||||
adapter?.updateDataSet(filterableConversationItems, false)
|
||||
adapter?.showAllHeaders()
|
||||
adapter!!.updateDataSet(filterableConversationItems, false)
|
||||
adapter!!.showAllHeaders()
|
||||
binding.swipeRefreshLayoutView?.isEnabled = false
|
||||
searchBehaviorSubject.onNext(true)
|
||||
return true
|
||||
@ -786,9 +842,9 @@ class ConversationsListActivity :
|
||||
// cancel any pending searches
|
||||
searchHelper!!.cancelSearch()
|
||||
}
|
||||
binding.swipeRefreshLayoutView?.isRefreshing = false
|
||||
binding.swipeRefreshLayoutView.isRefreshing = false
|
||||
searchBehaviorSubject.onNext(false)
|
||||
binding.swipeRefreshLayoutView?.isEnabled = true
|
||||
binding.swipeRefreshLayoutView.isEnabled = true
|
||||
searchView!!.onActionViewCollapsed()
|
||||
|
||||
binding.conversationListAppbar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
|
||||
@ -801,7 +857,7 @@ class ConversationsListActivity :
|
||||
viewThemeUtils.platform.resetStatusBar(this@ConversationsListActivity)
|
||||
}
|
||||
|
||||
val layoutManager = binding.recyclerView?.layoutManager as SmoothScrollLinearLayoutManager?
|
||||
val layoutManager = binding.recyclerView.layoutManager as SmoothScrollLinearLayoutManager?
|
||||
layoutManager?.scrollToPositionWithOffset(0, 0)
|
||||
return true
|
||||
}
|
||||
@ -894,18 +950,18 @@ class ConversationsListActivity :
|
||||
|
||||
private fun initOverallLayout(isConversationListNotEmpty: Boolean) {
|
||||
if (isConversationListNotEmpty) {
|
||||
if (binding.emptyLayout?.visibility != View.GONE) {
|
||||
binding.emptyLayout?.visibility = View.GONE
|
||||
if (binding.emptyLayout.visibility != View.GONE) {
|
||||
binding.emptyLayout.visibility = View.GONE
|
||||
}
|
||||
if (binding.swipeRefreshLayoutView?.visibility != View.VISIBLE) {
|
||||
binding.swipeRefreshLayoutView?.visibility = View.VISIBLE
|
||||
if (binding.swipeRefreshLayoutView.visibility != View.VISIBLE) {
|
||||
binding.swipeRefreshLayoutView.visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
if (binding.emptyLayout?.visibility != View.VISIBLE) {
|
||||
binding.emptyLayout?.visibility = View.VISIBLE
|
||||
if (binding.emptyLayout.visibility != View.VISIBLE) {
|
||||
binding.emptyLayout.visibility = View.VISIBLE
|
||||
}
|
||||
if (binding.swipeRefreshLayoutView?.visibility != View.GONE) {
|
||||
binding.swipeRefreshLayoutView?.visibility = View.GONE
|
||||
if (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) {
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(v.windowToken, 0)
|
||||
}
|
||||
false
|
||||
}
|
||||
binding.swipeRefreshLayoutView?.setOnRefreshListener {
|
||||
binding.swipeRefreshLayoutView.setOnRefreshListener {
|
||||
fetchRooms()
|
||||
fetchPendingInvitations()
|
||||
}
|
||||
binding.swipeRefreshLayoutView?.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it) }
|
||||
binding.emptyLayout?.setOnClickListener { showNewConversationsScreen() }
|
||||
binding.floatingActionButton?.setOnClickListener {
|
||||
binding.swipeRefreshLayoutView.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it) }
|
||||
binding.emptyLayout.setOnClickListener { showNewConversationsScreen() }
|
||||
binding.floatingActionButton.setOnClickListener {
|
||||
run(context)
|
||||
showNewConversationsScreen()
|
||||
}
|
||||
binding.floatingActionButton?.let { viewThemeUtils.material.themeFAB(it) }
|
||||
binding.floatingActionButton.let { viewThemeUtils.material.themeFAB(it) }
|
||||
|
||||
binding.switchAccountButton.setOnClickListener {
|
||||
if (resources != null && resources!!.getBoolean(R.bool.multiaccount_support)) {
|
||||
@ -1284,7 +1340,7 @@ class ConversationsListActivity :
|
||||
|
||||
@SuppressLint("CheckResult") // handled by helper
|
||||
private fun startMessageSearch(search: String?) {
|
||||
binding.swipeRefreshLayoutView?.isRefreshing = true
|
||||
binding.swipeRefreshLayoutView.isRefreshing = true
|
||||
searchHelper?.startMessageSearch(search!!)
|
||||
?.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
@ -1539,8 +1595,8 @@ class ConversationsListActivity :
|
||||
filesToShare?.forEach {
|
||||
UploadAndShareFilesWorker.upload(
|
||||
it,
|
||||
selectedConversation!!.token!!,
|
||||
selectedConversation!!.displayName!!,
|
||||
selectedConversation!!.token,
|
||||
selectedConversation!!.displayName,
|
||||
null
|
||||
)
|
||||
}
|
||||
@ -2016,7 +2072,7 @@ class ConversationsListActivity :
|
||||
binding.recyclerView?.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
binding.swipeRefreshLayoutView?.isRefreshing = false
|
||||
binding.swipeRefreshLayoutView.isRefreshing = false
|
||||
}
|
||||
|
||||
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.MediaPlayerManager
|
||||
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@ -29,8 +30,10 @@ class ManagerModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideMediaPlayerManager(): MediaPlayerManager {
|
||||
return MediaPlayerManager()
|
||||
fun provideMediaPlayerManager(preferences: AppPreferences): MediaPlayerManager {
|
||||
return MediaPlayerManager().apply {
|
||||
appPreferences = preferences
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -10,8 +10,8 @@ package com.nextcloud.talk.dagger.modules
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
|
||||
import com.nextcloud.talk.contacts.ContactsViewModel
|
||||
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
|
||||
import com.nextcloud.talk.contacts.ContactsViewModel
|
||||
import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
|
||||
import com.nextcloud.talk.conversationcreation.ConversationCreationViewModel
|
||||
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
|
||||
@ -125,8 +125,6 @@ abstract class ViewModelModule {
|
||||
@ViewModelKey(MessageInputViewModel::class)
|
||||
abstract fun messageInputViewModel(viewModel: MessageInputViewModel): ViewModel
|
||||
|
||||
// TODO I had a merge conflict here that went weird. choose their version
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@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 com.nextcloud.talk.chat.viewmodels.MessageInputViewModel;
|
||||
import com.nextcloud.talk.ui.PlaybackSpeed;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressLint("NonConstantResourceId")
|
||||
public interface AppPreferences {
|
||||
|
||||
@ -175,9 +171,11 @@ public interface AppPreferences {
|
||||
|
||||
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();
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
package com.nextcloud.talk.utils.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
@ -24,9 +23,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@Suppress("TooManyFunctions", "DeferredResultUnused", "EmptyFunctionBlock")
|
||||
@ -500,26 +496,42 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
|
||||
return if (lastReadId.isNotEmpty()) lastReadId.toInt() else defaultValue
|
||||
}
|
||||
|
||||
override fun saveVoiceMessagePlaybackSpeedPreferences(speeds: Map<String, PlaybackSpeed>) {
|
||||
Json.encodeToString(speeds).let {
|
||||
runBlocking<Unit> { async { writeString(VOICE_MESSAGE_PLAYBACK_SPEEDS, it) } }
|
||||
}
|
||||
}
|
||||
override fun deleteAllMessageQueuesFor(userId: String) {
|
||||
runBlocking {
|
||||
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> {
|
||||
return runBlocking {
|
||||
async { readString(VOICE_MESSAGE_PLAYBACK_SPEEDS, "{}").first() }
|
||||
}.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()
|
||||
for (key in keyList) {
|
||||
context.dataStore.edit {
|
||||
it.remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 =
|
||||
runBlocking {
|
||||
async { readLong(LAST_NOTIFICATION_WARNING).first() }
|
||||
@ -609,6 +621,8 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
|
||||
const val DB_ROOM_MIGRATED = "db_room_migrated"
|
||||
const val PHONE_BOOK_INTEGRATION_LAST_RUN = "phone_book_integration_last_run"
|
||||
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 SHOW_REGULAR_NOTIFICATION_WARNING = "show_regular_notification_warning"
|
||||
const val LAST_NOTIFICATION_WARNING = "last_notification_warning"
|
||||
|
@ -143,6 +143,11 @@
|
||||
app:popupTheme="@style/appActionBarPopupMenu"
|
||||
app:titleTextColor="@color/fontAppbar"
|
||||
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>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -272,8 +272,6 @@ How to translate with transifex:
|
||||
<string name="nc_contacts_done">Done</string>
|
||||
<string name="user_avatar">User avatar</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 -->
|
||||
<string name="nc_permissions_rationale_dialog_title">Please allow permissions</string>
|
||||
<string name="nc_permissions_denied">Some permissions were denied.</string>
|
||||
|
@ -1339,6 +1339,8 @@ vCeonVI7Q1CkIHt8u7eMgzfEkaiPLZlI0l0RpfT4pnNieqg=
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
pub BAC30622339994C4
|
||||
uid Chris Povirk <cpovirk@google.com>
|
||||
|
||||
sub FC9BDC25FB378008
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
@ -1347,20 +1349,21 @@ Gyoc9ZmChrhLoim7z4ILqmNo8eegknepQ3dGdUij4NVIhR+m+8irayTbsNHvo3UG
|
||||
9y7eM5tTSjyNYkyk5fAVuT7OhzIzMA+qtc3GRVxNYRKnaHajt+pOSqr+uoDtMG3n
|
||||
6eAMHCAnhgh5Nd+dCFcNT+syl3zCwolA1wrzGxxOaif+xi5wwXjmF/lAt4PDIuDT
|
||||
etA2/AqPM4zAC0BtC0iqVgVypjFV3EAexm/g0LNMiG/M/krzwjPq5gf1DY/57jU0
|
||||
02FpKd79HmR7bHdc4e2olEf9NlHxfbPXDDsHABEBAAG5AQ0EWUwTFgEIANmMpV3N
|
||||
K8aLrLgQTyh5++det8C3D3T5tkEdljHOuN31/qdKNge8H6uKH8zXRZsj5pd8adpW
|
||||
kD4TzIMvzIwzizsGw34O9hf1E2XPoDqvQr39p1sovX3PeDvRJY/7JFNt9DsphVc3
|
||||
xWQfNkC7JdMPa6JRiFHd3ynfbQ+wplf4tfaDVn1JXAWp0NSGgMtXfn5i19hHQWjm
|
||||
RNAKNQLdVn8UczI8XdVM7bS4giDpQMukSyjsjgAo466iRK2+8f8BwIRe1JRvF37B
|
||||
dnbvTg/dzoi1/E4ukwVJD6YE2LlDwzdGno9KxPlRsuY3nnheVgjbrGJ2XKRJkIk8
|
||||
7cMGh41VKw6L4usAEQEAAYkBHwQYAQIACQUCWUwTFgIbDAAKCRC6wwYiM5mUxEiH
|
||||
CACQViGOHi0BoZ78ZJz6L48YNMx8fSdSv3YJ83Ih1n5DWCJgrDV5S3/edYinkoVI
|
||||
0Lusy3MdftRg6OWaYOuOTf6MYcddO/mY363jiMByf9Uh3Dqq4sKqVLRnZbAqgD1o
|
||||
dRoj2NkEQfgEH/H4JRVrxquzAKoWwJh3MhY+kajYJRJyWfc1/Bm3Bj1tcMGlGeIQ
|
||||
fgWheeMg3kxrxJ9TXPqVi6VVPaPKIU5i8l46S+Wg3uvMs8vC3XzOIvhY6cwguJv9
|
||||
UkjZwGDSI952wLqnREMy0gFZ+OAB0qJpYM3nDEekWZP38G80kojnN61tZjRThu9I
|
||||
i8/b+PwSW+nW3EpQZdLqZtOU
|
||||
=2H2i
|
||||
02FpKd79HmR7bHdc4e2olEf9NlHxfbPXDDsHABEBAAG0IUNocmlzIFBvdmlyayA8
|
||||
Y3Bvdmlya0Bnb29nbGUuY29tPrkBDQRZTBMWAQgA2YylXc0rxousuBBPKHn75163
|
||||
wLcPdPm2QR2WMc643fX+p0o2B7wfq4ofzNdFmyPml3xp2laQPhPMgy/MjDOLOwbD
|
||||
fg72F/UTZc+gOq9Cvf2nWyi9fc94O9Elj/skU230OymFVzfFZB82QLsl0w9rolGI
|
||||
Ud3fKd9tD7CmV/i19oNWfUlcBanQ1IaAy1d+fmLX2EdBaOZE0Ao1At1WfxRzMjxd
|
||||
1UzttLiCIOlAy6RLKOyOACjjrqJErb7x/wHAhF7UlG8XfsF2du9OD93OiLX8Ti6T
|
||||
BUkPpgTYuUPDN0aej0rE+VGy5jeeeF5WCNusYnZcpEmQiTztwwaHjVUrDovi6wAR
|
||||
AQABiQEfBBgBAgAJBQJZTBMWAhsMAAoJELrDBiIzmZTESIcIAJBWIY4eLQGhnvxk
|
||||
nPovjxg0zHx9J1K/dgnzciHWfkNYImCsNXlLf951iKeShUjQu6zLcx1+1GDo5Zpg
|
||||
645N/oxhx107+ZjfreOIwHJ/1SHcOqriwqpUtGdlsCqAPWh1GiPY2QRB+AQf8fgl
|
||||
FWvGq7MAqhbAmHcyFj6RqNglEnJZ9zX8GbcGPW1wwaUZ4hB+BaF54yDeTGvEn1Nc
|
||||
+pWLpVU9o8ohTmLyXjpL5aDe68yzy8LdfM4i+FjpzCC4m/1SSNnAYNIj3nbAuqdE
|
||||
QzLSAVn44AHSomlgzecMR6RZk/fwbzSSiOc3rW1mNFOG70iLz9v4/BJb6dbcSlBl
|
||||
0upm05Q=
|
||||
=Gf3Y
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
pub BCF4173966770193
|
||||
@ -1449,42 +1452,6 @@ lQyC8nl8P5PgkEZ5CHcGymZlpzihR3ECrPJTk39Sb7D3SxCW4WrChV3kVfmLgvc=
|
||||
=WqT9
|
||||
-----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
|
||||
sub 4F80368F9034B8D0
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
@ -5670,13 +5637,15 @@ xOcUt3JhIGtKwRMO4mte4wmT6Ko+Nj4uy6tFjbTfN2eBins/1F9qLU4YJUqC4QD4
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
pub 7721F63BD38B4796
|
||||
sub 4EB27DB2A3B88B8B
|
||||
sub 1397BC53640DB551
|
||||
sub 78BD65473CB3BD13
|
||||
sub 6494C6D6997C215E
|
||||
uid Google Inc. (Linux Packages Signing Authority) <linux-packages-keymaster@google.com>
|
||||
|
||||
sub FD533C07C264648F
|
||||
sub 32EE5355A6BC6E42
|
||||
sub E88979FB9B30ACF2
|
||||
sub 1397BC53640DB551
|
||||
sub 6494C6D6997C215E
|
||||
sub 78BD65473CB3BD13
|
||||
sub 4EB27DB2A3B88B8B
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBFcMjNMBEAC6Wr5QuLIFgz1V1EFPlg8ty2TsjQEl4VWftUAqWlMevJFWvYEx
|
||||
@ -5690,295 +5659,411 @@ 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
|
||||
c8LNGrDaCFdXnOdlNV/zT9VvBk/RkV+Tl/Lk4g==
|
||||
=AP/d
|
||||
tFRHb29nbGUgSW5jLiAoTGludXggUGFja2FnZXMgU2lnbmluZyBBdXRob3JpdHkp
|
||||
IDxsaW51eC1wYWNrYWdlcy1rZXltYXN0ZXJAZ29vZ2xlLmNvbT65Ag0EZ31b7QEQ
|
||||
AMEWM0g1KKIxE7q8JK7QblKom7++NYn92E3suHv3WxqzrhRT9tYNDSaoOazQP+ha
|
||||
NA2BqkdFcB7G4jKdtK2VLFc7RBpcR2rnQEJWgpeP03DHrdZYFdpH9zABoFsotgZR
|
||||
KwwTOoxdm6XtV+47LEY9yAefPrt1gPJQ4h1SKwWIFSRPChQ1cThBz2QD2LaPAGtj
|
||||
Wzr6+0xf0nm4xTDvya0EbdTpMOvtyDCUp7qe41u34RelGxoo0+rmoL/0cTJGCr2L
|
||||
3xWijlvWCMLhp2dgVnvRIpvo+tOSSl/pvTCLgE0nFjNQFbNh3D1Qo/AHhEz3MzbQ
|
||||
9JbEy7MF+fiw83YULRKN1kZ673Z1ng1wLA0m4+EWrh/PpMPp1raYQT0fSqUkiGBo
|
||||
25MfIjdheAuTgUf/4aHKU7vi4yFwxr8DWcKrxiv7g6xxbFvI3p+/wmyDbLXBBh2c
|
||||
iqwQfW/H32TlfL+cXqtapB93L1xR4IPTRvMnIVJBA+J6I/jlqx6RqPABemHudFvL
|
||||
2sAJu91lQjL5GxEmgVNL7l+UKGsy+h8mg4Sonnw3MI1c1KrvvIhJlkTpqiCqCnaG
|
||||
BBjZQWkuiyVJSq8VLHq2LxlWJd9nt73MpOLgj79ylD5G7OQEVgGBbvKwqdRYvKik
|
||||
8UTYYu7sTolNFVNMvQoCIJxropO/xk17qK0a4LtaZ8oVABEBAAGJBHIEGAEKACYW
|
||||
IQTrTBv9TwQvbd3M7JF3IfY704tHlgUCZ31b7QIbAgUJBaOagAJACRB3IfY704tH
|
||||
lsF0IAQZAQoAHRYhBA4iWRdBRnD0RCwlDf1TPAfCZGSPBQJnfVvtAAoJEP1TPAfC
|
||||
ZGSPphkP/iHWjnYuEXC7uKzt+zvsqjkGkGkXVApXgZGm9h1/ujlab0FK+9VA1JlV
|
||||
uzqw9SBYuwUwkGX0TVVCCc5KAxDa6sYH2IggcC+dN4ZjCMiUrJlEHNVE6f7Fjg59
|
||||
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
|
||||
Y7Iyz4ygr8etweBPnEIvjwpAZxOu
|
||||
=wwl/
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
pub 79752DB6C966F0B8
|
||||
|
@ -12,6 +12,9 @@
|
||||
<trust group="org.javassist" name="javassist" version="3.26.0-GA" reason="java assist"/>
|
||||
<trust file=".*-sources[.]jar" regex="true"/>
|
||||
</trusted-artifacts>
|
||||
<ignored-keys>
|
||||
<ignored-key id="C020E96222A31FB3" reason="Key couldn't be downloaded from any key server"/>
|
||||
</ignored-keys>
|
||||
<trusted-keys>
|
||||
<trusted-key id="02A36B6DB7056EB5E6FFEF893DA731F041734930" group="org.parceler"/>
|
||||
<trusted-key id="03D5EBC6C81161316CF21CEE1592D9DA6586CF26" group="^com[.]afollestad($|([.].*))" regex="true"/>
|
||||
@ -10244,10 +10247,10 @@
|
||||
</component>
|
||||
<component group="net.swiftzer.semver" name="semver" version="1.1.1">
|
||||
<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 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>
|
||||
</component>
|
||||
<component group="net.zetetic" name="android-database-sqlcipher" version="4.5.4">
|
||||
|
@ -1,2 +1,2 @@
|
||||
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