Skip to content

Commit 9de8088

Browse files
committed
Optimising waveform messages
Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
1 parent 219d5a1 commit 9de8088

7 files changed

Lines changed: 61 additions & 35 deletions

File tree

app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import androidx.compose.runtime.LaunchedEffect
8181
import androidx.compose.runtime.MutableState
8282
import androidx.compose.runtime.SideEffect
8383
import androidx.compose.runtime.collectAsState
84+
import androidx.compose.runtime.collectAsState
8485
import androidx.compose.runtime.derivedStateOf
8586
import androidx.compose.runtime.getValue
8687
import androidx.compose.runtime.mutableStateOf
@@ -136,6 +137,7 @@ import com.nextcloud.talk.api.NcApi
136137
import com.nextcloud.talk.api.NcApiCoroutines
137138
import com.nextcloud.talk.application.NextcloudTalkApplication
138139
import com.nextcloud.talk.chat.data.model.ChatMessage
140+
import com.nextcloud.talk.chat.ui.ShowReactionsModalBottomSheet
139141
import com.nextcloud.talk.chat.data.model.FileParameters
140142
import com.nextcloud.talk.chat.ui.MessageActionsBottomSheet
141143
import com.nextcloud.talk.chat.ui.ProfileModalBottomSheet
@@ -188,6 +190,7 @@ import com.nextcloud.talk.ui.dialog.DateTimeCompose
188190
import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
189191
import com.nextcloud.talk.ui.dialog.GetPinnedOptionsDialog
190192
import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
193+
import com.nextcloud.talk.ui.dialog.TempMessageActionsDialog
191194
import com.nextcloud.talk.ui.theme.LocalMessageUtils
192195
import com.nextcloud.talk.ui.theme.LocalOpenGraphFetcher
193196
import com.nextcloud.talk.ui.theme.LocalViewThemeUtils
@@ -746,6 +749,8 @@ class ChatActivity :
746749
LocalMessageUtils provides messageUtils,
747750
LocalOpenGraphFetcher provides { url -> chatViewModel.fetchOpenGraph(url) }
748751
) {
752+
val currentlyPlayingId by chatViewModel.currentlyPlayedMessageId.collectAsState(null)
753+
749754
val isOneToOneConversation = uiState.isOneToOneConversation
750755
Log.d(TAG, "isOneToOneConversation=" + isOneToOneConversation)
751756

@@ -765,6 +770,7 @@ class ChatActivity :
765770
state = ChatViewState(
766771
chatItems = uiState.items,
767772
isOneToOneConversation = isOneToOneConversation,
773+
currentlyPlayingVoiceMessageId = currentlyPlayingId,
768774
conversationThreadId = conversationThreadId,
769775
chatMode = chatMode,
770776
highlightedMessageId = uiState.highlightedMessageId,

app/src/main/java/com/nextcloud/talk/chat/data/io/MediaPlayerManager.kt

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ class MediaPlayerManager : LifecycleAwareManager {
8484
private var mediaPlayer: MediaPlayer? = null
8585
private var loop = false
8686
private var scope = MainScope()
87-
private var currentCycledMessage: ChatMessage? = null
87+
88+
private val _currentCycledMessage = MutableStateFlow<ChatMessage?>(null)
89+
val currentCycledMessage: StateFlow<ChatMessage?>
90+
get() = _currentCycledMessage
91+
8892
private var currentDataSource: String = ""
8993
var mediaPlayerDuration: Int = 0
9094
var mediaPlayerPosition: Int = 0
@@ -140,7 +144,7 @@ class MediaPlayerManager : LifecycleAwareManager {
140144
mediaPlayer!!.stop()
141145
mediaPlayer!!.release()
142146
mediaPlayer = null
143-
currentCycledMessage = null
147+
_currentCycledMessage.value = null
144148
_backgroundPlayUIFlow.tryEmit(null)
145149
_managerState.value = MediaPlayerManagerState.STOPPED
146150
}
@@ -174,8 +178,8 @@ class MediaPlayerManager : LifecycleAwareManager {
174178

175179
private suspend fun seekbarUpdateObserver() {
176180
withContext(Dispatchers.IO) {
177-
currentCycledMessage?.voiceMessageDuration = mediaPlayerDuration / ONE_SEC
178-
currentCycledMessage?.resetVoiceMessage = false
181+
_currentCycledMessage.value?.voiceMessageDuration = mediaPlayerDuration / ONE_SEC
182+
_currentCycledMessage.value?.resetVoiceMessage = false
179183
while (true) {
180184
if (!loop) {
181185
// NOTE: ok so this doesn't stop the loop, but rather stop the update. Wasteful, but minimal
@@ -197,7 +201,7 @@ class MediaPlayerManager : LifecycleAwareManager {
197201
val progressI = ceil(progress).toInt()
198202
val seconds = (pos / ONE_SEC)
199203
_mediaPlayerSeekBarPosition.emit(progressI)
200-
currentCycledMessage?.let { msg ->
204+
_currentCycledMessage.value?.let { msg ->
201205
msg.isPlayingVoiceMessage = true
202206
msg.voiceMessageSeekbarProgress = progressI
203207
msg.voiceMessagePlayedSeconds = seconds
@@ -240,7 +244,7 @@ class MediaPlayerManager : LifecycleAwareManager {
240244
requestedPlaybackSpeed = speed
241245
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
242246
mediaPlayer!!.playbackParams.let { params ->
243-
params.setSpeed(speed.value)
247+
params.speed = speed.value
244248
mediaPlayer!!.playbackParams = params
245249
}
246250
}
@@ -270,7 +274,7 @@ class MediaPlayerManager : LifecycleAwareManager {
270274
val pair = playQueue.iterator().next()
271275
setDataSource(pair.first)
272276
currentDataSource = pair.first
273-
currentCycledMessage = pair.second
277+
_currentCycledMessage.value = pair.second
274278
playQueue.removeAt(0)
275279
prepareAsync()
276280
setOnPreparedListener {
@@ -284,20 +288,20 @@ class MediaPlayerManager : LifecycleAwareManager {
284288
playQueue.removeAt(0)
285289
mediaPlayer?.reset()
286290
mediaPlayer?.setDataSource(nextPair.first)
287-
currentCycledMessage = nextPair.second
291+
_currentCycledMessage.value = nextPair.second
288292
prepare()
289293
} else {
290294
mediaPlayer?.release()
291295
mediaPlayer = null
292296
_backgroundPlayUIFlow.tryEmit(null)
293-
currentCycledMessage?.let {
297+
_currentCycledMessage.value?.let {
294298
it.resetVoiceMessage = true
295299
it.isPlayingVoiceMessage = false
296300
it.voiceMessageSeekbarProgress = 0
297301
it.voiceMessagePlayedSeconds = 0
298302
}
299-
val completedMessage = currentCycledMessage
300-
currentCycledMessage = null
303+
val completedMessage = _currentCycledMessage.value
304+
_currentCycledMessage.value = null
301305
if (completedMessage != null) {
302306
scope.launch {
303307
_mediaPlayerSeekBarPositionMsg.emit(completedMessage)
@@ -318,16 +322,16 @@ class MediaPlayerManager : LifecycleAwareManager {
318322
mediaPlayerDuration = this.duration
319323

320324
val playBackSpeed = requestedPlaybackSpeed?.value
321-
?: if (currentCycledMessage?.actorId == null) {
325+
?: if (_currentCycledMessage.value?.actorId == null) {
322326
PlaybackSpeed.NORMAL.value
323327
} else {
324-
appPreferences.getPreferredPlayback(currentCycledMessage?.actorId).value
328+
appPreferences.getPreferredPlayback(_currentCycledMessage.value?.actorId).value
325329
}
326330
mediaPlayer!!.playbackParams = mediaPlayer!!.playbackParams.setSpeed(playBackSpeed)
327331

328332
start()
329333
_managerState.value = MediaPlayerManagerState.STARTED
330-
currentCycledMessage?.let {
334+
_currentCycledMessage.value?.let {
331335
it.isPlayingVoiceMessage = true
332336
_backgroundPlayUIFlow.tryEmit(it)
333337
}
@@ -348,9 +352,9 @@ class MediaPlayerManager : LifecycleAwareManager {
348352

349353
override fun handleOnStop() {
350354
loop = false
351-
if (mediaPlayer != null && currentCycledMessage != null && mediaPlayer!!.isPlaying) {
355+
if (mediaPlayer != null && _currentCycledMessage.value != null && mediaPlayer!!.isPlaying) {
352356
CoroutineScope(Dispatchers.Default).launch {
353-
_backgroundPlayUIFlow.tryEmit(currentCycledMessage!!)
357+
_backgroundPlayUIFlow.tryEmit(_currentCycledMessage.value)
354358
}
355359
}
356360
}

app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.lifecycle.MutableLiveData
1818
import androidx.lifecycle.ViewModel
1919
import androidx.lifecycle.viewModelScope
2020
import com.google.gson.Gson
21+
import com.nextcloud.talk.application.NextcloudTalkApplication
2122
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
2223
import com.nextcloud.talk.chat.data.ChatMessageRepository
2324
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
@@ -28,7 +29,6 @@ import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
2829
import com.nextcloud.talk.chat.ui.model.ChatMessageUi
2930
import com.nextcloud.talk.chat.ui.model.MessageTypeContent
3031
import com.nextcloud.talk.chat.ui.model.toUiModel
31-
import com.nextcloud.talk.application.NextcloudTalkApplication
3232
import com.nextcloud.talk.conversationlist.DirectShareHelper
3333
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
3434
import com.nextcloud.talk.conversationlist.data.network.OfflineFirstConversationsRepository
@@ -74,6 +74,8 @@ import io.reactivex.Observer
7474
import io.reactivex.android.schedulers.AndroidSchedulers
7575
import io.reactivex.disposables.Disposable
7676
import io.reactivex.schedulers.Schedulers
77+
import kotlinx.coroutines.Dispatchers
78+
import kotlinx.coroutines.delay
7779
import kotlinx.coroutines.flow.Flow
7880
import kotlinx.coroutines.flow.MutableSharedFlow
7981
import kotlinx.coroutines.flow.MutableStateFlow
@@ -88,12 +90,12 @@ import kotlinx.coroutines.flow.filter
8890
import kotlinx.coroutines.flow.filterNotNull
8991
import kotlinx.coroutines.flow.first
9092
import kotlinx.coroutines.flow.flatMapLatest
93+
import kotlinx.coroutines.flow.flowOf
94+
import kotlinx.coroutines.flow.flowOn
9195
import kotlinx.coroutines.flow.launchIn
9296
import kotlinx.coroutines.flow.map
9397
import kotlinx.coroutines.flow.mapNotNull
9498
import kotlinx.coroutines.flow.onEach
95-
import kotlinx.coroutines.flow.flowOf
96-
import kotlinx.coroutines.flow.flowOn
9799
import kotlinx.coroutines.flow.onStart
98100
import kotlinx.coroutines.flow.shareIn
99101
import kotlinx.coroutines.flow.stateIn
@@ -249,6 +251,10 @@ class ChatViewModel @AssistedInject constructor(
249251
val mediaPlayerSeekbarObserver: Flow<ChatMessage>
250252
get() = mediaPlayerManager.mediaPlayerSeekBarPositionMsg
251253

254+
// FIXME - map this to string id or some other kinda of id idk
255+
val currentlyPlayedMessageId: Flow<Int?>
256+
get() = mediaPlayerManager.currentCycledMessage.map { msg -> msg?.jsonMessageId }
257+
252258
val managerStateFlow: Flow<MediaPlayerManager.MediaPlayerManagerState>
253259
get() = mediaPlayerManager.managerState
254260

app/src/main/java/com/nextcloud/talk/ui/ComposeWaveformSeekbar.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fun ComposeWaveformSeekBar(value: Float, onValueChange: (Float) -> Unit, modifie
5454
for (i in waveData.indices) {
5555
val x: Float = i * (barWidth + barGap)
5656
val y: Float = waveData[i] * height
57-
val isXBeforeThumb = (x / this.size.width) <= value + OVERLAP
57+
val isXBeforeThumb = (x / this.size.width) <= value
5858

5959
drawLine(
6060
if (isXBeforeThumb) inversePrimary else onPrimaryContainer,
@@ -85,7 +85,7 @@ fun Preview() {
8585
val waveData = remember { FloatArray(WAVEFORM_SIZE) { (Math.random() % 1).toFloat() } }
8686

8787
ComposeWaveformSeekBar(
88-
0f,
88+
0.0f,
8989
{},
9090
modifier = Modifier
9191
.height(MAX_HEIGHT.dp)

app/src/main/java/com/nextcloud/talk/ui/chat/ChatMessageView.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ private const val QUOTE_HIGHLIGHT_HOLD_MILLIS = 700L
5252
private const val QUOTE_HIGHLIGHT_FADE_OUT_MILLIS = 1500
5353

5454
data class ChatMessageContext(
55+
val currentlyPlayingVoiceMessageId: Int? = null,
5556
val isOneToOneConversation: Boolean = false,
5657
val conversationThreadId: Long? = null,
5758
val hasChatPermission: Boolean = true,
@@ -175,17 +176,18 @@ fun ChatMessageView(
175176
)
176177
}
177178

178-
is MessageTypeContent.Voice -> {
179-
VoiceMessage(
180-
typeContent = content,
181-
message = message,
182-
isOneToOneConversation = context.isOneToOneConversation,
183-
conversationThreadId = context.conversationThreadId,
184-
onPlayPauseClick = callbacks.onVoicePlayPauseClick,
185-
onSeek = callbacks.onVoiceSeek,
186-
onSpeedClick = callbacks.onVoiceSpeedClick
187-
)
188-
}
179+
is MessageTypeContent.Voice -> {
180+
VoiceMessage(
181+
typeContent = content,
182+
message = message,
183+
isOneToOneConversation = context.isOneToOneConversation,
184+
conversationThreadId = context.conversationThreadId,
185+
currentlyPlayingVoiceMessageId = context.currentlyPlayingVoiceMessageId,
186+
onPlayPauseClick = callbacks.onVoicePlayPauseClick,
187+
onSeek = callbacks.onVoiceSeek,
188+
onSpeedClick = callbacks.onVoiceSpeedClick
189+
)
190+
}
189191

190192
is MessageTypeContent.Poll -> {
191193
PollMessage(

app/src/main/java/com/nextcloud/talk/ui/chat/ChatView.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ data class ChatViewState(
8888
val chatItems: List<ChatViewModel.ChatItem>,
8989
val isOneToOneConversation: Boolean,
9090
val conversationThreadId: Long? = null,
91+
val currentlyPlayingVoiceMessageId: Int? = null,
9192
val hasChatPermission: Boolean = true,
9293
val initialUnreadCount: Int = 0,
9394
val initialShowUnreadPopup: Boolean = false,
@@ -346,10 +347,10 @@ fun ChatView(
346347
isSelected = state.highlightedMessageId == chatItem.uiMessage.id,
347348
highlightSearchTerm = state.highlightedSearchTerm,
348349
context = ChatMessageContext(
350+
currentlyPlayingVoiceMessageId = state.currentlyPlayingVoiceMessageId,
349351
isOneToOneConversation = state.isOneToOneConversation,
350352
conversationThreadId = state.conversationThreadId,
351-
hasChatPermission = state.hasChatPermission,
352-
downloadingFileState = state.downloadingFileState
353+
hasChatPermission = state.hasChatPermission
353354
),
354355
callbacks = ChatMessageCallbacks(
355356
onLongClick = callbacks.messageCallbacks.onLongClick,

app/src/main/java/com/nextcloud/talk/ui/chat/VoiceMessage.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fun VoiceMessage(
5454
message: ChatMessageUi,
5555
isOneToOneConversation: Boolean = false,
5656
conversationThreadId: Long? = null,
57+
currentlyPlayingVoiceMessageId: Int? = null,
5758
onPlayPauseClick: (Int) -> Unit = {},
5859
onSeek: (messageId: Int, progress: Int) -> Unit = { _, _ -> },
5960
onSpeedClick: (messageId: Int) -> Unit = {}
@@ -83,6 +84,12 @@ fun VoiceMessage(
8384
label = "size"
8485
)
8586

87+
val icon = if (message.id != currentlyPlayingVoiceMessageId) {
88+
Icons.Filled.PlayArrow
89+
} else {
90+
if (typeContent.isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow
91+
}
92+
8693
Column {
8794
Row(
8895
verticalAlignment = Alignment.CenterVertically,
@@ -96,7 +103,7 @@ fun VoiceMessage(
96103
modifier = Modifier.size(48.dp)
97104
) {
98105
Icon(
99-
imageVector = if (typeContent.isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
106+
imageVector = icon,
100107
contentDescription = stringResource(R.string.play_pause_voice_message),
101108
modifier = Modifier.size(40.dp)
102109
)

0 commit comments

Comments
 (0)