From e469eb2dcf99947fcb0ed7b44661d3d221a3f789 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Sat, 24 Jun 2023 22:57:05 +0200 Subject: [PATCH] WIP. add MVVM's & load stuff from repos to avoid passing too big bundles. Signed-off-by: Marcel Hibbe --- app/src/main/AndroidManifest.xml | 2 +- .../talk/activities/CallActivity.java | 2 +- .../talk/activities/CallBaseActivity.java | 8 +- .../nextcloud/talk/activities/MainActivity.kt | 1 + .../CallNotificationActivity.kt | 196 +++++---- .../viewmodel/CallNotificationViewModel.kt | 78 ++++ .../com/nextcloud/talk/chat/ChatActivity.kt | 415 +++++++++++------- .../talk/chat/data/ChatRepository.kt | 78 +++- .../talk/chat/data/ChatRepositoryImpl.kt | 89 +++- .../talk/chat/data/ConversationModel.kt | 27 -- .../talk/chat/viewmodels/ChatViewModel.kt | 65 ++- .../talk/data/source/local/TalkDatabase.kt | 2 +- .../nextcloud/talk/jobs/NotificationWorker.kt | 5 +- .../models/json/conversations/Conversation.kt | 9 + .../reactions/ReactionsRepository.kt | 5 +- .../reactions/ReactionsRepositoryImpl.kt | 9 +- .../talk/ui/dialog/MessageActionsDialog.kt | 12 +- .../talk/ui/dialog/ShowReactionsDialog.kt | 7 +- .../nextcloud/talk/utils/ConversationUtils.kt | 87 ++++ .../talk/utils/ParticipantPermissions.kt | 10 +- 20 files changed, 760 insertions(+), 347 deletions(-) rename app/src/main/java/com/nextcloud/talk/{activities => callnotification}/CallNotificationActivity.kt (76%) create mode 100644 app/src/main/java/com/nextcloud/talk/callnotification/viewmodel/CallNotificationViewModel.kt delete mode 100644 app/src/main/java/com/nextcloud/talk/chat/data/ConversationModel.kt create mode 100644 app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a5e8cf2471..e8b9bb517f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -140,7 +140,7 @@ android:theme="@style/AppTheme.CallLauncher" /> = Build.VERSION_CODES.O; } - abstract void updateUiForPipMode(); + public abstract void updateUiForPipMode(); - abstract void updateUiForNormalMode(); + public abstract void updateUiForNormalMode(); - abstract void suppressFitsSystemWindows(); + public abstract void suppressFitsSystemWindows(); } diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index 78f4d3535a..9e212beaf3 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -47,6 +47,7 @@ import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.callnotification.CallNotificationActivity import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.controllers.ServerSelectionController import com.nextcloud.talk.controllers.WebViewLoginController diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt b/app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt similarity index 76% rename from app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt rename to app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt index 0c5df6a124..99227b5938 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt @@ -19,7 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.nextcloud.talk.activities +package com.nextcloud.talk.callnotification import android.annotation.SuppressLint import android.content.Intent @@ -30,36 +30,35 @@ import android.os.Handler import android.os.Looper import android.util.Log import android.view.View +import android.widget.Toast import androidx.annotation.RequiresApi import androidx.core.app.NotificationManagerCompat +import androidx.lifecycle.ViewModelProvider import autodagger.AutoInjector import com.nextcloud.talk.R +import com.nextcloud.talk.activities.CallActivity +import com.nextcloud.talk.activities.CallBaseActivity import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.callnotification.viewmodel.CallNotificationViewModel +import com.nextcloud.talk.chat.data.ChatRepository import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.CallNotificationActivityBinding import com.nextcloud.talk.extensions.loadUserAvatar -import com.nextcloud.talk.models.json.conversations.Conversation -import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.ConversationUtils import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.ParticipantPermissions import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability -import io.reactivex.Observer -import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers import okhttp3.Cache -import org.parceler.Parcels import java.io.IOException import javax.inject.Inject @@ -77,13 +76,18 @@ class CallNotificationActivity : CallBaseActivity() { @Inject lateinit var userManager: UserManager + @Inject + lateinit var viewModelFactory: ViewModelProvider.Factory + + lateinit var callNotificationViewModel: CallNotificationViewModel + private val disposablesList: MutableList = ArrayList() private var originalBundle: Bundle? = null private var roomToken: String? = null private var notificationTimestamp: Int? = null private var userBeingCalled: User? = null private var credentials: String? = null - private var currentConversation: Conversation? = null + var currentConversation: ChatRepository.ConversationModel? = null private var leavingScreen = false private var handler: Handler? = null private var binding: CallNotificationActivityBinding? = null @@ -98,19 +102,20 @@ class CallNotificationActivity : CallBaseActivity() { val extras = intent.extras roomToken = extras!!.getString(KEY_ROOM_TOKEN, "") notificationTimestamp = extras.getInt(BundleKeys.KEY_NOTIFICATION_TIMESTAMP) - currentConversation = Parcels.unwrap(extras.getParcelable(KEY_ROOM)) - userBeingCalled = extras.getParcelable(KEY_USER_ENTITY) + + val internalUserId = extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) + userBeingCalled = userManager.getUserWithId(internalUserId).blockingGet() + originalBundle = extras credentials = ApiUtils.getCredentials(userBeingCalled!!.username, userBeingCalled!!.token) + callNotificationViewModel = ViewModelProvider(this, viewModelFactory)[CallNotificationViewModel::class.java] + + initObservers() + if (userManager.setUserAsActive(userBeingCalled!!).blockingGet()) { setCallDescriptionText() - if (currentConversation == null) { - handleFromNotification() - } else { - setUpAfterConversationIsKnown() - } - initClickListeners() + callNotificationViewModel.getRoom(userBeingCalled!!, roomToken!!) } } @@ -140,6 +145,78 @@ class CallNotificationActivity : CallBaseActivity() { binding!!.hangupButton.setOnClickListener { hangup() } } + private fun initObservers() { + val apiVersion = ApiUtils.getConversationApiVersion( + userBeingCalled, + intArrayOf( + ApiUtils.APIv4, + ApiUtils.APIv3, + 1 + ) + ) + + callNotificationViewModel.getRoomViewState.observe(this) { state -> + when (state) { + is CallNotificationViewModel.GetRoomSuccessState -> { + currentConversation = state.conversationModel + + binding!!.conversationNameTextView.text = currentConversation!!.displayName + if (currentConversation!!.type === ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { + binding!!.avatarImageView.loadUserAvatar( + userBeingCalled!!, + currentConversation!!.name!!, + true, + false + ) + } else { + binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group) + } + + val notificationHandler = Handler(Looper.getMainLooper()) + notificationHandler.post(object : Runnable { + override fun run() { + if (NotificationUtils.isNotificationVisible(context, notificationTimestamp!!.toInt())) { + notificationHandler.postDelayed(this, ONE_SECOND) + } else { + finish() + } + } + }) + + showAnswerControls() + + if (apiVersion >= 3) { + val hasCallFlags = hasSpreedFeatureCapability( + userBeingCalled, + "conversation-call-flags" + ) + if (hasCallFlags) { + if (isInCallWithVideo(currentConversation!!.callFlag)) { + binding!!.incomingCallVoiceOrVideoTextView.text = String.format( + resources.getString(R.string.nc_call_video), + resources.getString(R.string.nc_app_product_name) + ) + } else { + binding!!.incomingCallVoiceOrVideoTextView.text = String.format( + resources.getString(R.string.nc_call_voice), + resources.getString(R.string.nc_app_product_name) + ) + } + } + } + + initClickListeners() + } + + is CallNotificationViewModel.GetRoomErrorState -> { + Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show() + } + + else -> {} + } + } + } + private fun setCallDescriptionText() { val callDescriptionWithoutTypeInfo = String.format( resources.getString(R.string.nc_call_unknown), @@ -178,7 +255,7 @@ class CallNotificationActivity : CallBaseActivity() { ) originalBundle!!.putBoolean( BundleKeys.KEY_IS_MODERATOR, - currentConversation!!.isParticipantOwnerOrModerator + ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!) ) val intent = Intent(this, CallActivity::class.java) @@ -189,85 +266,10 @@ class CallNotificationActivity : CallBaseActivity() { } } - @Suppress("MagicNumber") - private fun handleFromNotification() { - val apiVersion = ApiUtils.getConversationApiVersion( - userBeingCalled, - intArrayOf( - ApiUtils.APIv4, - ApiUtils.APIv3, - 1 - ) - ) - ncApi!!.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, userBeingCalled!!.baseUrl, roomToken)) - .subscribeOn(Schedulers.io()) - .retry(GET_ROOM_RETRY_COUNT) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposablesList.add(d) - } - - override fun onNext(roomOverall: RoomOverall) { - currentConversation = roomOverall.ocs!!.data - setUpAfterConversationIsKnown() - if (apiVersion >= 3) { - val hasCallFlags = hasSpreedFeatureCapability( - userBeingCalled, - "conversation-call-flags" - ) - if (hasCallFlags) { - if (isInCallWithVideo(currentConversation!!.callFlag)) { - binding!!.incomingCallVoiceOrVideoTextView.text = String.format( - resources.getString(R.string.nc_call_video), - resources.getString(R.string.nc_app_product_name) - ) - } else { - binding!!.incomingCallVoiceOrVideoTextView.text = String.format( - resources.getString(R.string.nc_call_voice), - resources.getString(R.string.nc_app_product_name) - ) - } - } - } - } - - override fun onError(e: Throwable) { - Log.e(TAG, e.message, e) - } - - override fun onComplete() { - // unused atm - } - }) - } - private fun isInCallWithVideo(callFlag: Int): Boolean { return (callFlag and Participant.InCallFlags.WITH_VIDEO) > 0 } - private fun setUpAfterConversationIsKnown() { - binding!!.conversationNameTextView.text = currentConversation!!.displayName - if (currentConversation!!.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { - binding!!.avatarImageView.loadUserAvatar(userBeingCalled!!, currentConversation!!.name!!, true, false) - } else { - binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group) - } - - val notificationHandler = Handler(Looper.getMainLooper()) - notificationHandler.post(object : Runnable { - override fun run() { - if (NotificationUtils.isNotificationVisible(context, notificationTimestamp!!.toInt())) { - notificationHandler.postDelayed(this, ONE_SECOND) - } else { - finish() - } - } - }) - - showAnswerControls() - } - override fun onStop() { val notificationManager = NotificationManagerCompat.from(context) notificationManager.cancel(notificationTimestamp!!) @@ -303,17 +305,17 @@ class CallNotificationActivity : CallBaseActivity() { } } - public override fun updateUiForPipMode() { + override fun updateUiForPipMode() { binding!!.callAnswerButtons.visibility = View.INVISIBLE binding!!.incomingCallRelativeLayout.visibility = View.INVISIBLE } - public override fun updateUiForNormalMode() { + override fun updateUiForNormalMode() { binding!!.callAnswerButtons.visibility = View.VISIBLE binding!!.incomingCallRelativeLayout.visibility = View.VISIBLE } - public override fun suppressFitsSystemWindows() { + override fun suppressFitsSystemWindows() { binding!!.controllerCallNotificationLayout.fitsSystemWindows = false } diff --git a/app/src/main/java/com/nextcloud/talk/callnotification/viewmodel/CallNotificationViewModel.kt b/app/src/main/java/com/nextcloud/talk/callnotification/viewmodel/CallNotificationViewModel.kt new file mode 100644 index 0000000000..b110146eed --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/callnotification/viewmodel/CallNotificationViewModel.kt @@ -0,0 +1,78 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2023 Marcel Hibbe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.callnotification.viewmodel + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.nextcloud.talk.chat.data.ChatRepository +import com.nextcloud.talk.data.user.model.User +import io.reactivex.Observer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import javax.inject.Inject + +class CallNotificationViewModel @Inject constructor(private val repository: ChatRepository) : + ViewModel() { + + sealed interface ViewState + + object GetRoomStartState : ViewState + object GetRoomErrorState : ViewState + open class GetRoomSuccessState(val conversationModel: ChatRepository.ConversationModel) : ViewState + + private val _getRoomViewState: MutableLiveData = MutableLiveData(GetRoomStartState) + val getRoomViewState: LiveData + get() = _getRoomViewState + + fun getRoom(user: User, token: String) { + _getRoomViewState.value = GetRoomStartState + repository.getRoom(user, token) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(GetRoomObserver()) + } + + inner class GetRoomObserver : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(conversationModel: ChatRepository.ConversationModel) { + _getRoomViewState.value = GetRoomSuccessState(conversationModel) + } + + override fun onError(e: Throwable) { + Log.e(TAG, "Error when fetching room") + _getRoomViewState.value = GetRoomErrorState + } + + override fun onComplete() { + // unused atm + } + } + + companion object { + private val TAG = CallNotificationViewModel::class.simpleName + } +} diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 9e8205fd69..d557776fe5 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -129,6 +129,7 @@ import com.nextcloud.talk.adapters.messages.VoiceMessageInterface import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.callbacks.MentionAutocompleteCallback +import com.nextcloud.talk.chat.data.ChatRepository import com.nextcloud.talk.chat.viewmodels.ChatViewModel import com.nextcloud.talk.conversationinfo.ConversationInfoActivity import com.nextcloud.talk.conversationlist.ConversationsListActivity @@ -148,7 +149,6 @@ import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.chat.ReadStatus -import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.mention.Mention @@ -169,6 +169,7 @@ import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ContactUtils +import com.nextcloud.talk.utils.ConversationUtils import com.nextcloud.talk.utils.DateConstants import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DisplayUtils @@ -273,7 +274,7 @@ class ChatActivity : var conversationUser: User? = null private var roomPassword: String = "" var credentials: String? = null - var currentConversation: Conversation? = null + var currentConversation: ChatRepository.ConversationModel? = null private var globalLastKnownFutureMessageId = -1 private var globalLastKnownPastMessageId = -1 var adapter: TalkMessagesListAdapter? = null @@ -394,13 +395,10 @@ class ChatActivity : handleIntent(intent) chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java] - chatViewModel.fetchConversation(conversationUser!!, roomToken) + // chatViewModel.getRoom(conversationUser!!, roomToken) binding.progressBar.visibility = View.VISIBLE - initAdapter() - binding.messagesListView.setAdapter(adapter) - onBackPressedDispatcher.addCallback(this, onBackPressedCallback) initObservers() @@ -468,22 +466,100 @@ class ChatActivity : } private fun initObservers() { - chatViewModel.viewState.observe(this) { state -> + chatViewModel.getRoomViewState.observe(this) { state -> when (state) { - is ChatViewModel.FetchConversationStartState -> { - Toast.makeText(context, "fetch room...", Toast.LENGTH_LONG).show() + is ChatViewModel.GetRoomSuccessState -> { + currentConversation = state.conversationModel + logConversationInfos("GetRoomSuccessState") + + if (adapter == null) { + initAdapter() + binding.messagesListView.setAdapter(adapter) + } + + layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager? + + loadAvatarForStatusBar() + setActionBarTitle() + participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!) + + setupSwipeToReply() + setupMentionAutocomplete() + checkShowCallButtons() + checkShowMessageInputView() + checkLobbyState() + + if (!validSessionId()) { + joinRoomWithPassword() + } else { + Log.d(TAG, "already inConversation. joinRoomWithPassword is skipped") + } + + val delayForRecursiveCall = if (shouldShowLobby()) { + GET_ROOM_INFO_DELAY_LOBBY + } else { + GET_ROOM_INFO_DELAY_NORMAL + } + + if (getRoomInfoTimerHandler == null) { + getRoomInfoTimerHandler = Handler() + } + getRoomInfoTimerHandler?.postDelayed( + { + chatViewModel.getRoom(conversationUser!!, roomToken) + }, + delayForRecursiveCall + ) } - is ChatViewModel.FetchConversationSuccessState -> { - Toast.makeText( - context, - "fetched room " + - state.conversationModel.displayName, - Toast.LENGTH_LONG - ).show() + + is ChatViewModel.GetRoomErrorState -> { + Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show() } - is ChatViewModel.FetchConversationErrorState -> { + + else -> {} + } + } + + chatViewModel.joinRoomViewState.observe(this) { state -> + when (state) { + is ChatViewModel.JoinRoomSuccessState -> { + currentConversation = state.conversationModel + + sessionIdAfterRoomJoined = currentConversation!!.sessionId + ApplicationWideCurrentRoomHolder.getInstance().session = currentConversation!!.sessionId + ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = currentConversation!!.roomId + ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = currentConversation!!.token + ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser + + logConversationInfos("joinRoomWithPassword#onNext") + + if (isFirstMessagesProcessing) { + pullChatMessages(false) + } else { + pullChatMessages(true, false) + } + + if (webSocketInstance != null) { + webSocketInstance?.joinRoomWithRoomTokenAndSession( + roomToken, + sessionIdAfterRoomJoined + ) + } + if (startCallFromNotification != null && startCallFromNotification ?: false) { + startCallFromNotification = false + startACall(voiceOnly, false) + } + + if (startCallFromRoomSwitch) { + startCallFromRoomSwitch = false + startACall(voiceOnly, true) + } + } + + is ChatViewModel.JoinRoomErrorState -> { Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show() } + else -> {} } } @@ -518,14 +594,13 @@ class ChatActivity : cancelNotificationsForCurrentConversation() - getRoomInfo() + // getRoomInfo() // remove + chatViewModel.getRoom(conversationUser!!, roomToken) actionBar?.show() setupSwipeToReply() - layoutManager = binding?.messagesListView?.layoutManager as LinearLayoutManager? - binding?.popupBubbleView?.setRecyclerView(binding?.messagesListView) binding?.popupBubbleView?.setPopupBubbleListener { context -> @@ -722,8 +797,11 @@ class ChatActivity : val messageHolders = MessageHolders() val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!) - val payload = - MessagePayload(roomToken!!, currentConversation?.isParticipantOwnerOrModerator, profileBottomSheet) + val payload = MessagePayload( + roomToken, + ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!), + profileBottomSheet + ) messageHolders.setIncomingTextConfig( IncomingTextMessageViewHolder::class.java, @@ -1108,67 +1186,67 @@ class ChatActivity : !CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!) } - private fun getRoomInfo() { - logConversationInfos("getRoomInfo") - - conversationUser?.let { - val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1)) - - val startNanoTime = System.nanoTime() - Log.d(TAG, "getRoomInfo - getRoom - calling: $startNanoTime") - ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, it.baseUrl, roomToken)) - ?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposables.add(d) - } - - @Suppress("Detekt.TooGenericExceptionCaught") - override fun onNext(roomOverall: RoomOverall) { - Log.d(TAG, "getRoomInfo - getRoom - got response: $startNanoTime") - currentConversation = roomOverall.ocs!!.data - - logConversationInfos("getRoomInfo#onNext") - - loadAvatarForStatusBar() - setActionBarTitle() - participantPermissions = ParticipantPermissions(it, currentConversation!!) - - setupSwipeToReply() - setupMentionAutocomplete() - checkShowCallButtons() - checkShowMessageInputView() - checkLobbyState() - - if (!validSessionId()) { - joinRoomWithPassword() - } else { - Log.d(TAG, "already inConversation. joinRoomWithPassword is skipped") - } - } - - override fun onError(e: Throwable) { - Log.e(TAG, "getRoomInfo - getRoom - ERROR", e) - } - - override fun onComplete() { - Log.d(TAG, "getRoomInfo - getRoom - onComplete: $startNanoTime") - - val delayForRecursiveCall = if (shouldShowLobby()) { - GET_ROOM_INFO_DELAY_LOBBY - } else { - GET_ROOM_INFO_DELAY_NORMAL - } - - if (getRoomInfoTimerHandler == null) { - getRoomInfoTimerHandler = Handler() - } - getRoomInfoTimerHandler?.postDelayed({ getRoomInfo() }, delayForRecursiveCall) - } - }) - } - } + // private fun getRoomInfo() { + // logConversationInfos("getRoomInfo") + // + // conversationUser?.let { + // val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1)) + // + // val startNanoTime = System.nanoTime() + // Log.d(TAG, "getRoomInfo - getRoom - calling: $startNanoTime") + // ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, it.baseUrl, roomToken)) + // ?.subscribeOn(Schedulers.io()) + // ?.observeOn(AndroidSchedulers.mainThread()) + // ?.subscribe(object : Observer { + // override fun onSubscribe(d: Disposable) { + // disposables.add(d) + // } + // + // @Suppress("Detekt.TooGenericExceptionCaught") + // override fun onNext(roomOverall: RoomOverall) { + // Log.d(TAG, "getRoomInfo - getRoom - got response: $startNanoTime") + // currentConversation = roomOverall.ocs!!.data + // + // logConversationInfos("getRoomInfo#onNext") + // + // loadAvatarForStatusBar() + // setActionBarTitle() + // participantPermissions = ParticipantPermissions(it, currentConversation!!) + // + // setupSwipeToReply() + // setupMentionAutocomplete() + // checkShowCallButtons() + // checkShowMessageInputView() + // checkLobbyState() + // + // if (!validSessionId()) { + // joinRoomWithPassword() + // } else { + // Log.d(TAG, "already inConversation. joinRoomWithPassword is skipped") + // } + // } + // + // override fun onError(e: Throwable) { + // Log.e(TAG, "getRoomInfo - getRoom - ERROR", e) + // } + // + // override fun onComplete() { + // Log.d(TAG, "getRoomInfo - getRoom - onComplete: $startNanoTime") + // + // val delayForRecursiveCall = if (shouldShowLobby()) { + // GET_ROOM_INFO_DELAY_LOBBY + // } else { + // GET_ROOM_INFO_DELAY_NORMAL + // } + // + // if (getRoomInfoTimerHandler == null) { + // getRoomInfoTimerHandler = Handler() + // } + // getRoomInfoTimerHandler?.postDelayed({ getRoomInfo() }, delayForRecursiveCall) + // } + // }) + // } + // } private fun setupSwipeToReply() { if (this::participantPermissions.isInitialized && @@ -1247,18 +1325,18 @@ class ChatActivity : } fun isOneToOneConversation() = currentConversation != null && currentConversation?.type != null && - currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + currentConversation?.type == ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL private fun isGroupConversation() = currentConversation != null && currentConversation?.type != null && - currentConversation?.type == Conversation.ConversationType.ROOM_GROUP_CALL + currentConversation?.type == ChatRepository.ConversationType.ROOM_GROUP_CALL private fun isPublicConversation() = currentConversation != null && currentConversation?.type != null && - currentConversation?.type == Conversation.ConversationType.ROOM_PUBLIC_CALL + currentConversation?.type == ChatRepository.ConversationType.ROOM_PUBLIC_CALL private fun switchToRoom(token: String, startCallAfterRoomSwitch: Boolean, isVoiceOnlyCall: Boolean) { if (conversationUser != null) { runOnUiThread { - if (currentConversation?.objectType == Conversation.ObjectType.ROOM) { + if (currentConversation?.objectType == ChatRepository.ObjectType.ROOM) { Toast.makeText( context, context.resources.getString(R.string.switch_to_main_room), @@ -1689,8 +1767,8 @@ class ChatActivity : private fun shouldShowLobby(): Boolean { if (currentConversation != null) { return CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") && - currentConversation?.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY && - currentConversation?.canModerate(conversationUser!!) == false && + currentConversation?.lobbyState == ChatRepository.LobbyState.LOBBY_STATE_MODERATORS_ONLY && + !ConversationUtils.canModerate(currentConversation!!, conversationUser!!) && !participantPermissions.canIgnoreLobby() } return false @@ -1725,12 +1803,12 @@ class ChatActivity : private fun isReadOnlyConversation(): Boolean { return currentConversation?.conversationReadOnlyState != null && currentConversation?.conversationReadOnlyState == - Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY + ChatRepository.ConversationReadOnlyState.CONVERSATION_READ_ONLY } private fun checkLobbyState() { if (currentConversation != null && - currentConversation?.isLobbyViewApplicable(conversationUser!!) == true + ConversationUtils.isLobbyViewApplicable(currentConversation!!, conversationUser!!) ) { if (shouldShowLobby()) { binding?.lobby?.lobbyView?.visibility = View.VISIBLE @@ -2266,65 +2344,68 @@ class ChatActivity : val startNanoTime = System.nanoTime() Log.d(TAG, "joinRoomWithPassword - joinRoom - calling: $startNanoTime") - ncApi.joinRoom( - credentials, - ApiUtils.getUrlForParticipantsActive(apiVersion, conversationUser?.baseUrl, roomToken), - roomPassword - ) - ?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.retry(RETRIES) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposables.add(d) - } - - @Suppress("Detekt.TooGenericExceptionCaught") - override fun onNext(roomOverall: RoomOverall) { - Log.d(TAG, "joinRoomWithPassword - joinRoom - got response: $startNanoTime") - - val conversation = roomOverall.ocs!!.data!! - currentConversation = conversation - sessionIdAfterRoomJoined = conversation.sessionId - ApplicationWideCurrentRoomHolder.getInstance().session = conversation.sessionId - ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = conversation.roomId - ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = conversation.token - ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser - - logConversationInfos("joinRoomWithPassword#onNext") - - if (isFirstMessagesProcessing) { - pullChatMessages(false) - } else { - pullChatMessages(true, false) - } - - if (webSocketInstance != null) { - webSocketInstance?.joinRoomWithRoomTokenAndSession( - roomToken!!, - sessionIdAfterRoomJoined - ) - } - if (startCallFromNotification != null && startCallFromNotification ?: false) { - startCallFromNotification = false - startACall(voiceOnly, false) - } - - if (startCallFromRoomSwitch) { - startCallFromRoomSwitch = false - startACall(voiceOnly, true) - } - } - - override fun onError(e: Throwable) { - Log.e(TAG, "joinRoomWithPassword - joinRoom - ERROR", e) - } - - override fun onComplete() { - // unused atm - } - }) + chatViewModel.joinRoom(conversationUser!!, roomToken, roomPassword) + + // ncApi.joinRoom( + // credentials, + // ApiUtils.getUrlForParticipantsActive(apiVersion, conversationUser?.baseUrl, roomToken), + // roomPassword + // ) + // ?.subscribeOn(Schedulers.io()) + // ?.observeOn(AndroidSchedulers.mainThread()) + // ?.retry(RETRIES) + // ?.subscribe(object : Observer { + // override fun onSubscribe(d: Disposable) { + // disposables.add(d) + // } + // + // @Suppress("Detekt.TooGenericExceptionCaught") + // override fun onNext(roomOverall: RoomOverall) { + // Log.d(TAG, "joinRoomWithPassword - joinRoom - got response: $startNanoTime") + // + // val conversation = roomOverall.ocs!!.data!! + // currentConversation = conversation + // + // sessionIdAfterRoomJoined = conversation.sessionId + // ApplicationWideCurrentRoomHolder.getInstance().session = conversation.sessionId + // ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = conversation.roomId + // ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = conversation.token + // ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser + // + // logConversationInfos("joinRoomWithPassword#onNext") + // + // if (isFirstMessagesProcessing) { + // pullChatMessages(false) + // } else { + // pullChatMessages(true, false) + // } + // + // if (webSocketInstance != null) { + // webSocketInstance?.joinRoomWithRoomTokenAndSession( + // roomToken!!, + // sessionIdAfterRoomJoined + // ) + // } + // if (startCallFromNotification != null && startCallFromNotification ?: false) { + // startCallFromNotification = false + // startACall(voiceOnly, false) + // } + // + // if (startCallFromRoomSwitch) { + // startCallFromRoomSwitch = false + // startACall(voiceOnly, true) + // } + // } + // + // override fun onError(e: Throwable) { + // Log.e(TAG, "joinRoomWithPassword - joinRoom - ERROR", e) + // } + // + // override fun onComplete() { + // // unused atm + // } + // }) } else { Log.d(TAG, "sessionID was valid -> skip joinRoom") @@ -2793,9 +2874,9 @@ class ChatActivity : GROUPED_MESSAGES_SAME_AUTHOR_THRESHOLD > 0 ) chatMessage.isOneToOneConversation = - (currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) + (currentConversation?.type == ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) chatMessage.isFormerOneToOneConversation = - (currentConversation?.type == Conversation.ConversationType.FORMER_ONE_TO_ONE) + (currentConversation?.type == ChatRepository.ConversationType.FORMER_ONE_TO_ONE) it.addToStart(chatMessage, shouldScroll) } } @@ -2837,9 +2918,9 @@ class ChatActivity : val chatMessage = chatMessageList[i] chatMessage.isOneToOneConversation = - currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + currentConversation?.type == ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL chatMessage.isFormerOneToOneConversation = - (currentConversation?.type == Conversation.ConversationType.FORMER_ONE_TO_ONE) + (currentConversation?.type == ChatRepository.ConversationType.FORMER_ONE_TO_ONE) chatMessage.activeUser = conversationUser } @@ -3026,7 +3107,7 @@ class ChatActivity : intent.putExtra(KEY_ROOM_TOKEN, roomToken) intent.putExtra( SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR, - currentConversation?.isParticipantOwnerOrModerator + ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!) ) startActivity(intent) } @@ -3114,7 +3195,7 @@ class ChatActivity : bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl) bundle.putString(KEY_CONVERSATION_NAME, it.displayName) bundle.putInt(KEY_RECORDING_STATE, it.callRecording) - bundle.putBoolean(KEY_IS_MODERATOR, it.isParticipantOwnerOrModerator) + bundle.putBoolean(KEY_IS_MODERATOR, ConversationUtils.isParticipantOwnerOrModerator(it)) bundle.putBoolean( BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO, participantPermissions.canPublishAudio() @@ -3131,7 +3212,7 @@ class ChatActivity : bundle.putBoolean(BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION, true) } - if (it.objectType == Conversation.ObjectType.ROOM) { + if (it.objectType == ChatRepository.ObjectType.ROOM) { bundle.putBoolean(KEY_IS_BREAKOUT_ROOM, true) } @@ -3146,12 +3227,12 @@ class ChatActivity : override fun onClickReaction(chatMessage: ChatMessage, emoji: String) { VibrationUtils.vibrateShort(context) if (chatMessage.reactionsSelf?.contains(emoji) == true) { - reactionsRepository.deleteReaction(currentConversation!!, chatMessage, emoji) + reactionsRepository.deleteReaction(roomToken, chatMessage, emoji) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(ReactionDeletedObserver()) } else { - reactionsRepository.addReaction(currentConversation!!, chatMessage, emoji) + reactionsRepository.addReaction(roomToken, chatMessage, emoji) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(ReactionAddedObserver()) @@ -3161,7 +3242,7 @@ class ChatActivity : override fun onLongClickReactions(chatMessage: ChatMessage) { ShowReactionsDialog( this, - currentConversation, + roomToken, chatMessage, conversationUser, participantPermissions.hasChatPermission(), @@ -3453,7 +3534,7 @@ class ChatActivity : conversationUser?.userId?.isNotEmpty() == true && conversationUser!!.userId != "?" && message.user.id.startsWith("users/") && message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId && - currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL || + currentConversation?.type != ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL || isShowMessageDeletionButton(message) || // delete ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() || // forward message.previousMessageId > NO_PREVIOUS_MESSAGE_ID && // mark as unread @@ -3522,7 +3603,7 @@ class ChatActivity : messageTemp.isDeleted = true messageTemp.isOneToOneConversation = - currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + currentConversation?.type == ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL messageTemp.activeUser = conversationUser adapter?.update(messageTemp) @@ -3532,7 +3613,7 @@ class ChatActivity : val messageTemp = message as ChatMessage messageTemp.isOneToOneConversation = - currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + currentConversation?.type == ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL messageTemp.activeUser = conversationUser adapter?.update(messageTemp) @@ -3580,7 +3661,7 @@ class ChatActivity : val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) { true } else { - currentConversation!!.canModerate(conversationUser!!) + ConversationUtils.canModerate(currentConversation!!, conversationUser!!) } val isOlderThanSixHours = message @@ -3634,7 +3715,7 @@ class ChatActivity : @Subscribe(threadMode = ThreadMode.BACKGROUND) fun onMessageEvent(userMentionClickEvent: UserMentionClickEvent) { - if (currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL || + if (currentConversation?.type != ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL || currentConversation?.name != userMentionClickEvent.userId ) { var apiVersion = 1 @@ -3669,7 +3750,11 @@ class ChatActivity : val bundle = Bundle() bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token) bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) - bundle.putBoolean(KEY_IS_MODERATOR, roomOverall.ocs!!.data!!.isParticipantOwnerOrModerator) + + // TODO: change return type of createRoom to ConversationModel. + // TODO: move createRoom to ChatRepository + // bundle.putBoolean(KEY_IS_MODERATOR, + // ConversationUtils.isParticipantOwnerOrModerator(roomOverall.ocs!!.data!!)) if (conversationUser != null) { bundle.putParcelable( diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt index 678cbd23ad..a92302c5b2 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt @@ -21,9 +21,85 @@ package com.nextcloud.talk.chat.data import com.nextcloud.talk.data.user.model.User + import io.reactivex.Observable interface ChatRepository { + data class ConversationModel( + var roomId: String?, + var token: String? = null, + var name: String? = null, + var displayName: String? = null, + var description: String? = null, + var type: ConversationType? = null, + var lastPing: Long = 0, + var participantType: ParticipantType? = null, + var hasPassword: Boolean = false, + var sessionId: String? = null, + var actorId: String? = null, + var actorType: String? = null, + var password: String? = null, + var favorite: Boolean = false, + var lastActivity: Long = 0, + var unreadMessages: Int = 0, + var unreadMention: Boolean = false, + // var lastMessage: .....? = null, + var objectType: ObjectType? = null, + var notificationLevel: NotificationLevel? = null, + var conversationReadOnlyState: ConversationReadOnlyState? = null, + var lobbyState: LobbyState? = null, + var lobbyTimer: Long? = null, + var lastReadMessage: Int = 0, + var hasCall: Boolean = false, + var callFlag: Int = 0, + var canStartCall: Boolean = false, + var canLeaveConversation: Boolean? = null, + var canDeleteConversation: Boolean? = null, + var unreadMentionDirect: Boolean? = null, + var notificationCalls: Int? = null, + var permissions: Int = 0, + var messageExpiration: Int = 0, + var status: String? = null, + var statusIcon: String? = null, + var statusMessage: String? = null, + var statusClearAt: Long? = 0, + var callRecording: Int = 0, + var avatarVersion: String? = null, + var hasCustomAvatar: Boolean? = null + ) + + enum class ConversationType { + DUMMY, + ROOM_TYPE_ONE_TO_ONE_CALL, + ROOM_GROUP_CALL, + ROOM_PUBLIC_CALL, + ROOM_SYSTEM, + FORMER_ONE_TO_ONE + } + + enum class ParticipantType { + DUMMY, OWNER, MODERATOR, USER, GUEST, USER_FOLLOWING_LINK, GUEST_MODERATOR + } + + enum class ObjectType { + DEFAULT, + SHARE_PASSWORD, + FILE, + ROOM + } + + enum class NotificationLevel { + DEFAULT, ALWAYS, MENTION, NEVER + } + + enum class ConversationReadOnlyState { + CONVERSATION_READ_WRITE, CONVERSATION_READ_ONLY + } + + enum class LobbyState { + LOBBY_STATE_ALL_PARTICIPANTS, LOBBY_STATE_MODERATORS_ONLY + } - fun fetchConversation(user: User, token: String): Observable + fun getRoom(user: User, roomToken: String): Observable + fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt index 3f337da1a3..0630a15a29 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt @@ -26,32 +26,93 @@ import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.utils.ApiUtils import io.reactivex.Observable -class ChatRepositoryImpl(private val ncApi: NcApi) : - ChatRepository { +class ChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository { // val currentUser: User = currentUserProvider.currentUser.blockingGet() // val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token) - override fun fetchConversation( + override fun getRoom( user: User, - token: String - ): Observable { + roomToken: String + ): Observable { val credentials: String = ApiUtils.getCredentials(user.username, user.token) val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1)) return ncApi.getRoom( credentials, - ApiUtils.getUrlForRoom(apiVersion, user.baseUrl, token) + ApiUtils.getUrlForRoom(apiVersion, user.baseUrl, roomToken) ).map { mapToConversationModel(it.ocs?.data!!) } } - private fun mapToConversationModel( - conversation: Conversation - ): ConversationModel { - return ConversationModel( - conversation.roomId!!, - conversation.token!!, - conversation.name!! - ) + override fun joinRoom( + user: User, + roomToken: String, + roomPassword: String + ): Observable { + val credentials: String = ApiUtils.getCredentials(user.username, user.token) + val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, 1)) + + return ncApi.joinRoom( + credentials, + ApiUtils.getUrlForParticipantsActive(apiVersion, user.baseUrl, roomToken), + roomPassword + ).map { mapToConversationModel(it.ocs?.data!!) } + } + + companion object { + fun mapToConversationModel( + conversation: Conversation + ): ChatRepository.ConversationModel { + return ChatRepository.ConversationModel( + roomId = conversation.roomId, + token = conversation.token, + name = conversation.name, + displayName = conversation.displayName, + description = conversation.description, + type = conversation.type?.let { ChatRepository.ConversationType.valueOf(it.name) }, + lastPing = conversation.lastPing, + participantType = conversation.participantType?.let { ChatRepository.ParticipantType.valueOf(it.name) }, + hasPassword = conversation.hasPassword, + sessionId = conversation.sessionId, + actorId = conversation.actorId, + actorType = conversation.actorType, + password = conversation.password, + favorite = conversation.favorite, + lastActivity = conversation.lastActivity, + unreadMessages = conversation.unreadMessages, + unreadMention = conversation.unreadMention, + // lastMessage = conversation.lastMessage, + objectType = conversation.objectType?.let { ChatRepository.ObjectType.valueOf(it.name) }, + notificationLevel = conversation.notificationLevel?.let { + ChatRepository.NotificationLevel.valueOf( + it.name + ) + }, + conversationReadOnlyState = conversation.conversationReadOnlyState?.let { + ChatRepository.ConversationReadOnlyState.valueOf( + it.name + ) + }, + lobbyState = conversation.lobbyState?.let { ChatRepository.LobbyState.valueOf(it.name) }, + lobbyTimer = conversation.lobbyTimer, + lastReadMessage = conversation.lastReadMessage, + hasCall = conversation.hasCall, + callFlag = conversation.callFlag, + canStartCall = conversation.canStartCall, + canLeaveConversation = conversation.canLeaveConversation, + canDeleteConversation = conversation.canDeleteConversation, + unreadMentionDirect = conversation.unreadMentionDirect, + notificationCalls = conversation.notificationCalls, + permissions = conversation.permissions, + messageExpiration = conversation.messageExpiration, + status = conversation.status, + statusIcon = conversation.statusIcon, + statusMessage = conversation.statusMessage, + statusClearAt = conversation.statusClearAt, + callRecording = conversation.callRecording, + avatarVersion = conversation.avatarVersion, + hasCustomAvatar = conversation.hasCustomAvatar + ) + } } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ConversationModel.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ConversationModel.kt deleted file mode 100644 index 254d7dddf4..0000000000 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ConversationModel.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Marcel Hibbe - * Copyright (C) 2023 Marcel Hibbe - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.chat.data - -data class ConversationModel( - var roomId: String, - var roomToken: String, - var displayName: String -) diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 2cc1ee9135..b61a0aaa17 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -25,7 +25,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.nextcloud.talk.chat.data.ChatRepository -import com.nextcloud.talk.chat.data.ConversationModel import com.nextcloud.talk.data.user.model.User import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers @@ -38,34 +37,70 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) sealed interface ViewState - object FetchConversationStartState : ViewState - object FetchConversationErrorState : ViewState - open class FetchConversationSuccessState(val conversationModel: ConversationModel) : ViewState + object GetRoomStartState : ViewState + object GetRoomErrorState : ViewState + open class GetRoomSuccessState(val conversationModel: ChatRepository.ConversationModel) : ViewState - private val _viewState: MutableLiveData = MutableLiveData(FetchConversationStartState) - val viewState: LiveData - get() = _viewState + private val _getRoomViewState: MutableLiveData = MutableLiveData(GetRoomStartState) + val getRoomViewState: LiveData + get() = _getRoomViewState - fun fetchConversation(user: User, token: String) { - _viewState.value = FetchConversationStartState - repository.fetchConversation(user, token) + object JoinRoomStartState : ViewState + object JoinRoomErrorState : ViewState + open class JoinRoomSuccessState(val conversationModel: ChatRepository.ConversationModel) : ViewState + + private val _joinRoomViewState: MutableLiveData = MutableLiveData(JoinRoomStartState) + val joinRoomViewState: LiveData + get() = _joinRoomViewState + + fun getRoom(user: User, token: String) { + _getRoomViewState.value = GetRoomStartState + repository.getRoom(user, token) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(GetRoomObserver()) + } + + fun joinRoom(user: User, token: String, roomPassword: String) { + _getRoomViewState.value = JoinRoomStartState + repository.joinRoom(user, token, roomPassword) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(RoomObserver()) + ?.retry(3) + ?.subscribe(JoinRoomObserver()) + } + + inner class GetRoomObserver : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(conversationModel: ChatRepository.ConversationModel) { + _getRoomViewState.value = GetRoomSuccessState(conversationModel) + } + + override fun onError(e: Throwable) { + Log.e(TAG, "Error when fetching room") + _getRoomViewState.value = GetRoomErrorState + } + + override fun onComplete() { + // unused atm + } } - inner class RoomObserver : Observer { + inner class JoinRoomObserver : Observer { override fun onSubscribe(d: Disposable) { // unused atm } - override fun onNext(conversationModel: ConversationModel) { - _viewState.value = FetchConversationSuccessState(conversationModel) + override fun onNext(conversationModel: ChatRepository.ConversationModel) { + _joinRoomViewState.value = JoinRoomSuccessState(conversationModel) } override fun onError(e: Throwable) { Log.e(TAG, "Error when fetching room") - _viewState.value = FetchConversationErrorState + _joinRoomViewState.value = JoinRoomErrorState } override fun onComplete() { diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index 7071111bfc..78e3eecd2c 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -94,7 +94,7 @@ abstract class TalkDatabase : RoomDatabase() { return Room .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName) - .openHelperFactory(factory) + // .openHelperFactory(factory) .addMigrations(Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8) .allowMainThreadQueries() .addCallback( diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index de8f6eb68d..71ca915cfa 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -55,12 +55,12 @@ import autodagger.AutoInjector import com.bluelinelabs.logansquare.LoganSquare import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R -import com.nextcloud.talk.activities.CallNotificationActivity import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager +import com.nextcloud.talk.callnotification.CallNotificationActivity import com.nextcloud.talk.models.SignatureVerification import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage import com.nextcloud.talk.models.json.conversations.RoomOverall @@ -197,7 +197,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor val bundle = Bundle() bundle.putString(KEY_ROOM_TOKEN, pushMessage.id) bundle.putInt(KEY_NOTIFICATION_TIMESTAMP, pushMessage.timestamp.toInt()) - bundle.putParcelable(KEY_USER_ENTITY, signatureVerification.user) + // bundle.putParcelable(KEY_USER_ENTITY, signatureVerification.user) + bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!) bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, true) fullScreenIntent.putExtras(bundle) fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK diff --git a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt index f2051acc40..b3a19828e4 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt @@ -158,39 +158,47 @@ data class Conversation( // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' constructor() : this(null, null) + @Deprecated("Use ConversationUtil") val isPublic: Boolean get() = ConversationType.ROOM_PUBLIC_CALL == type + @Deprecated("Use ConversationUtil") val isGuest: Boolean get() = ParticipantType.GUEST == participantType || ParticipantType.GUEST_MODERATOR == participantType || ParticipantType.USER_FOLLOWING_LINK == participantType + @Deprecated("Use ConversationUtil") val isParticipantOwnerOrModerator: Boolean get() = ParticipantType.OWNER == participantType || ParticipantType.GUEST_MODERATOR == participantType || ParticipantType.MODERATOR == participantType + @Deprecated("Use ConversationUtil") private fun isLockedOneToOne(conversationUser: User): Boolean { return type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL && CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms") } + @Deprecated("Use ConversationUtil") fun canModerate(conversationUser: User): Boolean { return isParticipantOwnerOrModerator && !isLockedOneToOne(conversationUser) && type != ConversationType.FORMER_ONE_TO_ONE } + @Deprecated("Use ConversationUtil") fun isLobbyViewApplicable(conversationUser: User): Boolean { return !canModerate(conversationUser) && (type == ConversationType.ROOM_GROUP_CALL || type == ConversationType.ROOM_PUBLIC_CALL) } + @Deprecated("Use ConversationUtil") fun isNameEditable(conversationUser: User): Boolean { return canModerate(conversationUser) && ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != type } + @Deprecated("Use ConversationUtil") fun canLeave(): Boolean { return if (canLeaveConversation != null) { // Available since APIv2 @@ -200,6 +208,7 @@ data class Conversation( } } + @Deprecated("Use ConversationUtil") fun canDelete(conversationUser: User): Boolean { return if (canDeleteConversation != null) { // Available since APIv2 diff --git a/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt b/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt index 912b8cc61a..71a999bd5b 100644 --- a/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt @@ -23,19 +23,18 @@ package com.nextcloud.talk.repositories.reactions import com.nextcloud.talk.models.domain.ReactionAddedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.json.chat.ChatMessage -import com.nextcloud.talk.models.json.conversations.Conversation import io.reactivex.Observable interface ReactionsRepository { fun addReaction( - currentConversation: Conversation, + roomToken: String, message: ChatMessage, emoji: String ): Observable fun deleteReaction( - currentConversation: Conversation, + roomToken: String, message: ChatMessage, emoji: String ): Observable diff --git a/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt index 17eff5b012..2a99c3c429 100644 --- a/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt @@ -25,7 +25,6 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ReactionAddedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.json.chat.ChatMessage -import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.generic.GenericMeta import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew @@ -38,7 +37,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token) override fun addReaction( - currentConversation: Conversation, + roomToken: String, message: ChatMessage, emoji: String ): Observable { @@ -46,7 +45,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur credentials, ApiUtils.getUrlForMessageReaction( currentUser.baseUrl, - currentConversation.token, + roomToken, message.id ), emoji @@ -54,7 +53,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur } override fun deleteReaction( - currentConversation: Conversation, + roomToken: String, message: ChatMessage, emoji: String ): Observable { @@ -62,7 +61,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur credentials, ApiUtils.getUrlForMessageReaction( currentUser.baseUrl, - currentConversation.token, + roomToken, message.id ), emoji diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 87384a2869..375a9f4ab8 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -38,12 +38,12 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.chat.ChatActivity +import com.nextcloud.talk.chat.data.ChatRepository import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.DialogMessageActionsBinding import com.nextcloud.talk.models.domain.ReactionAddedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.json.chat.ChatMessage -import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.repositories.reactions.ReactionsRepository import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew @@ -63,7 +63,7 @@ class MessageActionsDialog( private val chatActivity: ChatActivity, private val message: ChatMessage, private val user: User?, - private val currentConversation: Conversation?, + private val currentConversation: ChatRepository.ConversationModel?, private val showMessageDeletionButton: Boolean, private val hasChatPermission: Boolean ) : BottomSheetDialog(chatActivity) { @@ -100,7 +100,7 @@ class MessageActionsDialog( message.replyable && hasUserId(user) && hasUserActorId(message) && - currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + currentConversation?.type != ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ) initMenuDeleteMessage(showMessageDeletionButton) initMenuForwardMessage( @@ -223,7 +223,7 @@ class MessageActionsDialog( } private fun isPermitted(hasChatPermission: Boolean): Boolean { - return hasChatPermission && Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY != + return hasChatPermission && ChatRepository.ConversationReadOnlyState.CONVERSATION_READ_ONLY != currentConversation?.conversationReadOnlyState } @@ -324,12 +324,12 @@ class MessageActionsDialog( private fun clickOnEmoji(message: ChatMessage, emoji: String) { if (message.reactionsSelf?.contains(emoji) == true) { - reactionsRepository.deleteReaction(currentConversation!!, message, emoji) + reactionsRepository.deleteReaction(currentConversation!!.token!!, message, emoji) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(ReactionDeletedObserver()) } else { - reactionsRepository.addReaction(currentConversation!!, message, emoji) + reactionsRepository.addReaction(currentConversation!!.token!!, message, emoji) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(ReactionAddedObserver()) diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt index 0bba68df4f..196a21b2cb 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt @@ -44,7 +44,6 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.DialogMessageReactionsBinding import com.nextcloud.talk.databinding.ItemReactionsTabBinding import com.nextcloud.talk.models.json.chat.ChatMessage -import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.reactions.ReactionsOverall import com.nextcloud.talk.ui.theme.ViewThemeUtils @@ -59,7 +58,7 @@ import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) class ShowReactionsDialog( activity: Activity, - private val currentConversation: Conversation?, + private val roomToken: String, private val chatMessage: ChatMessage, private val user: User?, private val hasChatPermission: Boolean, @@ -156,7 +155,7 @@ class ShowReactionsDialog( credentials, ApiUtils.getUrlForMessageReaction( user?.baseUrl, - currentConversation!!.token, + roomToken, chatMessage.id ), emoji @@ -211,7 +210,7 @@ class ShowReactionsDialog( credentials, ApiUtils.getUrlForMessageReaction( user?.baseUrl, - currentConversation!!.token, + roomToken, message.id ), emoji diff --git a/app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt new file mode 100644 index 0000000000..f35c0abecc --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt @@ -0,0 +1,87 @@ +package com.nextcloud.talk.utils + +import com.nextcloud.talk.chat.data.ChatRepository +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew + +/* + * Nextcloud Talk application + * @author Marcel Hibbe + * Copyright (C) 2023 Marcel Hibbe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +object ConversationUtils { + private val TAG = ConversationUtils::class.java.simpleName + + fun isPublic(conversation: ChatRepository.ConversationModel): Boolean { + return ChatRepository.ConversationType.ROOM_PUBLIC_CALL == conversation.type + } + + fun isGuest(conversation: ChatRepository.ConversationModel): Boolean { + return ChatRepository.ParticipantType.GUEST == conversation.participantType || + ChatRepository.ParticipantType.GUEST_MODERATOR == conversation.participantType || + ChatRepository.ParticipantType.USER_FOLLOWING_LINK == conversation.participantType + } + + fun isParticipantOwnerOrModerator(conversation: ChatRepository.ConversationModel): Boolean { + return ChatRepository.ParticipantType.OWNER == conversation.participantType || + ChatRepository.ParticipantType.GUEST_MODERATOR == conversation.participantType || + ChatRepository.ParticipantType.MODERATOR == conversation.participantType + } + + private fun isLockedOneToOne(conversation: ChatRepository.ConversationModel, conversationUser: User): Boolean { + return conversation.type == ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL && + CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms") + } + + fun canModerate(conversation: ChatRepository.ConversationModel, conversationUser: User): Boolean { + return isParticipantOwnerOrModerator(conversation) && + !isLockedOneToOne(conversation, conversationUser) && + conversation.type != ChatRepository.ConversationType.FORMER_ONE_TO_ONE + } + + fun isLobbyViewApplicable(conversation: ChatRepository.ConversationModel, conversationUser: User): Boolean { + return !canModerate(conversation, conversationUser) && + ( + conversation.type == ChatRepository.ConversationType.ROOM_GROUP_CALL || + conversation.type == ChatRepository.ConversationType.ROOM_PUBLIC_CALL + ) + } + + fun isNameEditable(conversation: ChatRepository.ConversationModel, conversationUser: User): Boolean { + return canModerate(conversation, conversationUser) && + ChatRepository.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation.type + } + + fun canLeave(conversation: ChatRepository.ConversationModel): Boolean { + return if (conversation.canLeaveConversation != null) { + // Available since APIv2 + conversation.canLeaveConversation!! + } else { + true + } + } + + fun canDelete(conversation: ChatRepository.ConversationModel, conversationUser: User): Boolean { + return if (conversation.canDeleteConversation != null) { + // Available since APIv2 + conversation.canDeleteConversation!! + } else { + canModerate(conversation, conversationUser) + // Fallback for APIv1 + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt b/app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt index cadb8d894b..c6676f497e 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt @@ -22,6 +22,8 @@ package com.nextcloud.talk.utils +import com.nextcloud.talk.chat.data.ChatRepository +import com.nextcloud.talk.chat.data.ChatRepositoryImpl import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew @@ -31,9 +33,15 @@ import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew */ class ParticipantPermissions( private val user: User, - private val conversation: Conversation + private val conversation: ChatRepository.ConversationModel ) { + @Deprecated("Use ChatRepository.ConversationModel") + constructor(user: User, conversation: Conversation) : this( + user, + ChatRepositoryImpl.mapToConversationModel(conversation) + ) + val isDefault = (conversation.permissions and DEFAULT) == DEFAULT val isCustom = (conversation.permissions and CUSTOM) == CUSTOM private val canStartCall = (conversation.permissions and START_CALL) == START_CALL