mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-25 06:25:40 +03:00
Merge pull request #4355 from nextcloud/backport/4300/stable-20.0
[stable-20.0] Feature/4299/improve offline support
This commit is contained in:
commit
2ab6111333
6 changed files with 258 additions and 169 deletions
|
@ -183,6 +183,7 @@ import io.reactivex.Observer
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -416,7 +417,12 @@ class ChatActivity :
|
||||||
|
|
||||||
messageInputViewModel = ViewModelProvider(this, viewModelFactory)[MessageInputViewModel::class.java]
|
messageInputViewModel = ViewModelProvider(this, viewModelFactory)[MessageInputViewModel::class.java]
|
||||||
|
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
this.lifecycleScope.launch {
|
||||||
|
delay(DELAY_TO_SHOW_PROGRESS_BAR)
|
||||||
|
if (adapter?.isEmpty == true) {
|
||||||
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||||
|
|
||||||
|
@ -1229,9 +1235,7 @@ class ChatActivity :
|
||||||
|
|
||||||
@Suppress("MagicNumber", "LongMethod")
|
@Suppress("MagicNumber", "LongMethod")
|
||||||
private fun updateTypingIndicator() {
|
private fun updateTypingIndicator() {
|
||||||
fun ellipsize(text: String): String {
|
fun ellipsize(text: String): String = DisplayUtils.ellipsize(text, TYPING_INDICATOR_MAX_NAME_LENGTH)
|
||||||
return DisplayUtils.ellipsize(text, TYPING_INDICATOR_MAX_NAME_LENGTH)
|
|
||||||
}
|
|
||||||
|
|
||||||
val participantNames = ArrayList<String>()
|
val participantNames = ArrayList<String>()
|
||||||
|
|
||||||
|
@ -1305,10 +1309,9 @@ class ChatActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isTypingStatusEnabled(): Boolean {
|
private fun isTypingStatusEnabled(): Boolean =
|
||||||
return webSocketInstance != null &&
|
webSocketInstance != null &&
|
||||||
!CapabilitiesUtil.isTypingStatusPrivate(conversationUser!!)
|
!CapabilitiesUtil.isTypingStatusPrivate(conversationUser!!)
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupSwipeToReply() {
|
private fun setupSwipeToReply() {
|
||||||
if (this::participantPermissions.isInitialized &&
|
if (this::participantPermissions.isInitialized &&
|
||||||
|
@ -1407,15 +1410,18 @@ class ChatActivity :
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isOneToOneConversation() =
|
fun isOneToOneConversation() =
|
||||||
currentConversation != null && currentConversation?.type != null &&
|
currentConversation != null &&
|
||||||
|
currentConversation?.type != null &&
|
||||||
currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
||||||
|
|
||||||
private fun isGroupConversation() =
|
private fun isGroupConversation() =
|
||||||
currentConversation != null && currentConversation?.type != null &&
|
currentConversation != null &&
|
||||||
|
currentConversation?.type != null &&
|
||||||
currentConversation?.type == ConversationEnums.ConversationType.ROOM_GROUP_CALL
|
currentConversation?.type == ConversationEnums.ConversationType.ROOM_GROUP_CALL
|
||||||
|
|
||||||
private fun isPublicConversation() =
|
private fun isPublicConversation() =
|
||||||
currentConversation != null && currentConversation?.type != null &&
|
currentConversation != null &&
|
||||||
|
currentConversation?.type != null &&
|
||||||
currentConversation?.type == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
|
currentConversation?.type == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
|
||||||
|
|
||||||
private fun updateRoomTimerHandler() {
|
private fun updateRoomTimerHandler() {
|
||||||
|
@ -1653,11 +1659,10 @@ class ChatActivity :
|
||||||
adapter?.notifyDataSetChanged()
|
adapter?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isChildOfExpandableSystemMessage(chatMessage: ChatMessage): Boolean {
|
private fun isChildOfExpandableSystemMessage(chatMessage: ChatMessage): Boolean =
|
||||||
return isSystemMessage(chatMessage) &&
|
isSystemMessage(chatMessage) &&
|
||||||
!chatMessage.expandableParent &&
|
!chatMessage.expandableParent &&
|
||||||
chatMessage.lastItemOfExpandableGroup != 0
|
chatMessage.lastItemOfExpandableGroup != 0
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
override fun expandSystemMessage(chatMessageToExpand: ChatMessage) {
|
override fun expandSystemMessage(chatMessageToExpand: ChatMessage) {
|
||||||
|
@ -1743,12 +1748,11 @@ class ChatActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isRecordAudioPermissionGranted(): Boolean {
|
fun isRecordAudioPermissionGranted(): Boolean =
|
||||||
return PermissionChecker.checkSelfPermission(
|
PermissionChecker.checkSelfPermission(
|
||||||
context,
|
context,
|
||||||
Manifest.permission.RECORD_AUDIO
|
Manifest.permission.RECORD_AUDIO
|
||||||
) == PERMISSION_GRANTED
|
) == PERMISSION_GRANTED
|
||||||
}
|
|
||||||
|
|
||||||
fun requestRecordAudioPermissions() {
|
fun requestRecordAudioPermissions() {
|
||||||
requestPermissions(
|
requestPermissions(
|
||||||
|
@ -1855,11 +1859,10 @@ class ChatActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isReadOnlyConversation(): Boolean {
|
private fun isReadOnlyConversation(): Boolean =
|
||||||
return currentConversation?.conversationReadOnlyState != null &&
|
currentConversation?.conversationReadOnlyState != null &&
|
||||||
currentConversation?.conversationReadOnlyState ==
|
currentConversation?.conversationReadOnlyState ==
|
||||||
ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY
|
ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkLobbyState() {
|
private fun checkLobbyState() {
|
||||||
if (currentConversation != null &&
|
if (currentConversation != null &&
|
||||||
|
@ -1875,7 +1878,8 @@ class ChatActivity :
|
||||||
sb.append(resources!!.getText(R.string.nc_lobby_waiting))
|
sb.append(resources!!.getText(R.string.nc_lobby_waiting))
|
||||||
.append("\n\n")
|
.append("\n\n")
|
||||||
|
|
||||||
if (currentConversation?.lobbyTimer != null && currentConversation?.lobbyTimer !=
|
if (currentConversation?.lobbyTimer != null &&
|
||||||
|
currentConversation?.lobbyTimer !=
|
||||||
0L
|
0L
|
||||||
) {
|
) {
|
||||||
val timestampMS = (currentConversation?.lobbyTimer ?: 0) * DateConstants.SECOND_DIVIDER
|
val timestampMS = (currentConversation?.lobbyTimer ?: 0) * DateConstants.SECOND_DIVIDER
|
||||||
|
@ -2074,7 +2078,7 @@ class ChatActivity :
|
||||||
if (position != null && position >= 0) {
|
if (position != null && position >= 0) {
|
||||||
binding.messagesListView.scrollToPosition(position)
|
binding.messagesListView.scrollToPosition(position)
|
||||||
} else {
|
} else {
|
||||||
// TODO show error that we don't have that message?
|
Log.d(TAG, "message $messageId that should be scrolled to was not found (scrollToMessageWithId)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2086,6 +2090,12 @@ class ChatActivity :
|
||||||
position,
|
position,
|
||||||
binding.messagesListView.height / 2
|
binding.messagesListView.height / 2
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"message $messageId that should be scrolled to was not found " +
|
||||||
|
"(scrollToAndCenterMessageWithId)"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2249,11 +2259,10 @@ class ChatActivity :
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validSessionId(): Boolean {
|
private fun validSessionId(): Boolean =
|
||||||
return currentConversation != null &&
|
currentConversation != null &&
|
||||||
sessionIdAfterRoomJoined?.isNotEmpty() == true &&
|
sessionIdAfterRoomJoined?.isNotEmpty() == true &&
|
||||||
sessionIdAfterRoomJoined != "0"
|
sessionIdAfterRoomJoined != "0"
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||||
private fun cancelNotificationsForCurrentConversation() {
|
private fun cancelNotificationsForCurrentConversation() {
|
||||||
|
@ -2306,14 +2315,11 @@ class ChatActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isActivityNotChangingConfigurations(): Boolean {
|
private fun isActivityNotChangingConfigurations(): Boolean = !isChangingConfigurations
|
||||||
return !isChangingConfigurations
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isNotInCall(): Boolean {
|
private fun isNotInCall(): Boolean =
|
||||||
return !ApplicationWideCurrentRoomHolder.getInstance().isInCall &&
|
!ApplicationWideCurrentRoomHolder.getInstance().isInCall &&
|
||||||
!ApplicationWideCurrentRoomHolder.getInstance().isDialing
|
!ApplicationWideCurrentRoomHolder.getInstance().isDialing
|
||||||
}
|
|
||||||
|
|
||||||
private fun setActionBarTitle() {
|
private fun setActionBarTitle() {
|
||||||
val title = binding.chatToolbar.findViewById<TextView>(R.id.chat_toolbar_title)
|
val title = binding.chatToolbar.findViewById<TextView>(R.id.chat_toolbar_title)
|
||||||
|
@ -2750,11 +2756,10 @@ class ChatActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isSameDayNonSystemMessages(messageLeft: ChatMessage, messageRight: ChatMessage): Boolean {
|
private fun isSameDayNonSystemMessages(messageLeft: ChatMessage, messageRight: ChatMessage): Boolean =
|
||||||
return TextUtils.isEmpty(messageLeft.systemMessage) &&
|
TextUtils.isEmpty(messageLeft.systemMessage) &&
|
||||||
TextUtils.isEmpty(messageRight.systemMessage) &&
|
TextUtils.isEmpty(messageRight.systemMessage) &&
|
||||||
DateFormatter.isSameDay(messageLeft.createdAt, messageRight.createdAt)
|
DateFormatter.isSameDay(messageLeft.createdAt, messageRight.createdAt)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadMore(page: Int, totalItemsCount: Int) {
|
override fun onLoadMore(page: Int, totalItemsCount: Int) {
|
||||||
val id = (
|
val id = (
|
||||||
|
@ -2774,15 +2779,14 @@ class ChatActivity :
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun format(date: Date): String {
|
override fun format(date: Date): String =
|
||||||
return if (DateFormatter.isToday(date)) {
|
if (DateFormatter.isToday(date)) {
|
||||||
resources!!.getString(R.string.nc_date_header_today)
|
resources!!.getString(R.string.nc_date_header_today)
|
||||||
} else if (DateFormatter.isYesterday(date)) {
|
} else if (DateFormatter.isYesterday(date)) {
|
||||||
resources!!.getString(R.string.nc_date_header_yesterday)
|
resources!!.getString(R.string.nc_date_header_yesterday)
|
||||||
} else {
|
} else {
|
||||||
DateFormatter.format(date, DateFormatter.Template.STRING_DAY_MONTH_YEAR)
|
DateFormatter.format(date, DateFormatter.Template.STRING_DAY_MONTH_YEAR)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
|
@ -2842,8 +2846,8 @@ class ChatActivity :
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
||||||
return when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.conversation_video_call -> {
|
R.id.conversation_video_call -> {
|
||||||
startACall(false, false)
|
startACall(false, false)
|
||||||
true
|
true
|
||||||
|
@ -2871,7 +2875,6 @@ class ChatActivity :
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun showSharedItems() {
|
private fun showSharedItems() {
|
||||||
val intent = Intent(this, SharedItemsActivity::class.java)
|
val intent = Intent(this, SharedItemsActivity::class.java)
|
||||||
|
@ -2933,25 +2936,23 @@ class ChatActivity :
|
||||||
return chatMessageMap.values.toList()
|
return chatMessageMap.values.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
|
||||||
return currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage
|
currentMessage.value.parentMessageId != null &&
|
||||||
.SystemMessageType.MESSAGE_DELETED
|
currentMessage.value.systemMessageType == ChatMessage
|
||||||
}
|
.SystemMessageType.MESSAGE_DELETED
|
||||||
|
|
||||||
private fun isReactionsMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
private fun isReactionsMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
|
||||||
return currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION ||
|
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION ||
|
||||||
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_DELETED ||
|
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_DELETED ||
|
||||||
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
|
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
|
||||||
}
|
|
||||||
|
|
||||||
private fun isEditMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
private fun isEditMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
|
||||||
return currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage
|
currentMessage.value.parentMessageId != null &&
|
||||||
.SystemMessageType.MESSAGE_EDITED
|
currentMessage.value.systemMessageType == ChatMessage
|
||||||
}
|
.SystemMessageType.MESSAGE_EDITED
|
||||||
|
|
||||||
private fun isPollVotedMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
private fun isPollVotedMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
|
||||||
return currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.POLL_VOTED
|
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.POLL_VOTED
|
||||||
}
|
|
||||||
|
|
||||||
private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
|
private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
|
||||||
currentConversation?.let {
|
currentConversation?.let {
|
||||||
|
@ -3055,9 +3056,8 @@ class ChatActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isSystemMessage(message: ChatMessage): Boolean {
|
private fun isSystemMessage(message: ChatMessage): Boolean =
|
||||||
return ChatMessage.MessageType.SYSTEM_MESSAGE == message.getCalculateMessageType()
|
ChatMessage.MessageType.SYSTEM_MESSAGE == message.getCalculateMessageType()
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteMessage(message: IMessage) {
|
fun deleteMessage(message: IMessage) {
|
||||||
if (!participantPermissions.hasChatPermission()) {
|
if (!participantPermissions.hasChatPermission()) {
|
||||||
|
@ -3300,20 +3300,26 @@ class ChatActivity :
|
||||||
fileViewerUtils.openFileInFilesApp(link!!, keyID!!)
|
fileViewerUtils.openFileInFilesApp(link!!, keyID!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasVisibleItems(message: ChatMessage): Boolean {
|
private fun hasVisibleItems(message: ChatMessage): Boolean =
|
||||||
return !message.isDeleted || // copy message
|
!message.isDeleted ||
|
||||||
message.replyable || // reply to
|
// copy message
|
||||||
message.replyable && // reply privately
|
message.replyable ||
|
||||||
conversationUser?.userId?.isNotEmpty() == true && conversationUser!!.userId != "?" &&
|
// reply to
|
||||||
|
message.replyable &&
|
||||||
|
// reply privately
|
||||||
|
conversationUser?.userId?.isNotEmpty() == true &&
|
||||||
|
conversationUser!!.userId != "?" &&
|
||||||
message.user.id.startsWith("users/") &&
|
message.user.id.startsWith("users/") &&
|
||||||
message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId &&
|
message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId &&
|
||||||
currentConversation?.type != ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
|
currentConversation?.type != ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
|
||||||
isShowMessageDeletionButton(message) || // delete
|
isShowMessageDeletionButton(message) ||
|
||||||
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() || // forward
|
// delete
|
||||||
message.previousMessageId > NO_PREVIOUS_MESSAGE_ID && // mark as unread
|
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() ||
|
||||||
|
// forward
|
||||||
|
message.previousMessageId > NO_PREVIOUS_MESSAGE_ID &&
|
||||||
|
// mark as unread
|
||||||
ChatMessage.MessageType.SYSTEM_MESSAGE != message.getCalculateMessageType() &&
|
ChatMessage.MessageType.SYSTEM_MESSAGE != message.getCalculateMessageType() &&
|
||||||
BuildConfig.DEBUG
|
BuildConfig.DEBUG
|
||||||
}
|
|
||||||
|
|
||||||
private fun setMessageAsDeleted(message: IMessage?) {
|
private fun setMessageAsDeleted(message: IMessage?) {
|
||||||
val messageTemp = message as ChatMessage
|
val messageTemp = message as ChatMessage
|
||||||
|
@ -3431,8 +3437,8 @@ class ChatActivity :
|
||||||
return isUserAllowedByPrivileges
|
return isUserAllowedByPrivileges
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasContentFor(message: ChatMessage, type: Byte): Boolean {
|
override fun hasContentFor(message: ChatMessage, type: Byte): Boolean =
|
||||||
return when (type) {
|
when (type) {
|
||||||
CONTENT_TYPE_LOCATION -> message.hasGeoLocation()
|
CONTENT_TYPE_LOCATION -> message.hasGeoLocation()
|
||||||
CONTENT_TYPE_VOICE_MESSAGE -> message.isVoiceMessage
|
CONTENT_TYPE_VOICE_MESSAGE -> message.isVoiceMessage
|
||||||
CONTENT_TYPE_POLL -> message.isPoll()
|
CONTENT_TYPE_POLL -> message.isPoll()
|
||||||
|
@ -3443,7 +3449,6 @@ class ChatActivity :
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun processMostRecentMessage(recent: ChatMessage, chatMessageList: List<ChatMessage>) {
|
private fun processMostRecentMessage(recent: ChatMessage, chatMessageList: List<ChatMessage>) {
|
||||||
when (recent.systemMessageType) {
|
when (recent.systemMessageType) {
|
||||||
|
@ -3691,5 +3696,6 @@ class ChatActivity :
|
||||||
private const val CURRENT_AUDIO_POSITION_KEY = "CURRENT_AUDIO_POSITION"
|
private const val CURRENT_AUDIO_POSITION_KEY = "CURRENT_AUDIO_POSITION"
|
||||||
private const val CURRENT_AUDIO_WAS_PLAYING_KEY = "CURRENT_AUDIO_PLAYING"
|
private const val CURRENT_AUDIO_WAS_PLAYING_KEY = "CURRENT_AUDIO_PLAYING"
|
||||||
private const val RESUME_AUDIO_TAG = "RESUME_AUDIO_TAG"
|
private const val RESUME_AUDIO_TAG = "RESUME_AUDIO_TAG"
|
||||||
|
private const val DELAY_TO_SHOW_PROGRESS_BAR = 1000L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,10 +56,8 @@ interface ChatMessageRepository : LifecycleAwareManager {
|
||||||
* Long polls the server for any updates to the chat, if found, it synchronizes
|
* Long polls the server for any updates to the chat, if found, it synchronizes
|
||||||
* the database with the server and emits the new messages to [messageFlow],
|
* the database with the server and emits the new messages to [messageFlow],
|
||||||
* else it simply retries after timeout.
|
* else it simply retries after timeout.
|
||||||
*
|
|
||||||
* [withNetworkParams] credentials and url.
|
|
||||||
*/
|
*/
|
||||||
fun initMessagePolling(): Job
|
fun initMessagePolling(initialMessageId: Long): Job
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a individual message.
|
* Gets a individual message.
|
||||||
|
|
|
@ -108,38 +108,101 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
override fun loadInitialMessages(withNetworkParams: Bundle): Job =
|
override fun loadInitialMessages(withNetworkParams: Bundle): Job =
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Log.d(TAG, "---- loadInitialMessages ------------")
|
Log.d(TAG, "---- loadInitialMessages ------------")
|
||||||
|
|
||||||
newXChatLastCommonRead = conversationModel.lastCommonReadMessage
|
newXChatLastCommonRead = conversationModel.lastCommonReadMessage
|
||||||
|
|
||||||
val fieldMap = getFieldMap(
|
Log.d(TAG, "conversationModel.internalId: " + conversationModel.internalId)
|
||||||
lookIntoFuture = false,
|
Log.d(TAG, "conversationModel.lastReadMessage:" + conversationModel.lastReadMessage)
|
||||||
includeLastKnown = true,
|
|
||||||
setReadMarker = true,
|
|
||||||
lastKnown = null
|
|
||||||
)
|
|
||||||
withNetworkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
|
|
||||||
withNetworkParams.putString(BundleKeys.KEY_ROOM_TOKEN, conversationModel.token)
|
|
||||||
|
|
||||||
sync(withNetworkParams)
|
var newestMessageIdFromDb = chatDao.getNewestMessageId(internalConversationId)
|
||||||
|
Log.d(TAG, "newestMessageIdFromDb: $newestMessageIdFromDb")
|
||||||
|
|
||||||
val newestMessageId = chatDao.getNewestMessageId(internalConversationId)
|
val weAlreadyHaveSomeOfflineMessages = newestMessageIdFromDb > 0
|
||||||
Log.d(TAG, "newestMessageId after sync: $newestMessageId")
|
val weHaveAtLeastTheLastReadMessage = newestMessageIdFromDb >= conversationModel.lastReadMessage.toLong()
|
||||||
|
Log.d(TAG, "weAlreadyHaveSomeOfflineMessages:$weAlreadyHaveSomeOfflineMessages")
|
||||||
|
Log.d(TAG, "weHaveAtLeastTheLastReadMessage:$weHaveAtLeastTheLastReadMessage")
|
||||||
|
|
||||||
showLast100MessagesBeforeAndEqual(
|
if (weAlreadyHaveSomeOfflineMessages && weHaveAtLeastTheLastReadMessage) {
|
||||||
internalConversationId,
|
Log.d(
|
||||||
chatDao.getNewestMessageId(internalConversationId)
|
TAG,
|
||||||
)
|
"Initial online request is skipped because offline messages are up to date" +
|
||||||
|
" until lastReadMessage"
|
||||||
|
)
|
||||||
|
Log.d(TAG, "For messages newer than lastRead, lookIntoFuture will load them.")
|
||||||
|
} else {
|
||||||
|
if (!weAlreadyHaveSomeOfflineMessages) {
|
||||||
|
Log.d(TAG, "An online request for newest 100 messages is made because offline chat is empty")
|
||||||
|
} else {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"An online request for newest 100 messages is made because we don't have the lastReadMessage " +
|
||||||
|
"(gaps could be closed by scrolling up to merge the chatblocks)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing
|
// set up field map to load the newest messages
|
||||||
// with them (otherwise there is a race condition).
|
val fieldMap = getFieldMap(
|
||||||
delay(DELAY_TO_ENSURE_MESSAGES_ARE_ADDED)
|
lookIntoFuture = false,
|
||||||
|
includeLastKnown = true,
|
||||||
|
setReadMarker = true,
|
||||||
|
lastKnown = null
|
||||||
|
)
|
||||||
|
withNetworkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
|
||||||
|
withNetworkParams.putString(BundleKeys.KEY_ROOM_TOKEN, conversationModel.token)
|
||||||
|
|
||||||
updateUiForLastCommonRead()
|
Log.d(TAG, "Starting online request for initial loading")
|
||||||
updateUiForLastReadMessage(newestMessageId)
|
val chatMessageEntities = sync(withNetworkParams)
|
||||||
|
if (chatMessageEntities == null) {
|
||||||
|
Log.e(TAG, "initial loading of messages failed")
|
||||||
|
}
|
||||||
|
|
||||||
initMessagePolling()
|
newestMessageIdFromDb = chatDao.getNewestMessageId(internalConversationId)
|
||||||
|
Log.d(TAG, "newestMessageIdFromDb after sync: $newestMessageIdFromDb")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newestMessageIdFromDb.toInt() != 0) {
|
||||||
|
val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb)
|
||||||
|
|
||||||
|
showMessagesBeforeAndEqual(
|
||||||
|
internalConversationId,
|
||||||
|
newestMessageIdFromDb,
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
|
||||||
|
// delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing
|
||||||
|
// with them (otherwise there is a race condition).
|
||||||
|
delay(DELAY_TO_ENSURE_MESSAGES_ARE_ADDED)
|
||||||
|
|
||||||
|
updateUiForLastCommonRead()
|
||||||
|
updateUiForLastReadMessage(newestMessageIdFromDb)
|
||||||
|
}
|
||||||
|
|
||||||
|
initMessagePolling(newestMessageIdFromDb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getCappedMessagesAmountOfChatBlock(messageId: Long): Int {
|
||||||
|
val chatBlock = getBlockOfMessage(messageId.toInt())
|
||||||
|
|
||||||
|
if (chatBlock != null) {
|
||||||
|
val amountBetween = chatDao.getCountBetweenMessageIds(
|
||||||
|
internalConversationId,
|
||||||
|
messageId,
|
||||||
|
chatBlock.oldestMessageId
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d(TAG, "amount of messages between newestMessageId and oldest message of same ChatBlock:$amountBetween")
|
||||||
|
val limit = if (amountBetween > DEFAULT_MESSAGES_LIMIT) {
|
||||||
|
DEFAULT_MESSAGES_LIMIT
|
||||||
|
} else {
|
||||||
|
amountBetween
|
||||||
|
}
|
||||||
|
Log.d(TAG, "limit of messages to load for UI (max 100 to ensure performance is okay):$limit")
|
||||||
|
return limit
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "No chat block found. Returning 0 as limit.")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun updateUiForLastReadMessage(newestMessageId: Long) {
|
private suspend fun updateUiForLastReadMessage(newestMessageId: Long) {
|
||||||
val scrollToLastRead = conversationModel.lastReadMessage.toLong() < newestMessageId
|
val scrollToLastRead = conversationModel.lastReadMessage.toLong() < newestMessageId
|
||||||
if (scrollToLastRead) {
|
if (scrollToLastRead) {
|
||||||
|
@ -175,25 +238,25 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId)
|
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId)
|
||||||
|
|
||||||
if (loadFromServer) {
|
if (loadFromServer) {
|
||||||
|
Log.d(TAG, "Starting online request for loadMoreMessages")
|
||||||
sync(withNetworkParams)
|
sync(withNetworkParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
showLast100MessagesBefore(internalConversationId, beforeMessageId)
|
showMessagesBefore(internalConversationId, beforeMessageId, DEFAULT_MESSAGES_LIMIT)
|
||||||
updateUiForLastCommonRead()
|
updateUiForLastCommonRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initMessagePolling(): Job =
|
override fun initMessagePolling(initialMessageId: Long): Job =
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Log.d(TAG, "---- initMessagePolling ------------")
|
Log.d(TAG, "---- initMessagePolling ------------")
|
||||||
|
|
||||||
val initialMessageId = chatDao.getNewestMessageId(internalConversationId).toInt()
|
|
||||||
Log.d(TAG, "newestMessage: $initialMessageId")
|
Log.d(TAG, "newestMessage: $initialMessageId")
|
||||||
|
|
||||||
var fieldMap = getFieldMap(
|
var fieldMap = getFieldMap(
|
||||||
lookIntoFuture = true,
|
lookIntoFuture = true,
|
||||||
includeLastKnown = false,
|
includeLastKnown = false,
|
||||||
setReadMarker = true,
|
setReadMarker = true,
|
||||||
lastKnown = initialMessageId
|
lastKnown = initialMessageId.toInt()
|
||||||
)
|
)
|
||||||
|
|
||||||
val networkParams = Bundle()
|
val networkParams = Bundle()
|
||||||
|
@ -205,6 +268,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
// sync database with server (This is a long blocking call because long polling (lookIntoFuture) is set)
|
// sync database with server (This is a long blocking call because long polling (lookIntoFuture) is set)
|
||||||
networkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
|
networkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
|
||||||
|
|
||||||
|
Log.d(TAG, "Starting online request for long polling")
|
||||||
val resultsFromSync = sync(networkParams)
|
val resultsFromSync = sync(networkParams)
|
||||||
if (!resultsFromSync.isNullOrEmpty()) {
|
if (!resultsFromSync.isNullOrEmpty()) {
|
||||||
val chatMessages = resultsFromSync.map(ChatMessageEntity::asModel)
|
val chatMessages = resultsFromSync.map(ChatMessageEntity::asModel)
|
||||||
|
@ -240,15 +304,15 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
loadFromServer = false
|
loadFromServer = false
|
||||||
} else {
|
} else {
|
||||||
// we know that beforeMessageId and blockForMessage.oldestMessageId are in the same block.
|
// we know that beforeMessageId and blockForMessage.oldestMessageId are in the same block.
|
||||||
// As we want the last 100 entries before beforeMessageId, we calculate if these messages are 100
|
// As we want the last DEFAULT_MESSAGES_LIMIT entries before beforeMessageId, we calculate if these
|
||||||
// entries apart from each other
|
// messages are DEFAULT_MESSAGES_LIMIT entries apart from each other
|
||||||
|
|
||||||
val amountBetween = chatDao.getCountBetweenMessageIds(
|
val amountBetween = chatDao.getCountBetweenMessageIds(
|
||||||
internalConversationId,
|
internalConversationId,
|
||||||
beforeMessageId,
|
beforeMessageId,
|
||||||
blockForMessage.oldestMessageId
|
blockForMessage.oldestMessageId
|
||||||
)
|
)
|
||||||
loadFromServer = amountBetween < 100
|
loadFromServer = amountBetween < DEFAULT_MESSAGES_LIMIT
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
|
@ -263,7 +327,8 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
lookIntoFuture: Boolean,
|
lookIntoFuture: Boolean,
|
||||||
includeLastKnown: Boolean,
|
includeLastKnown: Boolean,
|
||||||
setReadMarker: Boolean,
|
setReadMarker: Boolean,
|
||||||
lastKnown: Int?
|
lastKnown: Int?,
|
||||||
|
limit: Int = DEFAULT_MESSAGES_LIMIT
|
||||||
): HashMap<String, Int> {
|
): HashMap<String, Int> {
|
||||||
val fieldMap = HashMap<String, Int>()
|
val fieldMap = HashMap<String, Int>()
|
||||||
|
|
||||||
|
@ -278,7 +343,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldMap["timeout"] = if (lookIntoFuture) 30 else 0
|
fieldMap["timeout"] = if (lookIntoFuture) 30 else 0
|
||||||
fieldMap["limit"] = 100
|
fieldMap["limit"] = limit
|
||||||
fieldMap["lookIntoFuture"] = if (lookIntoFuture) 1 else 0
|
fieldMap["lookIntoFuture"] = if (lookIntoFuture) 1 else 0
|
||||||
fieldMap["setReadMarker"] = if (setReadMarker) 1 else 0
|
fieldMap["setReadMarker"] = if (setReadMarker) 1 else 0
|
||||||
|
|
||||||
|
@ -294,73 +359,84 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
lookIntoFuture = false,
|
lookIntoFuture = false,
|
||||||
includeLastKnown = true,
|
includeLastKnown = true,
|
||||||
setReadMarker = false,
|
setReadMarker = false,
|
||||||
lastKnown = messageId.toInt()
|
lastKnown = messageId.toInt(),
|
||||||
|
limit = 1
|
||||||
)
|
)
|
||||||
bundle.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
|
bundle.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
|
||||||
|
|
||||||
// Although only the single message will be returned, a server request will load 100 messages.
|
Log.d(TAG, "Starting online request for single message (e.g. a reply)")
|
||||||
// If this turns out to be confusion for debugging we could load set the limit to 1 for this request.
|
|
||||||
sync(bundle)
|
sync(bundle)
|
||||||
}
|
}
|
||||||
return chatDao.getChatMessageForConversation(internalConversationId, messageId)
|
return chatDao.getChatMessageForConversation(internalConversationId, messageId)
|
||||||
.map(ChatMessageEntity::asModel)
|
.map(ChatMessageEntity::asModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST", "MagicNumber")
|
||||||
private fun getMessagesFromServer(bundle: Bundle): Pair<Int, List<ChatMessageJson>>? {
|
private fun getMessagesFromServer(bundle: Bundle): Pair<Int, List<ChatMessageJson>>? {
|
||||||
Log.d(TAG, "An online request is made!!!!!!!!!!!!!!!!!!!!")
|
|
||||||
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
|
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
|
||||||
|
|
||||||
try {
|
var attempts = 1
|
||||||
val result = network.pullChatMessages(credentials, urlForChatting, fieldMap)
|
while (attempts < 5) {
|
||||||
.subscribeOn(Schedulers.io())
|
Log.d(TAG, "message limit: " + fieldMap["limit"])
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
try {
|
||||||
// .timeout(3, TimeUnit.SECONDS)
|
val result = network.pullChatMessages(credentials, urlForChatting, fieldMap)
|
||||||
.map { it ->
|
.subscribeOn(Schedulers.io())
|
||||||
when (it.code()) {
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
HTTP_CODE_OK -> {
|
.map { it ->
|
||||||
Log.d(TAG, "getMessagesFromServer HTTP_CODE_OK")
|
when (it.code()) {
|
||||||
newXChatLastCommonRead = it.headers()["X-Chat-Last-Common-Read"]?.let {
|
HTTP_CODE_OK -> {
|
||||||
Integer.parseInt(it)
|
Log.d(TAG, "getMessagesFromServer HTTP_CODE_OK")
|
||||||
|
newXChatLastCommonRead = it.headers()["X-Chat-Last-Common-Read"]?.let {
|
||||||
|
Integer.parseInt(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@map Pair(
|
||||||
|
HTTP_CODE_OK,
|
||||||
|
(it.body() as ChatOverall).ocs!!.data!!
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return@map Pair(
|
HTTP_CODE_NOT_MODIFIED -> {
|
||||||
HTTP_CODE_OK,
|
Log.d(TAG, "getMessagesFromServer HTTP_CODE_NOT_MODIFIED")
|
||||||
(it.body() as ChatOverall).ocs!!.data!!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_CODE_NOT_MODIFIED -> {
|
return@map Pair(
|
||||||
Log.d(TAG, "getMessagesFromServer HTTP_CODE_NOT_MODIFIED")
|
HTTP_CODE_NOT_MODIFIED,
|
||||||
|
listOf<ChatMessageJson>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return@map Pair(
|
HTTP_CODE_PRECONDITION_FAILED -> {
|
||||||
HTTP_CODE_NOT_MODIFIED,
|
Log.d(TAG, "getMessagesFromServer HTTP_CODE_PRECONDITION_FAILED")
|
||||||
listOf<ChatMessageJson>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_CODE_PRECONDITION_FAILED -> {
|
return@map Pair(
|
||||||
Log.d(TAG, "getMessagesFromServer HTTP_CODE_PRECONDITION_FAILED")
|
HTTP_CODE_PRECONDITION_FAILED,
|
||||||
|
listOf<ChatMessageJson>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return@map Pair(
|
else -> {
|
||||||
HTTP_CODE_PRECONDITION_FAILED,
|
return@map Pair(
|
||||||
listOf<ChatMessageJson>()
|
HTTP_CODE_PRECONDITION_FAILED,
|
||||||
)
|
listOf<ChatMessageJson>()
|
||||||
}
|
)
|
||||||
|
}
|
||||||
else -> {
|
|
||||||
return@map Pair(
|
|
||||||
HTTP_CODE_PRECONDITION_FAILED,
|
|
||||||
listOf<ChatMessageJson>()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.blockingSingle()
|
||||||
|
return result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Something went wrong when pulling chat messages (attempt: $attempts)", e)
|
||||||
|
attempts++
|
||||||
|
|
||||||
|
val newMessageLimit = when (attempts) {
|
||||||
|
2 -> 50
|
||||||
|
3 -> 10
|
||||||
|
else -> 5
|
||||||
}
|
}
|
||||||
.blockingSingle()
|
fieldMap["limit"] = newMessageLimit
|
||||||
return result
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Something went wrong when pulling chat messages", e)
|
|
||||||
}
|
}
|
||||||
|
Log.e(TAG, "All attempts to get messages from server failed")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,7 +446,12 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = getMessagesFromServer(bundle) ?: return listOf()
|
val result = getMessagesFromServer(bundle)
|
||||||
|
if (result == null) {
|
||||||
|
Log.d(TAG, "No result from server")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
var chatMessagesFromSync: List<ChatMessageEntity>? = null
|
var chatMessagesFromSync: List<ChatMessageEntity>? = null
|
||||||
|
|
||||||
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
|
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
|
||||||
|
@ -471,7 +552,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
ChatMessage.SystemMessageType.CLEARED_CHAT -> {
|
ChatMessage.SystemMessageType.CLEARED_CHAT -> {
|
||||||
// for lookIntoFuture just deleting everything would be fine.
|
// for lookIntoFuture just deleting everything would be fine.
|
||||||
// But lets say we did not open the chat for a while and in between it was cleared.
|
// But lets say we did not open the chat for a while and in between it was cleared.
|
||||||
// We just load the last 100 messages but this don't contain the system message.
|
// We just load the last messages but this don't contain the system message.
|
||||||
// We scroll up and load the system message. Deleting everything is not an option as we
|
// We scroll up and load the system message. Deleting everything is not an option as we
|
||||||
// would loose the messages that we want to keep. We only want to
|
// would loose the messages that we want to keep. We only want to
|
||||||
// delete the messages and chatBlocks older than the system message.
|
// delete the messages and chatBlocks older than the system message.
|
||||||
|
@ -488,13 +569,12 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
* 304 is returned when oldest message of chat was queried or when long polling request returned with no
|
* 304 is returned when oldest message of chat was queried or when long polling request returned with no
|
||||||
* modification. hasHistory is only set to false, when 304 was returned for the the oldest message
|
* modification. hasHistory is only set to false, when 304 was returned for the the oldest message
|
||||||
*/
|
*/
|
||||||
private fun getHasHistory(statusCode: Int, lookIntoFuture: Boolean): Boolean {
|
private fun getHasHistory(statusCode: Int, lookIntoFuture: Boolean): Boolean =
|
||||||
return if (statusCode == HTTP_CODE_NOT_MODIFIED) {
|
if (statusCode == HTTP_CODE_NOT_MODIFIED) {
|
||||||
lookIntoFuture
|
lookIntoFuture
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getBlockOfMessage(queriedMessageId: Int?): ChatBlockEntity? {
|
private suspend fun getBlockOfMessage(queriedMessageId: Int?): ChatBlockEntity? {
|
||||||
var blockContainingQueriedMessage: ChatBlockEntity? = null
|
var blockContainingQueriedMessage: ChatBlockEntity? = null
|
||||||
|
@ -563,7 +643,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun showLast100MessagesBeforeAndEqual(internalConversationId: String, messageId: Long) {
|
private suspend fun showMessagesBeforeAndEqual(internalConversationId: String, messageId: Long, limit: Int) {
|
||||||
suspend fun getMessagesBeforeAndEqual(
|
suspend fun getMessagesBeforeAndEqual(
|
||||||
messageId: Long,
|
messageId: Long,
|
||||||
internalConversationId: String,
|
internalConversationId: String,
|
||||||
|
@ -580,7 +660,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
val list = getMessagesBeforeAndEqual(
|
val list = getMessagesBeforeAndEqual(
|
||||||
messageId,
|
messageId,
|
||||||
internalConversationId,
|
internalConversationId,
|
||||||
100
|
limit
|
||||||
)
|
)
|
||||||
|
|
||||||
if (list.isNotEmpty()) {
|
if (list.isNotEmpty()) {
|
||||||
|
@ -589,7 +669,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun showLast100MessagesBefore(internalConversationId: String, messageId: Long) {
|
private suspend fun showMessagesBefore(internalConversationId: String, messageId: Long, limit: Int) {
|
||||||
suspend fun getMessagesBefore(
|
suspend fun getMessagesBefore(
|
||||||
messageId: Long,
|
messageId: Long,
|
||||||
internalConversationId: String,
|
internalConversationId: String,
|
||||||
|
@ -606,7 +686,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
val list = getMessagesBefore(
|
val list = getMessagesBefore(
|
||||||
messageId,
|
messageId,
|
||||||
internalConversationId,
|
internalConversationId,
|
||||||
100
|
limit
|
||||||
)
|
)
|
||||||
|
|
||||||
if (list.isNotEmpty()) {
|
if (list.isNotEmpty()) {
|
||||||
|
@ -638,5 +718,6 @@ class OfflineFirstChatRepository @Inject constructor(
|
||||||
private const val HTTP_CODE_PRECONDITION_FAILED = 412
|
private const val HTTP_CODE_PRECONDITION_FAILED = 412
|
||||||
private const val HALF_SECOND = 500L
|
private const val HALF_SECOND = 500L
|
||||||
private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100
|
private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100
|
||||||
|
private const val DEFAULT_MESSAGES_LIMIT = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,7 +225,7 @@ class ChatViewModel @Inject constructor(
|
||||||
|
|
||||||
fun getRoom(user: User, token: String) {
|
fun getRoom(user: User, token: String) {
|
||||||
_getRoomViewState.value = GetRoomStartState
|
_getRoomViewState.value = GetRoomStartState
|
||||||
conversationRepository.getConversationSettings(token)
|
conversationRepository.getRoom(token)
|
||||||
|
|
||||||
// chatNetworkDataSource.getRoom(user, token)
|
// chatNetworkDataSource.getRoom(user, token)
|
||||||
// .subscribeOn(Schedulers.io())
|
// .subscribeOn(Schedulers.io())
|
||||||
|
|
|
@ -35,5 +35,5 @@ interface OfflineConversationsRepository {
|
||||||
* Called once onStart to emit a conversation to [conversationFlow]
|
* Called once onStart to emit a conversation to [conversationFlow]
|
||||||
* to be handled asynchronously.
|
* to be handled asynchronously.
|
||||||
*/
|
*/
|
||||||
fun getConversationSettings(roomToken: String): Job
|
fun getRoom(roomToken: String): Job
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,17 +56,19 @@ class OfflineFirstConversationsRepository @Inject constructor(
|
||||||
|
|
||||||
override fun getRooms(): Job =
|
override fun getRooms(): Job =
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val resultsFromSync = sync()
|
val initialConversationModels = getListOfConversations(user.id!!)
|
||||||
if (!resultsFromSync.isNullOrEmpty()) {
|
_roomListFlow.emit(initialConversationModels)
|
||||||
val conversations = resultsFromSync.map(ConversationEntity::asModel)
|
|
||||||
_roomListFlow.emit(conversations)
|
if (monitor.isOnline.first()) {
|
||||||
} else {
|
val conversationEntitiesFromSync = getRoomsFromServer()
|
||||||
val conversationsFromDb = getListOfConversations(user.id!!)
|
if (!conversationEntitiesFromSync.isNullOrEmpty()) {
|
||||||
_roomListFlow.emit(conversationsFromDb)
|
val conversationModelsFromSync = conversationEntitiesFromSync.map(ConversationEntity::asModel)
|
||||||
|
_roomListFlow.emit(conversationModelsFromSync)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getConversationSettings(roomToken: String): Job =
|
override fun getRoom(roomToken: String): Job =
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val id = user.id!!
|
val id = user.id!!
|
||||||
val model = getConversation(id, roomToken)
|
val model = getConversation(id, roomToken)
|
||||||
|
@ -100,7 +102,7 @@ class OfflineFirstConversationsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sync(): List<ConversationEntity>? {
|
private suspend fun getRoomsFromServer(): List<ConversationEntity>? {
|
||||||
var conversationsFromSync: List<ConversationEntity>? = null
|
var conversationsFromSync: List<ConversationEntity>? = null
|
||||||
|
|
||||||
if (!monitor.isOnline.first()) {
|
if (!monitor.isOnline.first()) {
|
||||||
|
@ -129,10 +131,12 @@ class OfflineFirstConversationsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deleteLeftConversations(conversationsFromSync: List<ConversationEntity>) {
|
private suspend fun deleteLeftConversations(conversationsFromSync: List<ConversationEntity>) {
|
||||||
|
val conversationsFromSyncIds = conversationsFromSync.map { it.internalId }.toSet()
|
||||||
val oldConversationsFromDb = dao.getConversationsForUser(user.id!!).first()
|
val oldConversationsFromDb = dao.getConversationsForUser(user.id!!).first()
|
||||||
|
|
||||||
val conversationsToDelete = oldConversationsFromDb.filterNot { conversationsFromSync.contains(it) }
|
val conversationIdsToDelete = oldConversationsFromDb
|
||||||
val conversationIdsToDelete = conversationsToDelete.map { it.internalId }
|
.map { it.internalId }
|
||||||
|
.filterNot { it in conversationsFromSyncIds }
|
||||||
|
|
||||||
dao.deleteConversations(conversationIdsToDelete)
|
dao.deleteConversations(conversationIdsToDelete)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue