mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-12-18 22:52:04 +03:00
playback speed control button for voice messages
Signed-off-by: Christian Reiner <foss@christian-reiner.info> Themed the PlaybackSpeedControl + Work around onBind bug Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
This commit is contained in:
parent
187f98ad6b
commit
20d36c1eb9
13 changed files with 260 additions and 34 deletions
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
* SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2021 Tim Krüger <t@timkrueger.me>
|
||||
* SPDX-FileCopyrightText: 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
|
@ -68,10 +69,16 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||
|
||||
lateinit var voiceMessageInterface: VoiceMessageInterface
|
||||
lateinit var commonMessageInterface: CommonMessageInterface
|
||||
private var isBound = false
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
if (isBound) {
|
||||
handleIsPlayingVoiceMessageState(message)
|
||||
return
|
||||
}
|
||||
|
||||
this.message = message
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
|
@ -100,25 +107,6 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeWaveFormSeekBar(binding.seekbar)
|
||||
viewThemeUtils.platform.colorCircularProgressBar(binding.progressBar, ColorRole.ON_SURFACE_VARIANT)
|
||||
|
||||
if (message.isPlayingVoiceMessage) {
|
||||
showPlayButton()
|
||||
binding.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context!!,
|
||||
R.drawable.ic_baseline_pause_voice_message_24
|
||||
)
|
||||
val d = message.voiceMessageDuration.toLong()
|
||||
val t = message.voiceMessagePlayedSeconds.toLong()
|
||||
binding.voiceMessageDuration.text = android.text.format.DateUtils.formatElapsedTime(d - t)
|
||||
binding.voiceMessageDuration.visibility = View.VISIBLE
|
||||
binding.seekbar.progress = message.voiceMessageSeekbarProgress
|
||||
} else {
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context!!,
|
||||
R.drawable.ic_baseline_play_arrow_voice_message_24
|
||||
)
|
||||
}
|
||||
|
||||
if (message.isDownloadingVoiceMessage) {
|
||||
showVoiceMessageLoading()
|
||||
} else {
|
||||
|
@ -158,6 +146,10 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||
}
|
||||
})
|
||||
|
||||
voiceMessageInterface.registerMessageToObservePlaybackSpeedPreferences(message.user.id) { speed ->
|
||||
binding.playbackSpeedControlBtn.setSpeed(speed)
|
||||
}
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
|
@ -167,6 +159,8 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||
false,
|
||||
viewThemeUtils
|
||||
)
|
||||
|
||||
isBound = true
|
||||
}
|
||||
|
||||
private fun longClickOnReaction(chatMessage: ChatMessage) {
|
||||
|
@ -177,6 +171,29 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
private fun handleIsPlayingVoiceMessageState(message: ChatMessage) {
|
||||
if (message.isPlayingVoiceMessage) {
|
||||
showPlayButton()
|
||||
binding.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context!!,
|
||||
R.drawable.ic_baseline_pause_voice_message_24
|
||||
)
|
||||
|
||||
val d = message.voiceMessageDuration.toLong()
|
||||
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 {
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context!!,
|
||||
R.drawable.ic_baseline_play_arrow_voice_message_24
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDownloadState(message: ChatMessage) {
|
||||
// check if download worker is already running
|
||||
val fileId = message.selectedIndividualHashMap!!["id"]
|
||||
|
|
|
@ -74,10 +74,16 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||
|
||||
lateinit var voiceMessageInterface: VoiceMessageInterface
|
||||
lateinit var commonMessageInterface: CommonMessageInterface
|
||||
private var isBound = false
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
if (isBound) {
|
||||
handleIsPlayingVoiceMessageState(message)
|
||||
return
|
||||
}
|
||||
|
||||
this.message = message
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
|
||||
|
@ -102,12 +108,9 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||
setParentMessageDataOnMessageItem(message)
|
||||
|
||||
updateDownloadState(message)
|
||||
binding.seekbar.max = message.voiceMessageDuration * ONE_SEC
|
||||
viewThemeUtils.talk.themeWaveFormSeekBar(binding.seekbar)
|
||||
viewThemeUtils.platform.colorCircularProgressBar(binding.progressBar, ColorRole.ON_SURFACE_VARIANT)
|
||||
|
||||
handleIsPlayingVoiceMessageState(message)
|
||||
|
||||
handleIsDownloadingVoiceMessageState(message)
|
||||
|
||||
handleResetVoiceMessageState(message)
|
||||
|
@ -149,6 +152,10 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||
|
||||
binding.checkMark.contentDescription = readStatusContentDescriptionString
|
||||
|
||||
voiceMessageInterface.registerMessageToObservePlaybackSpeedPreferences(message.user.id) { speed ->
|
||||
binding.playbackSpeedControlBtn.setSpeed(speed)
|
||||
}
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
|
@ -158,6 +165,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||
true,
|
||||
viewThemeUtils
|
||||
)
|
||||
isBound = true
|
||||
}
|
||||
|
||||
private fun longClickOnReaction(chatMessage: ChatMessage) {
|
||||
|
@ -207,6 +215,7 @@ 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 {
|
||||
binding.playPauseBtn.visibility = View.VISIBLE
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
* SPDX-FileCopyrightText: 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.adapters.messages
|
||||
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage
|
||||
import com.nextcloud.talk.ui.PlaybackSpeed
|
||||
|
||||
interface VoiceMessageInterface {
|
||||
fun updateMediaPlayerProgressBySlider(message: ChatMessage, progress: Int)
|
||||
fun registerMessageToObservePlaybackSpeedPreferences(userId: String, listener: (speed: PlaybackSpeed) -> Unit)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
* SPDX-FileCopyrightText: 2024 Parneet Singh <gurayaparneet@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2024 Giacomo Pacini <giacomo@paciosoft.com>
|
||||
* SPDX-FileCopyrightText: 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com>
|
||||
|
@ -136,6 +137,8 @@ import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
|
|||
import com.nextcloud.talk.signaling.SignalingMessageReceiver
|
||||
import com.nextcloud.talk.signaling.SignalingMessageSender
|
||||
import com.nextcloud.talk.translate.ui.TranslateActivity
|
||||
import com.nextcloud.talk.ui.PlaybackSpeed
|
||||
import com.nextcloud.talk.ui.PlaybackSpeedControl
|
||||
import com.nextcloud.talk.ui.StatusDrawable
|
||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
||||
import com.nextcloud.talk.ui.dialog.DateTimePickerFragment
|
||||
|
@ -205,6 +208,7 @@ import java.util.Date
|
|||
import java.util.Locale
|
||||
import java.util.concurrent.ExecutionException
|
||||
import javax.inject.Inject
|
||||
import kotlin.String
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
@ -357,6 +361,19 @@ class ChatActivity :
|
|||
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?) {
|
||||
if (token != null) {
|
||||
|
@ -434,6 +451,10 @@ class ChatActivity :
|
|||
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
|
||||
appPreferences.readVoiceMessagePlaybackSpeedPreferences().let { playbackSpeedPreferences ->
|
||||
chatViewModel.applyPlaybackSpeedPreferences(playbackSpeedPreferences)
|
||||
}
|
||||
|
||||
initObservers()
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
|
@ -1045,6 +1066,8 @@ class ChatActivity :
|
|||
|
||||
setupSwipeToReply()
|
||||
|
||||
chatViewModel.voiceMessagePlaybackSpeedPreferences.observe(this, playbackSpeedPreferencesObserver)
|
||||
|
||||
binding.unreadMessagesPopup.setOnClickListener {
|
||||
binding.messagesListView.smoothScrollToPosition(0)
|
||||
binding.unreadMessagesPopup.visibility = View.GONE
|
||||
|
@ -1131,6 +1154,7 @@ class ChatActivity :
|
|||
adapter?.setLoadMoreListener(this)
|
||||
adapter?.setDateHeadersFormatter { format(it) }
|
||||
adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) }
|
||||
|
||||
adapter?.registerViewClickListener(
|
||||
R.id.playPauseBtn
|
||||
) { _, message ->
|
||||
|
@ -1154,6 +1178,15 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpWaveform(message: ChatMessage, thenPlay: Boolean = true) {
|
||||
|
@ -1579,6 +1612,9 @@ class ChatActivity :
|
|||
mediaPlayer?.let {
|
||||
if (!it.isPlaying && doPlay) {
|
||||
chatViewModel.audioRequest(true) {
|
||||
it.playbackParams = it.playbackParams.apply {
|
||||
setSpeed(chatViewModel.getPlaybackSpeedPreference(message).value)
|
||||
}
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
|
@ -1703,6 +1739,20 @@ class ChatActivity :
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun collapseSystemMessages() {
|
||||
adapter?.items?.forEach {
|
||||
|
@ -2372,6 +2422,8 @@ class ChatActivity :
|
|||
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
||||
mentionAutocomplete?.dismissPopup()
|
||||
}
|
||||
|
||||
chatViewModel.voiceMessagePlaybackSpeedPreferences.removeObserver(playbackSpeedPreferencesObserver)
|
||||
}
|
||||
|
||||
private fun isActivityNotChangingConfigurations(): Boolean = !isChangingConfigurations
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
@ -33,6 +34,7 @@ import com.nextcloud.talk.models.json.conversations.RoomsOverall
|
|||
import com.nextcloud.talk.models.json.generic.GenericOverall
|
||||
import com.nextcloud.talk.models.json.reminder.Reminder
|
||||
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
|
||||
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
|
||||
|
@ -107,6 +109,10 @@ class ChatViewModel @Inject constructor(
|
|||
val getVoiceRecordingLocked: LiveData<Boolean>
|
||||
get() = _getVoiceRecordingLocked
|
||||
|
||||
private val _voiceMessagePlaybackSpeeds: MutableLiveData<Map<String, PlaybackSpeed>> = MutableLiveData()
|
||||
val voiceMessagePlaybackSpeedPreferences: LiveData<Map<String, PlaybackSpeed>>
|
||||
get() = _voiceMessagePlaybackSpeeds
|
||||
|
||||
val getMessageFlow = chatRepository.messageFlow
|
||||
.onEach {
|
||||
_chatMessageViewState.value = if (_chatMessageViewState.value == ChatMessageInitialState) {
|
||||
|
@ -644,6 +650,13 @@ class ChatViewModel @Inject constructor(
|
|||
emit(message.first())
|
||||
}
|
||||
|
||||
fun applyPlaybackSpeedPreferences(speeds: Map<String, PlaybackSpeed>) {
|
||||
_voiceMessagePlaybackSpeeds.postValue(speeds)
|
||||
}
|
||||
|
||||
fun getPlaybackSpeedPreference(message: ChatMessage) =
|
||||
_voiceMessagePlaybackSpeeds.value?.get(message.user.id) ?: PlaybackSpeed.NORMAL
|
||||
|
||||
// inner class GetRoomObserver : Observer<ConversationModel> {
|
||||
// override fun onSubscribe(d: Disposable) {
|
||||
// // unused atm
|
||||
|
|
|
@ -38,7 +38,7 @@ class MessageInputViewModel @Inject constructor(
|
|||
private val audioRecorderManager: AudioRecorderManager,
|
||||
private val mediaPlayerManager: MediaPlayerManager,
|
||||
private val audioFocusRequestManager: AudioFocusRequestManager,
|
||||
private val dataStore: AppPreferences
|
||||
private val appPreferences: AppPreferences
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
enum class LifeCycleFlag {
|
||||
PAUSED,
|
||||
|
@ -147,9 +147,9 @@ class MessageInputViewModel @Inject constructor(
|
|||
if (isQueueing) {
|
||||
val tempID = System.currentTimeMillis().toInt()
|
||||
val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification)
|
||||
messageQueue = dataStore.getMessageQueue(internalId)
|
||||
messageQueue = appPreferences.getMessageQueue(internalId)
|
||||
messageQueue.add(qMsg)
|
||||
dataStore.saveMessageQueue(internalId, messageQueue)
|
||||
appPreferences.saveMessageQueue(internalId, messageQueue)
|
||||
_messageQueueSizeFlow.update { messageQueue.size }
|
||||
_messageQueueFlow.postValue(listOf(qMsg))
|
||||
return
|
||||
|
@ -260,8 +260,8 @@ class MessageInputViewModel @Inject constructor(
|
|||
if (isQueueing) return
|
||||
messageQueue.clear()
|
||||
|
||||
val queue = dataStore.getMessageQueue(internalId)
|
||||
dataStore.saveMessageQueue(internalId, null) // empties the queue
|
||||
val queue = appPreferences.getMessageQueue(internalId)
|
||||
appPreferences.saveMessageQueue(internalId, null) // empties the queue
|
||||
while (queue.size > 0) {
|
||||
val msg = queue.removeAt(0)
|
||||
sendChatMessage(
|
||||
|
@ -279,7 +279,7 @@ class MessageInputViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun getTempMessagesFromMessageQueue(internalId: String) {
|
||||
val queue = dataStore.getMessageQueue(internalId)
|
||||
val queue = appPreferences.getMessageQueue(internalId)
|
||||
val list = mutableListOf<QueuedMessage>()
|
||||
for (msg in queue) {
|
||||
list.add(msg)
|
||||
|
@ -292,31 +292,31 @@ class MessageInputViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun restoreMessageQueue(internalId: String) {
|
||||
messageQueue = dataStore.getMessageQueue(internalId)
|
||||
messageQueue = appPreferences.getMessageQueue(internalId)
|
||||
_messageQueueSizeFlow.tryEmit(messageQueue.size)
|
||||
}
|
||||
|
||||
fun removeFromQueue(internalId: String, id: Int) {
|
||||
val queue = dataStore.getMessageQueue(internalId)
|
||||
val queue = appPreferences.getMessageQueue(internalId)
|
||||
for (qMsg in queue) {
|
||||
if (qMsg.id == id) {
|
||||
queue.remove(qMsg)
|
||||
break
|
||||
}
|
||||
}
|
||||
dataStore.saveMessageQueue(internalId, queue)
|
||||
appPreferences.saveMessageQueue(internalId, queue)
|
||||
_messageQueueSizeFlow.tryEmit(queue.size)
|
||||
}
|
||||
|
||||
fun editQueuedMessage(internalId: String, id: Int, newMessage: String) {
|
||||
val queue = dataStore.getMessageQueue(internalId)
|
||||
val queue = appPreferences.getMessageQueue(internalId)
|
||||
for (qMsg in queue) {
|
||||
if (qMsg.id == id) {
|
||||
qMsg.message = newMessage
|
||||
break
|
||||
}
|
||||
}
|
||||
dataStore.saveMessageQueue(internalId, queue)
|
||||
appPreferences.saveMessageQueue(internalId, queue)
|
||||
}
|
||||
|
||||
fun showCallStartedIndicator(recent: ChatMessage, show: Boolean) {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import java.util.Locale
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
internal const val SPEED_FACTOR_SLOW = 0.8f
|
||||
internal const val SPEED_FACTOR_NORMAL = 1.0f
|
||||
internal const val SPEED_FACTOR_FASTER = 1.5f
|
||||
internal const val SPEED_FACTOR_FASTEST = 2.0f
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class PlaybackSpeedControl @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : MaterialButton(context, attrs, defStyleAttr) {
|
||||
|
||||
@Inject
|
||||
lateinit var viewThemeUtils: ViewThemeUtils
|
||||
|
||||
private var currentSpeed = PlaybackSpeed.NORMAL
|
||||
|
||||
init {
|
||||
NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
|
||||
text = currentSpeed.label
|
||||
viewThemeUtils.material.colorMaterialButtonText(this)
|
||||
}
|
||||
|
||||
fun setSpeed(newSpeed: PlaybackSpeed) {
|
||||
currentSpeed = newSpeed
|
||||
text = currentSpeed.label
|
||||
}
|
||||
|
||||
fun getSpeed(): PlaybackSpeed {
|
||||
return currentSpeed
|
||||
}
|
||||
}
|
||||
|
||||
enum class PlaybackSpeed(val value: Float) {
|
||||
SLOW(SPEED_FACTOR_SLOW),
|
||||
NORMAL(SPEED_FACTOR_NORMAL),
|
||||
FASTER(SPEED_FACTOR_FASTER),
|
||||
FASTEST(SPEED_FACTOR_FASTEST);
|
||||
|
||||
// no fixed, literal labels, since we want to obey numeric interpunctuation for different locales
|
||||
val label: String = String.format(Locale.getDefault(), "%01.1fx", value)
|
||||
|
||||
fun next(): PlaybackSpeed {
|
||||
return entries[(ordinal + 1) % entries.size]
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun byName(name: String): PlaybackSpeed {
|
||||
for (speed in entries) {
|
||||
if (speed.name.equals(name, ignoreCase = true)) {
|
||||
return speed
|
||||
}
|
||||
}
|
||||
return NORMAL
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
* SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2021 Tim Krüger <t@timkrueger.me>
|
||||
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
||||
|
@ -11,8 +12,10 @@ 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 {
|
||||
|
@ -178,6 +181,10 @@ public interface AppPreferences {
|
|||
|
||||
void deleteAllMessageQueuesFor(String userId);
|
||||
|
||||
void saveVoiceMessagePlaybackSpeedPreferences(Map<String, PlaybackSpeed> speeds);
|
||||
|
||||
Map<String, PlaybackSpeed> readVoiceMessagePlaybackSpeedPreferences();
|
||||
|
||||
Long getNotificationWarningLastPostponedDate();
|
||||
|
||||
void setNotificationWarningLastPostponedDate(Long showNotificationWarning);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
* SPDX-FileCopyrightText: 2023 Julius Linus <julius.linus@nextcloud.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
@ -17,12 +18,16 @@ import androidx.datastore.preferences.core.stringPreferencesKey
|
|||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
|
||||
import com.nextcloud.talk.ui.PlaybackSpeed
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.async
|
||||
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")
|
||||
|
@ -565,6 +570,26 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
|
|||
}
|
||||
}
|
||||
|
||||
override fun saveVoiceMessagePlaybackSpeedPreferences(speeds: Map<String, PlaybackSpeed>) {
|
||||
Json.encodeToString(speeds).let {
|
||||
runBlocking<Unit> { async { writeString(VOICE_MESSAGE_PLAYBACK_SPEEDS, it) } }
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNotificationWarningLastPostponedDate(): Long =
|
||||
runBlocking {
|
||||
async { readLong(LAST_NOTIFICATION_WARNING).first() }
|
||||
|
@ -661,6 +686,7 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
|
|||
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 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"
|
||||
private fun String.convertStringToArray(): Array<Float> {
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
tools:progress="50"
|
||||
tools:progressTint="@color/hwSecurityRed"
|
||||
tools:progressBackgroundTint="@color/blue"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Chronometer
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<!--
|
||||
~ Nextcloud Talk - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
~ SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~ SPDX-FileCopyrightText: 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
~ SPDX-FileCopyrightText: 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
|
@ -76,7 +77,6 @@
|
|||
app:iconSize="40dp"
|
||||
app:iconTint="@color/nc_incoming_text_default" />
|
||||
|
||||
|
||||
<com.nextcloud.talk.ui.WaveformSeekBar
|
||||
android:id="@+id/seekbar"
|
||||
android:layout_width="0dp"
|
||||
|
@ -85,6 +85,16 @@
|
|||
tools:progress="50"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.nextcloud.talk.ui.PlaybackSpeedControl
|
||||
android:id="@+id/playbackSpeedControlBtn"
|
||||
style="@style/Widget.AppTheme.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="@dimen/standard_margin"
|
||||
android:contentDescription="@string/playback_speed_control"
|
||||
android:textColor="@color/black"
|
||||
app:cornerRadius="@dimen/button_corner_radius"
|
||||
app:rippleColor="#1FFFFFFF" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<!--
|
||||
~ Nextcloud Talk - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
|
||||
~ SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~ SPDX-FileCopyrightText: 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
~ SPDX-FileCopyrightText: 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
|
@ -70,6 +71,17 @@
|
|||
tools:progress="50"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.nextcloud.talk.ui.PlaybackSpeedControl
|
||||
android:id="@+id/playbackSpeedControlBtn"
|
||||
style="@style/Widget.AppTheme.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="@dimen/standard_margin"
|
||||
android:contentDescription="@string/playback_speed_control"
|
||||
android:textColor="@color/black"
|
||||
app:cornerRadius="@dimen/button_corner_radius"
|
||||
app:rippleColor="#1FFFFFFF" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -572,6 +572,7 @@ How to translate with transifex:
|
|||
<string name="nc_voice_message_slide_to_cancel">« Slide to cancel</string>
|
||||
<string name="play_pause_voice_message">Play/pause voice message</string>
|
||||
<string name="nc_voice_message_missing_audio_permission">Permission for audio recording is required</string>
|
||||
<string name="playback_speed_control">Playback speed control</string>
|
||||
|
||||
<!-- Phonebook Integration -->
|
||||
<string name="nc_settings_phone_book_integration_desc">Match contacts based on phone number to integrate Talk shortcut into system contacts app</string>
|
||||
|
|
Loading…
Reference in a new issue