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) {
override fun handleOnBackPressed() {
chatViewModel.handleChatOnBackPress()
if (currentlyPlayedVoiceMessage != null) {
stopMediaPlayer(currentlyPlayedVoiceMessage!!)
}
@ -601,10 +602,12 @@ class ChatActivity :
val urlForChatting = ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken)
chatViewModel.loadMessages(
withCredentials = credentials!!,
withUrl = urlForChatting
)
if (adapter?.isEmpty == true) {
chatViewModel.loadMessages(
withCredentials = credentials!!,
withUrl = urlForChatting
)
}
}
is ChatViewModel.GetCapabilitiesErrorState -> {
@ -2717,7 +2720,7 @@ class ChatActivity :
withUrl = urlForChatting,
withCredentials = credentials!!,
withMessageLimit = MESSAGE_PULL_LIMIT,
roomToken = currentConversation!!.token!!
roomToken = currentConversation!!.token
)
}

View file

@ -65,4 +65,9 @@ interface ChatMessageRepository : LifecycleAwareManager {
* Gets a individual message.
*/
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.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@ -197,8 +198,8 @@ class OfflineFirstChatRepository @Inject constructor(
val networkParams = Bundle()
while (!itIsPaused) {
if (!monitor.isOnline.first()) Thread.sleep(500)
while (true) {
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)
networkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
@ -457,9 +458,11 @@ class OfflineFirstChatRepository @Inject constructor(
// 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
messageJson.parentMessage?.let { parentMessageJson ->
val parentMessageEntity = parentMessageJson.asEntity(currentUser.id!!)
chatDao.upsertChatMessage(parentMessageEntity)
_updateMessageFlow.emit(parentMessageEntity.asModel())
parentMessageJson.message?.let {
val parentMessageEntity = parentMessageJson.asEntity(currentUser.id!!)
chatDao.upsertChatMessage(parentMessageEntity)
_updateMessageFlow.emit(parentMessageEntity.asModel())
}
}
}
@ -622,11 +625,16 @@ class OfflineFirstChatRepository @Inject constructor(
// unused atm
}
override fun handleChatOnBackPress() {
scope.cancel()
}
companion object {
val TAG = OfflineFirstChatRepository::class.simpleName
private const val HTTP_CODE_OK: Int = 200
private const val HTTP_CODE_NOT_MODIFIED = 304
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
}
}

View file

@ -626,6 +626,10 @@ class ChatViewModel @Inject constructor(
_getCapabilitiesViewState.value = GetCapabilitiesStartState
}
fun handleChatOnBackPress() {
chatRepository.handleChatOnBackPress()
}
suspend fun getMessageById(url: String, conversationModel: ConversationModel, messageId: Long): Flow<ChatMessage> =
flow {
val bundle = Bundle()
@ -634,7 +638,7 @@ class ChatViewModel @Inject constructor(
BundleKeys.KEY_CREDENTIALS,
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)
emit(message.first())

View file

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

View file

@ -9,6 +9,7 @@
package com.nextcloud.talk.conversationlist.data.network
import android.util.Log
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
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.models.domain.ConversationModel
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -29,11 +32,13 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
class OfflineFirstConversationsRepository @Inject constructor(
private val dao: ConversationsDao,
private val network: ConversationsNetworkDataSource,
private val chatNetworkDataSource: ChatNetworkDataSource,
private val monitor: NetworkMonitor,
private val currentUserProviderNew: CurrentUserProviderNew
) : OfflineConversationsRepository {
@ -64,7 +69,34 @@ class OfflineFirstConversationsRepository @Inject constructor(
scope.launch {
val id = user.id!!
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>? {
@ -107,9 +139,9 @@ class OfflineFirstConversationsRepository @Inject constructor(
it.map(ConversationEntity::asModel)
}.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()
return entity.asModel()
return entity?.asModel()
}
companion object {

View file

@ -10,11 +10,11 @@
package com.nextcloud.talk.dagger.modules
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.network.ChatNetworkDataSource
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
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.ContactsRepositoryImpl
import com.nextcloud.talk.conversation.repository.ConversationRepository
@ -191,10 +191,17 @@ class RepositoryModule {
fun provideOfflineFirstConversationsRepository(
dao: ConversationsDao,
dataSource: ConversationsNetworkDataSource,
chatNetworkDataSource: ChatNetworkDataSource,
networkMonitor: NetworkMonitor,
currentUserProviderNew: CurrentUserProviderNew
): OfflineConversationsRepository {
return OfflineFirstConversationsRepository(dao, dataSource, networkMonitor, currentUserProviderNew)
return OfflineFirstConversationsRepository(
dao,
dataSource,
chatNetworkDataSource,
networkMonitor,
currentUserProviderNew
)
}
@Provides

View file

@ -20,7 +20,7 @@ interface ConversationsDao {
fun getConversationsForUser(accountId: Long): Flow<List<ConversationEntity>>
@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
fun upsertConversations(conversationEntities: List<ConversationEntity>)