Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand Down Expand Up @@ -136,6 +137,7 @@ import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.chat.ui.ShowReactionsModalBottomSheet
import com.nextcloud.talk.chat.data.model.FileParameters
import com.nextcloud.talk.chat.ui.MessageActionsBottomSheet
import com.nextcloud.talk.chat.ui.ProfileModalBottomSheet
Expand Down Expand Up @@ -188,6 +190,7 @@ import com.nextcloud.talk.ui.dialog.DateTimeCompose
import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
import com.nextcloud.talk.ui.dialog.GetPinnedOptionsDialog
import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
import com.nextcloud.talk.ui.dialog.TempMessageActionsDialog
import com.nextcloud.talk.ui.theme.LocalMessageUtils
import com.nextcloud.talk.ui.theme.LocalOpenGraphFetcher
import com.nextcloud.talk.ui.theme.LocalViewThemeUtils
Expand Down Expand Up @@ -746,6 +749,8 @@ class ChatActivity :
LocalMessageUtils provides messageUtils,
LocalOpenGraphFetcher provides { url -> chatViewModel.fetchOpenGraph(url) }
) {
val currentlyPlayingId by chatViewModel.currentlyPlayedMessageId.collectAsState(null)

val isOneToOneConversation = uiState.isOneToOneConversation
Log.d(TAG, "isOneToOneConversation=" + isOneToOneConversation)

Expand All @@ -765,6 +770,7 @@ class ChatActivity :
state = ChatViewState(
chatItems = uiState.items,
isOneToOneConversation = isOneToOneConversation,
currentlyPlayingVoiceMessageId = currentlyPlayingId,
conversationThreadId = conversationThreadId,
chatMode = chatMode,
highlightedMessageId = uiState.highlightedMessageId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ class MediaPlayerManager : LifecycleAwareManager {
private var mediaPlayer: MediaPlayer? = null
private var loop = false
private var scope = MainScope()
private var currentCycledMessage: ChatMessage? = null

private val _currentCycledMessage = MutableStateFlow<ChatMessage?>(null)
val currentCycledMessage: StateFlow<ChatMessage?>
get() = _currentCycledMessage

private var currentDataSource: String = ""
var mediaPlayerDuration: Int = 0
var mediaPlayerPosition: Int = 0
Expand Down Expand Up @@ -140,7 +144,7 @@ class MediaPlayerManager : LifecycleAwareManager {
mediaPlayer!!.stop()
mediaPlayer!!.release()
mediaPlayer = null
currentCycledMessage = null
_currentCycledMessage.value = null
_backgroundPlayUIFlow.tryEmit(null)
_managerState.value = MediaPlayerManagerState.STOPPED
}
Expand Down Expand Up @@ -174,8 +178,8 @@ class MediaPlayerManager : LifecycleAwareManager {

private suspend fun seekbarUpdateObserver() {
withContext(Dispatchers.IO) {
currentCycledMessage?.voiceMessageDuration = mediaPlayerDuration / ONE_SEC
currentCycledMessage?.resetVoiceMessage = false
_currentCycledMessage.value?.voiceMessageDuration = mediaPlayerDuration / ONE_SEC
_currentCycledMessage.value?.resetVoiceMessage = false
while (true) {
if (!loop) {
// NOTE: ok so this doesn't stop the loop, but rather stop the update. Wasteful, but minimal
Expand All @@ -197,7 +201,7 @@ class MediaPlayerManager : LifecycleAwareManager {
val progressI = ceil(progress).toInt()
val seconds = (pos / ONE_SEC)
_mediaPlayerSeekBarPosition.emit(progressI)
currentCycledMessage?.let { msg ->
_currentCycledMessage.value?.let { msg ->
msg.isPlayingVoiceMessage = true
msg.voiceMessageSeekbarProgress = progressI
msg.voiceMessagePlayedSeconds = seconds
Expand Down Expand Up @@ -240,7 +244,7 @@ class MediaPlayerManager : LifecycleAwareManager {
requestedPlaybackSpeed = speed
if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
mediaPlayer!!.playbackParams.let { params ->
params.setSpeed(speed.value)
params.speed = speed.value
mediaPlayer!!.playbackParams = params
}
}
Expand Down Expand Up @@ -270,7 +274,7 @@ class MediaPlayerManager : LifecycleAwareManager {
val pair = playQueue.iterator().next()
setDataSource(pair.first)
currentDataSource = pair.first
currentCycledMessage = pair.second
_currentCycledMessage.value = pair.second
playQueue.removeAt(0)
prepareAsync()
setOnPreparedListener {
Expand All @@ -284,20 +288,20 @@ class MediaPlayerManager : LifecycleAwareManager {
playQueue.removeAt(0)
mediaPlayer?.reset()
mediaPlayer?.setDataSource(nextPair.first)
currentCycledMessage = nextPair.second
_currentCycledMessage.value = nextPair.second
prepare()
} else {
mediaPlayer?.release()
mediaPlayer = null
_backgroundPlayUIFlow.tryEmit(null)
currentCycledMessage?.let {
_currentCycledMessage.value?.let {
it.resetVoiceMessage = true
it.isPlayingVoiceMessage = false
it.voiceMessageSeekbarProgress = 0
it.voiceMessagePlayedSeconds = 0
}
val completedMessage = currentCycledMessage
currentCycledMessage = null
val completedMessage = _currentCycledMessage.value
_currentCycledMessage.value = null
if (completedMessage != null) {
scope.launch {
_mediaPlayerSeekBarPositionMsg.emit(completedMessage)
Expand All @@ -318,16 +322,16 @@ class MediaPlayerManager : LifecycleAwareManager {
mediaPlayerDuration = this.duration

val playBackSpeed = requestedPlaybackSpeed?.value
?: if (currentCycledMessage?.actorId == null) {
?: if (_currentCycledMessage.value?.actorId == null) {
PlaybackSpeed.NORMAL.value
} else {
appPreferences.getPreferredPlayback(currentCycledMessage?.actorId).value
appPreferences.getPreferredPlayback(_currentCycledMessage.value?.actorId).value
}
mediaPlayer!!.playbackParams = mediaPlayer!!.playbackParams.setSpeed(playBackSpeed)

start()
_managerState.value = MediaPlayerManagerState.STARTED
currentCycledMessage?.let {
_currentCycledMessage.value?.let {
it.isPlayingVoiceMessage = true
_backgroundPlayUIFlow.tryEmit(it)
}
Expand All @@ -348,9 +352,9 @@ class MediaPlayerManager : LifecycleAwareManager {

override fun handleOnStop() {
loop = false
if (mediaPlayer != null && currentCycledMessage != null && mediaPlayer!!.isPlaying) {
if (mediaPlayer != null && _currentCycledMessage.value != null && mediaPlayer!!.isPlaying) {
CoroutineScope(Dispatchers.Default).launch {
_backgroundPlayUIFlow.tryEmit(currentCycledMessage!!)
_backgroundPlayUIFlow.tryEmit(_currentCycledMessage.value)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
Expand All @@ -28,7 +29,6 @@ import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.chat.ui.model.ChatMessageUi
import com.nextcloud.talk.chat.ui.model.MessageTypeContent
import com.nextcloud.talk.chat.ui.model.toUiModel
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.conversationlist.DirectShareHelper
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
import com.nextcloud.talk.conversationlist.data.network.OfflineFirstConversationsRepository
Expand Down Expand Up @@ -74,6 +74,8 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -88,12 +90,12 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
Expand Down Expand Up @@ -249,6 +251,10 @@ class ChatViewModel @AssistedInject constructor(
val mediaPlayerSeekbarObserver: Flow<ChatMessage>
get() = mediaPlayerManager.mediaPlayerSeekBarPositionMsg

// FIXME - map this to string id or some other kinda of id idk
val currentlyPlayedMessageId: Flow<Int?>
get() = mediaPlayerManager.currentCycledMessage.map { msg -> msg?.jsonMessageId }

val managerStateFlow: Flow<MediaPlayerManager.MediaPlayerManagerState>
get() = mediaPlayerManager.managerState

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fun ComposeWaveformSeekBar(value: Float, onValueChange: (Float) -> Unit, modifie
for (i in waveData.indices) {
val x: Float = i * (barWidth + barGap)
val y: Float = waveData[i] * height
val isXBeforeThumb = (x / this.size.width) <= value + OVERLAP
val isXBeforeThumb = (x / this.size.width) <= value

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

ComposeWaveformSeekBar(
0f,
0.0f,
{},
modifier = Modifier
.height(MAX_HEIGHT.dp)
Expand Down
24 changes: 13 additions & 11 deletions app/src/main/java/com/nextcloud/talk/ui/chat/ChatMessageView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ private const val QUOTE_HIGHLIGHT_HOLD_MILLIS = 700L
private const val QUOTE_HIGHLIGHT_FADE_OUT_MILLIS = 1500

data class ChatMessageContext(
val currentlyPlayingVoiceMessageId: Int? = null,
val isOneToOneConversation: Boolean = false,
val conversationThreadId: Long? = null,
val hasChatPermission: Boolean = true,
Expand Down Expand Up @@ -175,17 +176,18 @@ fun ChatMessageView(
)
}

is MessageTypeContent.Voice -> {
VoiceMessage(
typeContent = content,
message = message,
isOneToOneConversation = context.isOneToOneConversation,
conversationThreadId = context.conversationThreadId,
onPlayPauseClick = callbacks.onVoicePlayPauseClick,
onSeek = callbacks.onVoiceSeek,
onSpeedClick = callbacks.onVoiceSpeedClick
)
}
is MessageTypeContent.Voice -> {
VoiceMessage(
typeContent = content,
message = message,
isOneToOneConversation = context.isOneToOneConversation,
conversationThreadId = context.conversationThreadId,
currentlyPlayingVoiceMessageId = context.currentlyPlayingVoiceMessageId,
onPlayPauseClick = callbacks.onVoicePlayPauseClick,
onSeek = callbacks.onVoiceSeek,
onSpeedClick = callbacks.onVoiceSpeedClick
)
}

is MessageTypeContent.Poll -> {
PollMessage(
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/nextcloud/talk/ui/chat/ChatView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ data class ChatViewState(
val chatItems: List<ChatViewModel.ChatItem>,
val isOneToOneConversation: Boolean,
val conversationThreadId: Long? = null,
val currentlyPlayingVoiceMessageId: Int? = null,
val hasChatPermission: Boolean = true,
val initialUnreadCount: Int = 0,
val initialShowUnreadPopup: Boolean = false,
Expand Down Expand Up @@ -346,10 +347,10 @@ fun ChatView(
isSelected = state.highlightedMessageId == chatItem.uiMessage.id,
highlightSearchTerm = state.highlightedSearchTerm,
context = ChatMessageContext(
currentlyPlayingVoiceMessageId = state.currentlyPlayingVoiceMessageId,
isOneToOneConversation = state.isOneToOneConversation,
conversationThreadId = state.conversationThreadId,
hasChatPermission = state.hasChatPermission,
downloadingFileState = state.downloadingFileState
hasChatPermission = state.hasChatPermission
),
callbacks = ChatMessageCallbacks(
onLongClick = callbacks.messageCallbacks.onLongClick,
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/java/com/nextcloud/talk/ui/chat/VoiceMessage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ fun VoiceMessage(
message: ChatMessageUi,
isOneToOneConversation: Boolean = false,
conversationThreadId: Long? = null,
currentlyPlayingVoiceMessageId: Int? = null,
onPlayPauseClick: (Int) -> Unit = {},
onSeek: (messageId: Int, progress: Int) -> Unit = { _, _ -> },
onSpeedClick: (messageId: Int) -> Unit = {}
Expand Down Expand Up @@ -83,6 +84,12 @@ fun VoiceMessage(
label = "size"
)

val icon = if (message.id != currentlyPlayingVoiceMessageId) {
Icons.Filled.PlayArrow
} else {
if (typeContent.isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow
}

Column {
Row(
verticalAlignment = Alignment.CenterVertically,
Expand All @@ -96,7 +103,7 @@ fun VoiceMessage(
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = if (typeContent.isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
imageVector = icon,
contentDescription = stringResource(R.string.play_pause_voice_message),
modifier = Modifier.size(40.dp)
)
Expand Down
Loading