Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve chat architecture #4638

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
3 changes: 3 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 @@ -227,4 +227,7 @@ interface NcApiCoroutines {
@Header("Authorization") authorization: String,
@Url url: String
): UserAbsenceOverall

@GET
suspend fun getRoom(@Header("Authorization") authorization: String?, @Url url: String?): RoomOverall
}
257 changes: 118 additions & 139 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import retrofit2.Response
@Suppress("LongParameterList", "TooManyFunctions")
interface ChatNetworkDataSource {
fun getRoom(user: User, roomToken: String): Observable<ConversationModel>
suspend fun getRoomCoroutines(user: User, roomToken: String): ConversationModel
fun getCapabilities(user: User, roomToken: String): Observable<SpreedCapability>
fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel>
fun setReminder(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ class RetrofitChatNetwork(
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!, user) }
}

override suspend fun getRoomCoroutines(user: User, roomToken: String): ConversationModel {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))

val conversation = ncApiCoroutines.getRoom(
credentials,
ApiUtils.getUrlForRoom(apiVersion, user.baseUrl!!, roomToken)
).ocs?.data!!

return ConversationModel.mapToConversationModel(conversation, user)
}

override fun getCapabilities(user: User, roomToken: String): Observable<SpreedCapability> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceData
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.ui.PlaybackSpeed
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
Expand Down Expand Up @@ -67,6 +68,12 @@ class ChatViewModel @Inject constructor(
) : ViewModel(),
DefaultLifecycleObserver {

var chatApiVersion: Int = 1

val currentUser: User = userProvider.currentUser.blockingGet()

lateinit var currentConversation: ConversationModel

enum class LifeCycleFlag {
PAUSED,
RESUMED,
Expand Down Expand Up @@ -145,13 +152,6 @@ class ChatViewModel @Inject constructor(

val getLastReadMessageFlow = chatRepository.lastReadMessageFlow

val getConversationFlow = conversationRepository.conversationFlow
.onEach {
_getRoomViewState.value = GetRoomSuccessState
}.catch {
_getRoomViewState.value = GetRoomErrorState
}

val getGeneralUIFlow = chatRepository.generalUIFlow

sealed interface ViewState
Expand All @@ -172,14 +172,6 @@ class ChatViewModel @Inject constructor(
val getNoteToSelfAvailability: LiveData<ViewState>
get() = _getNoteToSelfAvailability

object GetRoomStartState : ViewState
object GetRoomErrorState : ViewState
object GetRoomSuccessState : ViewState

private val _getRoomViewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
val getRoomViewState: LiveData<ViewState>
get() = _getRoomViewState

object GetCapabilitiesStartState : ViewState
object GetCapabilitiesErrorState : ViewState
open class GetCapabilitiesInitialLoadState(val spreedCapabilities: SpreedCapability) : ViewState
Expand Down Expand Up @@ -243,16 +235,31 @@ class ChatViewModel @Inject constructor(
val reactionDeletedViewState: LiveData<ViewState>
get() = _reactionDeletedViewState

fun setData(conversationModel: ConversationModel, credentials: String, urlForChatting: String) {
chatRepository.setData(conversationModel, credentials, urlForChatting)
}

fun getRoom(token: String) {
_getRoomViewState.value = GetRoomStartState
conversationRepository.getRoom(token)
viewModelScope.launch {
conversationRepository.getRoom(token).collect { conversation ->
currentConversation = conversation!!
// val chatApiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))

val urlForChatting = ApiUtils.getUrlForChat(chatApiVersion, currentUser.baseUrl, token)
val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)

chatRepository.setData(currentConversation, credentials!!, urlForChatting)

// logConversationInfos("GetRoomSuccessState")

// if (adapter == null) { // do later when capabilities are fetched?
// initAdapter()
// binding.messagesListView.setAdapter(adapter)
// layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager?
// }

getCapabilities(currentUser, currentConversation)
}
}
}

fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
fun getCapabilities(user: User, conversationModel: ConversationModel) {
Log.d(TAG, "Remote server ${conversationModel.remoteServer}")
if (conversationModel.remoteServer.isNullOrEmpty()) {
if (_getCapabilitiesViewState.value == GetCapabilitiesStartState) {
Expand All @@ -263,7 +270,7 @@ class ChatViewModel @Inject constructor(
_getCapabilitiesViewState.value = GetCapabilitiesUpdateState(user.capabilities!!.spreedCapability!!)
}
} else {
chatNetworkDataSource.getCapabilities(user, token)
chatNetworkDataSource.getCapabilities(user, conversationModel.token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<SpreedCapability> {
Expand Down Expand Up @@ -362,7 +369,6 @@ class ChatViewModel @Inject constructor(
override fun onNext(t: GenericOverall) {
_leaveRoomViewState.value = LeaveRoomSuccessState(funToCallWhenLeaveSuccessful)
_getCapabilitiesViewState.value = GetCapabilitiesStartState
_getRoomViewState.value = GetRoomStartState
}
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ interface OfflineConversationsRepository {
*/
val roomListFlow: Flow<List<ConversationModel>>

/**
* Stream of a single conversation, for use in each conversations settings.
*/
val conversationFlow: Flow<ConversationModel>

/**
* Loads rooms from local storage. If the rooms are not found, then it
* synchronizes the database with the server, before retrying exactly once. Only
Expand All @@ -35,5 +30,5 @@ interface OfflineConversationsRepository {
* Called once onStart to emit a conversation to [conversationFlow]
* to be handled asynchronously.
*/
fun getRoom(roomToken: String): Job
fun getRoom(roomToken: String): Flow<ConversationModel?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,17 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.utils.CapabilitiesUtil.isUserStatusAvailable
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
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import javax.inject.Inject

class OfflineFirstConversationsRepository @Inject constructor(
Expand All @@ -46,10 +44,6 @@ class OfflineFirstConversationsRepository @Inject constructor(
get() = _roomListFlow
private val _roomListFlow: MutableSharedFlow<List<ConversationModel>> = MutableSharedFlow()

override val conversationFlow: Flow<ConversationModel>
get() = _conversationFlow
private val _conversationFlow: MutableSharedFlow<ConversationModel> = MutableSharedFlow()

private val scope = CoroutineScope(Dispatchers.IO)
private var user: User = currentUserProviderNew.currentUser.blockingGet()

Expand All @@ -67,41 +61,23 @@ class OfflineFirstConversationsRepository @Inject constructor(
}
}

override fun getRoom(roomToken: String): Job =
scope.launch {
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) {
runBlocking {
// In case network is offline or call fails
val id = user.id!!
val model = getConversation(id, roomToken)
if (model != null) {
_conversationFlow.emit(model)
} else {
Log.e(TAG, "Conversation model not found on device database")
}
}
}

override fun onComplete() {
// unused atm
}

override fun onNext(model: ConversationModel) {
runBlocking {
_conversationFlow.emit(model)
val entityList = listOf(model.asEntity())
dao.upsertConversations(entityList)
}
}
})
override fun getRoom(roomToken: String): Flow<ConversationModel?> =
flow {
try {
val conversationModel = chatNetworkDataSource.getRoomCoroutines(user, roomToken)
emit(conversationModel)
val entityList = listOf(conversationModel.asEntity())
dao.upsertConversations(entityList)
} catch (e: Exception) {
// In case network is offline or call fails
val id = user.id!!
val model = getConversation(id, roomToken)
if (model != null) {
emit(model)
} else {
Log.e(TAG, "Conversation model not found on device database")
}
}
}

@Suppress("Detekt.TooGenericExceptionCaught")
Expand Down
Loading