Skip to content
Draft
11 changes: 7 additions & 4 deletions app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ class CallActivity : CallBaseActivity() {
private var isBreakoutRoom = false
private val localParticipantMessageListener = LocalParticipantMessageListener { token ->
switchToRoomToken = token
hangup(true, false)
hangup(
shutDownView = true,
endCallForAll = false
)
}
private val offerMessageListener = OfferMessageListener { sessionId, roomType, sdp, nick ->
getOrCreatePeerConnectionWrapperForSessionIdAndType(
Expand Down Expand Up @@ -1919,7 +1922,7 @@ class CallActivity : CallBaseActivity() {

when (messageType) {
"usersInRoom" ->
internalSignalingMessageReceiver.process(signaling.messageWrapper as List<Map<String?, Any?>?>?)
internalSignalingMessageReceiver.process(signaling.messageWrapper as List<Map<String?, Any?>>)

"message" -> {
val ncSignalingMessage = LoganSquare.parse(
Expand Down Expand Up @@ -2735,11 +2738,11 @@ class CallActivity : CallBaseActivity() {
* All listeners are called in the main thread.
*/
private class InternalSignalingMessageReceiver : SignalingMessageReceiver() {
fun process(users: List<Map<String?, Any?>?>?) {
fun process(users: List<Map<String?, Any?>>) {
processUsersInRoom(users)
}

fun process(message: NCSignalingMessage?) {
fun process(message: NCSignalingMessage) {
processSignalingMessage(message)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class ParticipantHandler(
_uiState.update { it.copy(raisedHand = state) }
}

override fun onReaction(reaction: String?) {
override fun onReaction(reaction: String) {
Log.d(TAG, "onReaction")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package com.nextcloud.talk.adapters.messages

import android.content.Context
import android.text.SpannableStringBuilder
import android.util.Log
import android.util.TypedValue
import android.view.View
Expand Down Expand Up @@ -150,10 +151,10 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
binding.messageAuthor.visibility = View.GONE
}
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
binding.messageText.text = processedMessageText
// binding.messageText.text = processedMessageText
// just for debugging:
// binding.messageText.text =
// SpannableStringBuilder(processedMessageText).append(" (" + message.jsonMessageId + ")")
binding.messageText.text =
SpannableStringBuilder(processedMessageText).append(" (" + message.jsonMessageId + ")")
} else {
binding.checkboxContainer.visibility = View.VISIBLE
binding.messageText.visibility = View.GONE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package com.nextcloud.talk.adapters.messages

import android.content.Context
import android.text.SpannableStringBuilder
import android.util.Log
import android.util.TypedValue
import android.view.View
Expand Down Expand Up @@ -163,10 +164,10 @@ class OutcomingTextMessageViewHolder(itemView: View) :

binding.messageTime.layoutParams = layoutParams
viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT)
binding.messageText.text = processedMessageText
// binding.messageText.text = processedMessageText
// just for debugging:
// binding.messageText.text =
// SpannableStringBuilder(processedMessageText).append(" (" + message.jsonMessageId + ")")
binding.messageText.text =
SpannableStringBuilder(processedMessageText).append(" (" + message.jsonMessageId + ")")
} else {
binding.messageText.visibility = View.GONE
binding.checkboxContainer.visibility = View.VISIBLE
Expand Down
12 changes: 0 additions & 12 deletions app/src/main/java/com/nextcloud/talk/api/NcApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -324,18 +324,6 @@ Observable<Response<GenericOverall>> setPassword2(@Header("Authorization") Strin
Observable<RoomCapabilitiesOverall> getRoomCapabilities(@Header("Authorization") String authorization,
@Url String url);

/*
QueryMap items are as follows:
- "lookIntoFuture": int (0 or 1),
- "limit" : int, range 100-200,
- "timeout": used with look into future, 30 default, 60 at most
- "lastKnownMessageId", int, use one from X-Chat-Last-Given
*/
@GET
Observable<Response<ChatOverall>> pullChatMessages(@Header("Authorization") String authorization,
@Url String url,
@QueryMap Map<String, Integer> fields);

/*
Fieldmap items are as follows:
- "message": ,
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.nextcloud.talk.models.json.threads.ThreadsOverall
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.Field
Expand Down Expand Up @@ -323,4 +324,11 @@ interface NcApiCoroutines {

@GET
suspend fun status(@Header("Authorization") authorization: String, @Url url: String): StatusOverall

@GET
suspend fun pullChatMessages(
@Header("Authorization") authorization: String,
@Url url: String,
@QueryMap fields: Map<String, Int>
): Response<ChatOverall>
}
145 changes: 101 additions & 44 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ import com.nextcloud.talk.messagesearch.MessageSearchActivity
import com.nextcloud.talk.models.ExternalSignalingServer
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.participants.Participant
Expand Down Expand Up @@ -229,6 +230,7 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
Expand All @@ -249,6 +251,8 @@ import java.util.Locale
import java.util.concurrent.ExecutionException
import javax.inject.Inject
import kotlin.math.roundToInt
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn

@Suppress("TooManyFunctions")
@AutoInjector(NextcloudTalkApplication::class)
Expand Down Expand Up @@ -427,15 +431,15 @@ class ChatActivity :

var callStarted = false

private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener {
override fun onSwitchTo(token: String?) {
if (token != null) {
if (CallActivity.active) {
Log.d(TAG, "CallActivity is running. Ignore to switch chat in ChatActivity...")
} else {
switchToRoom(token, false, false)
}
}
private val localParticipantMessageListener = SignalingMessageReceiver.LocalParticipantMessageListener { token ->
if (CallActivity.active) {
Log.d(TAG, "CallActivity is running. Ignore to switch chat in ChatActivity...")
} else {
switchToRoom(
token = token,
startCallAfterRoomSwitch = false,
isVoiceOnlyCall = false
)
}
}

Expand Down Expand Up @@ -475,6 +479,17 @@ class ChatActivity :
updateTypingIndicator()
}
}

override fun onChatMessageReceived(chatMessage: ChatMessageJson) {
chatViewModel.onSignalingChatMessageReceived(chatMessage)

Log.d(
TAG,
"received message in ChatActivity. This is the chat message received via HPB. It would be " +
"nicer to receive it in the ViewModel or Repository directly. " +
"Otherwise it needs to be passed into it from here..."
)
}
}

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -648,6 +663,7 @@ class ChatActivity :
this.lifecycle.removeObserver(chatViewModel)
}

@OptIn(FlowPreview::class)
@SuppressLint("NotifyDataSetChanged", "SetTextI18n", "ResourceAsColor")
@Suppress("LongMethod")
private fun initObservers() {
Expand Down Expand Up @@ -814,7 +830,9 @@ class ChatActivity :

chatViewModel.loadMessages(
withCredentials = credentials!!,
withUrl = urlForChatting
withUrl = urlForChatting,
hasHighPerformanceBackend =
WebSocketConnectionHelper.getWebSocketInstanceForUser(conversationUser) != null
)
} else {
Log.w(
Expand Down Expand Up @@ -927,7 +945,7 @@ class ChatActivity :

val id = state.msg.ocs!!.data!!.parentMessage!!.id.toString()
val index = adapter?.getMessagePositionById(id) ?: 0
val message = adapter?.items?.get(index)?.item as ChatMessage
val message = adapter?.items?.get(index)?.item as? ChatMessage
setMessageAsDeleted(message)
}

Expand Down Expand Up @@ -986,43 +1004,45 @@ class ChatActivity :
}
}

this.lifecycleScope.launch {
chatViewModel.getMessageFlow
.onEach { triple ->
val lookIntoFuture = triple.first
val setUnreadMessagesMarker = triple.second
var chatMessageList = triple.third

chatMessageList = handleSystemMessages(chatMessageList)
chatMessageList = handleThreadMessages(chatMessageList)
if (chatMessageList.isEmpty()) {
return@onEach
}

determinePreviousMessageIds(chatMessageList)

handleExpandableSystemMessages(chatMessageList)
chatViewModel.getMessageFlow
.onEach { triple ->
val lookIntoFuture = triple.first
val setUnreadMessagesMarker = triple.second
var chatMessageList = triple.third

if (ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType) {
adapter?.clear()
adapter?.notifyDataSetChanged()
}
chatMessageList = handleSystemMessages(chatMessageList)
chatMessageList = handleThreadMessages(chatMessageList)
if (chatMessageList.isEmpty()) {
return@onEach
}

if (lookIntoFuture) {
Log.d(TAG, "chatMessageList.size in getMessageFlow:" + chatMessageList.size)
processMessagesFromTheFuture(chatMessageList, setUnreadMessagesMarker)
} else {
processMessagesNotFromTheFuture(chatMessageList)
collapseSystemMessages()
}
determinePreviousMessageIds(chatMessageList)

processExpiredMessages()
processCallStartedMessages()
handleExpandableSystemMessages(chatMessageList)

if (ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType) {
adapter?.clear()
adapter?.notifyDataSetChanged()
}
.collect()
}

if (lookIntoFuture) {
Log.d(TAG, "chatMessageList.size in getMessageFlow:" + chatMessageList.size)
processMessagesFromTheFuture(chatMessageList, setUnreadMessagesMarker)
} else {
processMessagesNotFromTheFuture(chatMessageList)
collapseSystemMessages()
}

processExpiredMessages()
processCallStartedMessages()

adapter?.notifyDataSetChanged()
}
.debounce(300)
.onEach {
advanceLocalLastReadMessageIfNeeded()
}
.launchIn(lifecycleScope)

this.lifecycleScope.launch {
chatViewModel.getRemoveMessageFlow
Expand Down Expand Up @@ -1449,6 +1469,8 @@ class ChatActivity :
super.onScrollStateChanged(recyclerView, newState)

if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
advanceLocalLastReadMessageIfNeeded()
updateRemoteLastReadMessageIfNeeded()
if (isScrolledToBottom()) {
binding.unreadMessagesPopup.visibility = View.GONE
binding.scrollDownButton.visibility = View.GONE
Expand Down Expand Up @@ -2743,9 +2765,40 @@ class ChatActivity :
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
mentionAutocomplete?.dismissPopup()
}

// TODO: when updating remote last read message in onPause, there is a race condition with loading conversations
// for conversation list. It may or may not include info about the sent last read message...
// -> save this field offline in conversation?
updateRemoteLastReadMessageIfNeeded()

adapter = null
}

private fun advanceLocalLastReadMessageIfNeeded() {
val position = layoutManager?.findFirstVisibleItemPosition()
position?.let {
// Casting could fail if it's not a chatMessage. It should not matter as the function is triggered often
// enough. If it's a problem, either improve or wait for migration to Jetpack Compose.
val message = adapter?.items?.getOrNull(it)?.item as? ChatMessage
message?.jsonMessageId?.let { messageId ->
chatViewModel.advanceLocalLastReadMessageIfNeeded(messageId)
}
}
}

private fun updateRemoteLastReadMessageIfNeeded() {
val url = ApiUtils.getUrlForChatReadMarker(
ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(ApiUtils.API_V1)),
conversationUser.baseUrl!!,
roomToken
)

chatViewModel.updateRemoteLastReadMessageIfNeeded(
credentials = credentials!!,
url = url
)
}

private fun isActivityNotChangingConfigurations(): Boolean = !isChangingConfigurations

private fun isNotInCall(): Boolean =
Expand Down Expand Up @@ -2872,6 +2925,7 @@ class ChatActivity :

private fun setupWebsocket() {
if (currentConversation == null || conversationUser == null) {
Log.e(TAG, "setupWebsocket: currentConversation or conversationUser is null")
return
}

Expand Down Expand Up @@ -3182,7 +3236,7 @@ class ChatActivity :
it.item is ChatMessage && (it.item as ChatMessage).id == messageId
}
if (messagePosition >= 0) {
val currentItem = adapter?.items?.get(messagePosition)?.item
val currentItem = adapter?.items?.getOrNull(messagePosition)?.item
if (currentItem is ChatMessage && currentItem.id == messageId) {
return Pair(currentItem, messagePosition)
} else {
Expand Down Expand Up @@ -3934,7 +3988,10 @@ class ChatActivity :
fun markAsUnread(message: IMessage?) {
val chatMessage = message as ChatMessage?
if (chatMessage!!.previousMessageId > NO_PREVIOUS_MESSAGE_ID) {
chatViewModel.setChatReadMarker(
// previousMessageId is taken to mark chat as unread even when "chat-unread" capability is not available
// It should be checked if "chat-unread" capability is available and then use
// https://nextcloud-talk.readthedocs.io/en/latest/chat/#mark-chat-as-unread
chatViewModel.setChatReadMessage(
credentials!!,
ApiUtils.getUrlForChatReadMarker(
ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(ApiUtils.API_V1)),
Expand Down
Loading
Loading