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:
Christian Reiner 2024-11-21 21:52:37 +01:00 committed by Andy Scherzinger
parent 187f98ad6b
commit 20d36c1eb9
No known key found for this signature in database
GPG key ID: 6CADC7E3523C308B
13 changed files with 260 additions and 34 deletions

View file

@ -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"]

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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
}
}
}

View file

@ -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);

View file

@ -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> {

View file

@ -50,6 +50,7 @@
tools:progress="50"
tools:progressTint="@color/hwSecurityRed"
tools:progressBackgroundTint="@color/blue"/>
</LinearLayout>
<Chronometer

View file

@ -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>

View file

@ -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

View file

@ -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>