Follow up bug fixes for offline support

Got join conversation to work
Unread message popup should work when entering a conversation now
"Delete All Messages" now works without breaking the initMessagePolling
linter

Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
rapterjet2004 2024-08-12 11:17:40 -05:00 committed by Marcel Hibbe
parent 822af2c967
commit ea453dba3e
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
8 changed files with 77 additions and 18 deletions

View File

@ -323,6 +323,7 @@ class ChatActivity :
private val onBackPressedCallback = object : OnBackPressedCallback(true) { private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
chatViewModel.handleChatOnBackPress()
if (currentlyPlayedVoiceMessage != null) { if (currentlyPlayedVoiceMessage != null) {
stopMediaPlayer(currentlyPlayedVoiceMessage!!) stopMediaPlayer(currentlyPlayedVoiceMessage!!)
} }
@ -601,11 +602,13 @@ class ChatActivity :
val urlForChatting = ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken) val urlForChatting = ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken)
if (adapter?.isEmpty == true) {
chatViewModel.loadMessages( chatViewModel.loadMessages(
withCredentials = credentials!!, withCredentials = credentials!!,
withUrl = urlForChatting withUrl = urlForChatting
) )
} }
}
is ChatViewModel.GetCapabilitiesErrorState -> { is ChatViewModel.GetCapabilitiesErrorState -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
@ -2717,7 +2720,7 @@ class ChatActivity :
withUrl = urlForChatting, withUrl = urlForChatting,
withCredentials = credentials!!, withCredentials = credentials!!,
withMessageLimit = MESSAGE_PULL_LIMIT, withMessageLimit = MESSAGE_PULL_LIMIT,
roomToken = currentConversation!!.token!! roomToken = currentConversation!!.token
) )
} }

View File

@ -65,4 +65,9 @@ interface ChatMessageRepository : LifecycleAwareManager {
* Gets a individual message. * Gets a individual message.
*/ */
suspend fun getMessage(messageId: Long, bundle: Bundle): Flow<ChatMessage> suspend fun getMessage(messageId: Long, bundle: Bundle): Flow<ChatMessage>
/**
* Destroys unused resources.
*/
fun handleChatOnBackPress()
} }

View File

@ -31,6 +31,7 @@ import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@ -197,8 +198,8 @@ class OfflineFirstChatRepository @Inject constructor(
val networkParams = Bundle() val networkParams = Bundle()
while (!itIsPaused) { while (true) {
if (!monitor.isOnline.first()) Thread.sleep(500) if (!monitor.isOnline.first() || itIsPaused) Thread.sleep(HALF_SECOND)
// 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)
@ -457,11 +458,13 @@ class OfflineFirstChatRepository @Inject constructor(
// the parent message is always the newest state, no matter how old the system message is. // the parent message is always the newest state, no matter how old the system message is.
// that's why we can just take the parent, update it in DB and update the UI // that's why we can just take the parent, update it in DB and update the UI
messageJson.parentMessage?.let { parentMessageJson -> messageJson.parentMessage?.let { parentMessageJson ->
parentMessageJson.message?.let {
val parentMessageEntity = parentMessageJson.asEntity(currentUser.id!!) val parentMessageEntity = parentMessageJson.asEntity(currentUser.id!!)
chatDao.upsertChatMessage(parentMessageEntity) chatDao.upsertChatMessage(parentMessageEntity)
_updateMessageFlow.emit(parentMessageEntity.asModel()) _updateMessageFlow.emit(parentMessageEntity.asModel())
} }
} }
}
ChatMessage.SystemMessageType.CLEARED_CHAT -> { ChatMessage.SystemMessageType.CLEARED_CHAT -> {
// for lookIntoFuture just deleting everything would be fine. // for lookIntoFuture just deleting everything would be fine.
@ -622,11 +625,16 @@ class OfflineFirstChatRepository @Inject constructor(
// unused atm // unused atm
} }
override fun handleChatOnBackPress() {
scope.cancel()
}
companion object { companion object {
val TAG = OfflineFirstChatRepository::class.simpleName val TAG = OfflineFirstChatRepository::class.simpleName
private const val HTTP_CODE_OK: Int = 200 private const val HTTP_CODE_OK: Int = 200
private const val HTTP_CODE_NOT_MODIFIED = 304 private const val HTTP_CODE_NOT_MODIFIED = 304
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 DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100 private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100
} }
} }

View File

@ -626,6 +626,10 @@ class ChatViewModel @Inject constructor(
_getCapabilitiesViewState.value = GetCapabilitiesStartState _getCapabilitiesViewState.value = GetCapabilitiesStartState
} }
fun handleChatOnBackPress() {
chatRepository.handleChatOnBackPress()
}
suspend fun getMessageById(url: String, conversationModel: ConversationModel, messageId: Long): Flow<ChatMessage> = suspend fun getMessageById(url: String, conversationModel: ConversationModel, messageId: Long): Flow<ChatMessage> =
flow { flow {
val bundle = Bundle() val bundle = Bundle()
@ -634,7 +638,7 @@ class ChatViewModel @Inject constructor(
BundleKeys.KEY_CREDENTIALS, BundleKeys.KEY_CREDENTIALS,
userProvider.currentUser.blockingGet().getCredentials() userProvider.currentUser.blockingGet().getCredentials()
) )
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationModel.token!!) bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationModel.token)
val message = chatRepository.getMessage(messageId, bundle) val message = chatRepository.getMessage(messageId, bundle)
emit(message.first()) emit(message.first())

View File

@ -373,8 +373,8 @@ class ConversationsListActivity :
conversationsListViewModel.getRoomsFlow conversationsListViewModel.getRoomsFlow
.onEach { list -> .onEach { list ->
// Update Conversations // Update Conversations
conversationItems.clear()
conversationItemsWithHeader.clear() conversationItemsWithHeader.clear()
conversationItems.clear() // fixme remove this
for (conversation in list) { for (conversation in list) {
addToConversationItems(conversation) addToConversationItems(conversation)
} }

View File

@ -9,6 +9,7 @@
package com.nextcloud.talk.conversationlist.data.network package com.nextcloud.talk.conversationlist.data.network
import android.util.Log import android.util.Log
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
import com.nextcloud.talk.data.database.dao.ConversationsDao import com.nextcloud.talk.data.database.dao.ConversationsDao
@ -19,7 +20,9 @@ import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -29,11 +32,13 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import javax.inject.Inject import javax.inject.Inject
class OfflineFirstConversationsRepository @Inject constructor( class OfflineFirstConversationsRepository @Inject constructor(
private val dao: ConversationsDao, private val dao: ConversationsDao,
private val network: ConversationsNetworkDataSource, private val network: ConversationsNetworkDataSource,
private val chatNetworkDataSource: ChatNetworkDataSource,
private val monitor: NetworkMonitor, private val monitor: NetworkMonitor,
private val currentUserProviderNew: CurrentUserProviderNew private val currentUserProviderNew: CurrentUserProviderNew
) : OfflineConversationsRepository { ) : OfflineConversationsRepository {
@ -64,7 +69,34 @@ class OfflineFirstConversationsRepository @Inject constructor(
scope.launch { scope.launch {
val id = user.id!! val id = user.id!!
val model = getConversation(id, roomToken) val model = getConversation(id, roomToken)
model.let { _conversationFlow.emit(model) } if (model != null) {
_conversationFlow.emit(model)
} else {
chatNetworkDataSource.getRoom(user, roomToken)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<ConversationModel> {
override fun onSubscribe(p0: Disposable) {
// unused atm
}
override fun onError(e: Throwable) {
// unused atm
}
override fun onComplete() {
// unused atm
}
override fun onNext(model: ConversationModel) {
runBlocking {
_conversationFlow.emit(model)
val entityList = listOf(model.asEntity())
dao.upsertConversations(entityList)
}
}
})
}
} }
private suspend fun sync(): List<ConversationEntity>? { private suspend fun sync(): List<ConversationEntity>? {
@ -107,9 +139,9 @@ class OfflineFirstConversationsRepository @Inject constructor(
it.map(ConversationEntity::asModel) it.map(ConversationEntity::asModel)
}.first() }.first()
private suspend fun getConversation(accountId: Long, token: String): ConversationModel { private suspend fun getConversation(accountId: Long, token: String): ConversationModel? {
val entity = dao.getConversationForUser(accountId, token).first() val entity = dao.getConversationForUser(accountId, token).first()
return entity.asModel() return entity?.asModel()
} }
companion object { companion object {

View File

@ -10,11 +10,11 @@
package com.nextcloud.talk.dagger.modules package com.nextcloud.talk.dagger.modules
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
import com.nextcloud.talk.chat.data.network.RetrofitChatNetwork import com.nextcloud.talk.chat.data.network.RetrofitChatNetwork
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.contacts.ContactsRepository import com.nextcloud.talk.contacts.ContactsRepository
import com.nextcloud.talk.contacts.ContactsRepositoryImpl import com.nextcloud.talk.contacts.ContactsRepositoryImpl
import com.nextcloud.talk.conversation.repository.ConversationRepository import com.nextcloud.talk.conversation.repository.ConversationRepository
@ -191,10 +191,17 @@ class RepositoryModule {
fun provideOfflineFirstConversationsRepository( fun provideOfflineFirstConversationsRepository(
dao: ConversationsDao, dao: ConversationsDao,
dataSource: ConversationsNetworkDataSource, dataSource: ConversationsNetworkDataSource,
chatNetworkDataSource: ChatNetworkDataSource,
networkMonitor: NetworkMonitor, networkMonitor: NetworkMonitor,
currentUserProviderNew: CurrentUserProviderNew currentUserProviderNew: CurrentUserProviderNew
): OfflineConversationsRepository { ): OfflineConversationsRepository {
return OfflineFirstConversationsRepository(dao, dataSource, networkMonitor, currentUserProviderNew) return OfflineFirstConversationsRepository(
dao,
dataSource,
chatNetworkDataSource,
networkMonitor,
currentUserProviderNew
)
} }
@Provides @Provides

View File

@ -20,7 +20,7 @@ interface ConversationsDao {
fun getConversationsForUser(accountId: Long): Flow<List<ConversationEntity>> fun getConversationsForUser(accountId: Long): Flow<List<ConversationEntity>>
@Query("SELECT * FROM Conversations where accountId = :accountId AND token = :token") @Query("SELECT * FROM Conversations where accountId = :accountId AND token = :token")
fun getConversationForUser(accountId: Long, token: String): Flow<ConversationEntity> fun getConversationForUser(accountId: Long, token: String): Flow<ConversationEntity?>
@Upsert @Upsert
fun upsertConversations(conversationEntities: List<ConversationEntity>) fun upsertConversations(conversationEntities: List<ConversationEntity>)