Эх сурвалжийг харах

Avoid to send conversation and user via intent

sending too much data via intent always is a bad pattern which can lead to TransactionTooLargeException.

When OpenAI translation is enabled, the capabilities contain a ton of translation combinations. These capabilities are contained in 'currentUser' as well in 'selectedConversation'. So, TransactionTooLargeException was thrown.

this PR:
- avoids passing too much data as parcelables in intents (esp. conversation and user)
- introduces MVVM patterns to load required data (esp conversation) from backend (for now via requests, in the future from database first)
- introduces ConversationModel which is created out of the Conversation json model
- loads user data via injection when possible
- creates some quickfixes in ConversationBottomDialog, EntryMenuController and OperationsMenuController.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Marcel Hibbe 1 жил өмнө
parent
commit
817ea1ab64
42 өөрчлөгдсөн 1453 нэмэгдсэн , 868 устгасан
  1. 1 1
      app/src/main/AndroidManifest.xml
  2. 7 4
      app/src/main/java/com/nextcloud/talk/activities/CallActivity.java
  3. 4 4
      app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java
  4. 7 40
      app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
  5. 100 98
      app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt
  6. 79 0
      app/src/main/java/com/nextcloud/talk/callnotification/viewmodel/CallNotificationViewModel.kt
  7. 174 282
      app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  8. 31 0
      app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt
  9. 57 0
      app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt
  10. 116 0
      app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt
  11. 4 42
      app/src/main/java/com/nextcloud/talk/contacts/ContactsActivity.kt
  12. 127 81
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt
  13. 45 69
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt
  14. 6 9
      app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt
  15. 69 100
      app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt
  16. 33 0
      app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepository.kt
  17. 70 0
      app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepositoryImpl.kt
  18. 141 0
      app/src/main/java/com/nextcloud/talk/conversationinfoedit/viewmodel/ConversationInfoEditViewModel.kt
  19. 27 37
      app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
  20. 16 0
      app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
  21. 19 1
      app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
  22. 1 0
      app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt
  23. 21 4
      app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt
  24. 4 6
      app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
  25. 2 2
      app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt
  26. 136 0
      app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt
  27. 9 0
      app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt
  28. 0 3
      app/src/main/java/com/nextcloud/talk/openconversations/ListOpenConversationsActivity.kt
  29. 8 1
      app/src/main/java/com/nextcloud/talk/polls/ui/PollMainDialogFragment.kt
  30. 0 5
      app/src/main/java/com/nextcloud/talk/receivers/ShareRecordingToChatReceiver.kt
  31. 2 3
      app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt
  32. 4 5
      app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt
  33. 7 2
      app/src/main/java/com/nextcloud/talk/shareditems/activities/SharedItemsActivity.kt
  34. 4 38
      app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt
  35. 1 1
      app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java
  36. 6 11
      app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt
  37. 8 6
      app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt
  38. 6 3
      app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt
  39. 3 4
      app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt
  40. 89 0
      app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt
  41. 8 1
      app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt
  42. 1 5
      app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt

+ 1 - 1
app/src/main/AndroidManifest.xml

@@ -140,7 +140,7 @@
             android:theme="@style/AppTheme.CallLauncher" />
 
         <activity
-            android:name=".activities.CallNotificationActivity"
+            android:name=".callnotification.CallNotificationActivity"
             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
             android:excludeFromRecents="true"
             android:launchMode="singleTask"

+ 7 - 4
app/src/main/java/com/nextcloud/talk/activities/CallActivity.java

@@ -100,6 +100,7 @@ import com.nextcloud.talk.utils.NotificationUtils;
 import com.nextcloud.talk.utils.VibrationUtils;
 import com.nextcloud.talk.utils.animations.PulseAnimation;
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew;
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil;
 import com.nextcloud.talk.utils.power.PowerManagerUtils;
 import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder;
@@ -186,7 +187,6 @@ import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID;
 import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN;
 import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH;
 import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM;
-import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY;
 
 @AutoInjector(NextcloudTalkApplication.class)
 public class CallActivity extends CallBaseActivity {
@@ -199,6 +199,9 @@ public class CallActivity extends CallBaseActivity {
     @Inject
     NcApi ncApi;
 
+    @Inject
+    CurrentUserProviderNew currentUserProvider;
+
     @Inject
     UserManager userManager;
 
@@ -386,10 +389,11 @@ public class CallActivity extends CallBaseActivity {
 
         hideNavigationIfNoPipAvailable();
 
+        conversationUser = currentUserProvider.getCurrentUser().blockingGet();
+
         Bundle extras = getIntent().getExtras();
         roomId = extras.getString(KEY_ROOM_ID, "");
         roomToken = extras.getString(KEY_ROOM_TOKEN, "");
-        conversationUser = extras.getParcelable(KEY_USER_ENTITY);
         conversationPassword = extras.getString(KEY_CONVERSATION_PASSWORD, "");
         conversationName = extras.getString(KEY_CONVERSATION_NAME, "");
         isVoiceOnlyCall = extras.getBoolean(KEY_CALL_VOICE_ONLY, false);
@@ -1970,7 +1974,6 @@ public class CallActivity extends CallBaseActivity {
                         bundle.putBoolean(KEY_SWITCH_TO_ROOM, true);
                         bundle.putBoolean(KEY_START_CALL_AFTER_ROOM_SWITCH, true);
                         bundle.putString(KEY_ROOM_TOKEN, switchToRoomToken);
-                        bundle.putParcelable(KEY_USER_ENTITY, conversationUser);
                         bundle.putBoolean(KEY_CALL_VOICE_ONLY, isVoiceOnlyCall);
                         intent.putExtras(bundle);
                         startActivity(intent);
@@ -3151,7 +3154,7 @@ public class CallActivity extends CallBaseActivity {
     }
 
     @Override
-    void suppressFitsSystemWindows() {
+    public void suppressFitsSystemWindows() {
         binding.controllerCallLayout.setFitsSystemWindows(false);
     }
 

+ 4 - 4
app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java

@@ -74,7 +74,7 @@ public abstract class CallBaseActivity extends BaseActivity {
         getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
     }
 
-    void hideNavigationIfNoPipAvailable(){
+    public void hideNavigationIfNoPipAvailable(){
         if (!isPipModePossible()) {
             getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                                                                  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
@@ -160,9 +160,9 @@ public abstract class CallBaseActivity extends BaseActivity {
         return Build.VERSION.SDK_INT >= 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();
 }

+ 7 - 40
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
@@ -61,16 +62,13 @@ import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.SecurityUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ACCOUNT
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
 import io.reactivex.Observer
 import io.reactivex.SingleObserver
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.Disposable
 import io.reactivex.schedulers.Schedulers
-import org.parceler.Parcels
 import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
@@ -282,45 +280,12 @@ class MainActivity : BaseActivity(), ActionBarProvider {
 
                 override fun onNext(roomOverall: RoomOverall) {
                     val bundle = Bundle()
-                    bundle.putParcelable(KEY_USER_ENTITY, currentUser)
                     bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
                     bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
 
-                    // FIXME once APIv2 or later is used only, the createRoom already returns all the data
-                    ncApi.getRoom(
-                        credentials,
-                        ApiUtils.getUrlForRoom(
-                            apiVersion,
-                            currentUser?.baseUrl,
-                            roomOverall.ocs!!.data!!.token
-                        )
-                    )
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(object : Observer<RoomOverall> {
-                            override fun onSubscribe(d: Disposable) {
-                                // unused atm
-                            }
-
-                            override fun onNext(roomOverall: RoomOverall) {
-                                bundle.putParcelable(
-                                    KEY_ACTIVE_CONVERSATION,
-                                    Parcels.wrap(roomOverall.ocs!!.data)
-                                )
-
-                                val chatIntent = Intent(context, ChatActivity::class.java)
-                                chatIntent.putExtras(bundle)
-                                startActivity(chatIntent)
-                            }
-
-                            override fun onError(e: Throwable) {
-                                // unused atm
-                            }
-
-                            override fun onComplete() {
-                                // unused atm
-                            }
-                        })
+                    val chatIntent = Intent(context, ChatActivity::class.java)
+                    chatIntent.putExtras(bundle)
+                    startActivity(chatIntent)
                 }
 
                 override fun onError(e: Throwable) {
@@ -337,7 +302,9 @@ class MainActivity : BaseActivity(), ActionBarProvider {
         super.onNewIntent(intent)
         Log.d(TAG, "onNewIntent Activity: " + System.identityHashCode(this).toString())
 
-        val user = intent.getParcelableExtra<User>(KEY_USER_ENTITY)
+        val internalUserId = intent.extras?.getLong(BundleKeys.KEY_INTERNAL_USER_ID)
+        val user = userManager.getUserWithId(internalUserId!!).blockingGet()
+
         if (user != null && userManager.setUserAsActive(user).blockingGet()) {
             handleIntent(intent)
         }

+ 100 - 98
app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt → 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 <http://www.gnu.org/licenses/>.
  */
-package com.nextcloud.talk.activities
+package com.nextcloud.talk.callnotification
 
 import android.annotation.SuppressLint
 import android.content.Intent
@@ -30,36 +30,36 @@ 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.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.domain.ConversationModel
+import com.nextcloud.talk.models.domain.ConversationType
 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 +77,18 @@ class CallNotificationActivity : CallBaseActivity() {
     @Inject
     lateinit var userManager: UserManager
 
+    @Inject
+    lateinit var viewModelFactory: ViewModelProvider.Factory
+
+    lateinit var callNotificationViewModel: CallNotificationViewModel
+
     private val disposablesList: MutableList<Disposable> = 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: ConversationModel? = null
     private var leavingScreen = false
     private var handler: Handler? = null
     private var binding: CallNotificationActivityBinding? = null
@@ -98,19 +103,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 +146,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 === 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 >= ApiUtils.APIv3) {
+                        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 +256,7 @@ class CallNotificationActivity : CallBaseActivity() {
             )
             originalBundle!!.putBoolean(
                 BundleKeys.KEY_IS_MODERATOR,
-                currentConversation!!.isParticipantOwnerOrModerator
+                ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!)
             )
 
             val intent = Intent(this, CallActivity::class.java)
@@ -189,85 +267,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<RoomOverall> {
-                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,23 +306,22 @@ 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
     }
 
     companion object {
         const val TAG = "CallNotificationActivity"
-        const val GET_ROOM_RETRY_COUNT: Long = 3
         const val ONE_SECOND: Long = 1000
     }
 }

+ 79 - 0
app/src/main/java/com/nextcloud/talk/callnotification/viewmodel/CallNotificationViewModel.kt

@@ -0,0 +1,79 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 com.nextcloud.talk.models.domain.ConversationModel
+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: ConversationModel) : ViewState
+
+    private val _getRoomViewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
+    val getRoomViewState: LiveData<ViewState>
+        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<ConversationModel> {
+        override fun onSubscribe(d: Disposable) {
+            // unused atm
+        }
+
+        override fun onNext(conversationModel: 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
+    }
+}

+ 174 - 282
app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt

@@ -48,7 +48,6 @@ import android.os.Build
 import android.os.Bundle
 import android.os.CountDownTimer
 import android.os.Handler
-import android.os.Parcelable
 import android.os.SystemClock
 import android.provider.ContactsContract
 import android.provider.MediaStore
@@ -85,6 +84,7 @@ import androidx.core.text.bold
 import androidx.core.widget.doAfterTextChanged
 import androidx.emoji2.text.EmojiCompat
 import androidx.emoji2.widget.EmojiTextView
+import androidx.lifecycle.ViewModelProvider
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
@@ -130,6 +130,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.viewmodels.ChatViewModel
 import com.nextcloud.talk.conversationinfo.ConversationInfoActivity
 import com.nextcloud.talk.conversationlist.ConversationsListActivity
 import com.nextcloud.talk.data.user.model.User
@@ -142,15 +143,18 @@ import com.nextcloud.talk.jobs.ShareOperationWorker
 import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
 import com.nextcloud.talk.location.LocationPickerActivity
 import com.nextcloud.talk.messagesearch.MessageSearchActivity
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.domain.ConversationReadOnlyState
+import com.nextcloud.talk.models.domain.ConversationType
+import com.nextcloud.talk.models.domain.LobbyState
+import com.nextcloud.talk.models.domain.ObjectType
 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.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.conversations.RoomsOverall
 import com.nextcloud.talk.models.json.generic.GenericOverall
 import com.nextcloud.talk.models.json.mention.Mention
 import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
@@ -170,6 +174,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
@@ -181,7 +186,6 @@ import com.nextcloud.talk.utils.NotificationUtils
 import com.nextcloud.talk.utils.ParticipantPermissions
 import com.nextcloud.talk.utils.VibrationUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
 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_FILE_PATHS
@@ -193,8 +197,8 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.rx.DisposableSet
 import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
@@ -215,7 +219,6 @@ import io.reactivex.disposables.Disposable
 import io.reactivex.schedulers.Schedulers
 import org.greenrobot.eventbus.Subscribe
 import org.greenrobot.eventbus.ThreadMode
-import org.parceler.Parcels
 import retrofit2.HttpException
 import retrofit2.Response
 import java.io.File
@@ -248,6 +251,9 @@ class ChatActivity :
     @Inject
     lateinit var ncApi: NcApi
 
+    @Inject
+    lateinit var currentUserProvider: CurrentUserProviderNew
+
     @Inject
     lateinit var reactionsRepository: ReactionsRepository
 
@@ -257,6 +263,11 @@ class ChatActivity :
     @Inject
     lateinit var dateUtils: DateUtils
 
+    @Inject
+    lateinit var viewModelFactory: ViewModelProvider.Factory
+
+    lateinit var chatViewModel: ChatViewModel
+
     override val view: View
         get() = binding.root
 
@@ -267,7 +278,7 @@ class ChatActivity :
     var conversationUser: User? = null
     private var roomPassword: String = ""
     var credentials: String? = null
-    var currentConversation: Conversation? = null
+    var currentConversation: ConversationModel? = null
     private var globalLastKnownFutureMessageId = -1
     private var globalLastKnownPastMessageId = -1
     var adapter: TalkMessagesListAdapter<ChatMessage>? = null
@@ -383,14 +394,17 @@ class ChatActivity :
         setContentView(binding.root)
         setupSystemColors()
 
+        conversationUser = currentUserProvider.currentUser.blockingGet()
+
         handleIntent(intent)
 
-        binding.progressBar.visibility = View.VISIBLE
+        chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java]
 
-        initAdapter()
-        binding.messagesListView.setAdapter(adapter)
+        binding.progressBar.visibility = View.VISIBLE
 
         onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+
+        initObservers()
     }
 
     override fun onNewIntent(intent: Intent) {
@@ -415,7 +429,6 @@ class ChatActivity :
     private fun handleIntent(intent: Intent) {
         val extras: Bundle? = intent.extras
 
-        conversationUser = extras?.getParcelable(KEY_USER_ENTITY)
         roomId = extras?.getString(KEY_ROOM_ID).orEmpty()
         roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty()
 
@@ -426,11 +439,6 @@ class ChatActivity :
             Log.d(TAG, "   roomToken was null or empty!")
         }
 
-        if (intent.hasExtra(KEY_ACTIVE_CONVERSATION)) {
-            currentConversation = Parcels.unwrap<Conversation>(extras?.getParcelable(KEY_ACTIVE_CONVERSATION))
-            participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
-        }
-
         roomPassword = extras?.getString(BundleKeys.KEY_CONVERSATION_PASSWORD).orEmpty()
 
         credentials = if (conversationUser?.userId == "?") {
@@ -455,6 +463,106 @@ class ChatActivity :
         active = false
     }
 
+    private fun initObservers() {
+        chatViewModel.getRoomViewState.observe(this) { state ->
+            when (state) {
+                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.GetRoomErrorState -> {
+                    Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
+                }
+
+                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 -> {}
+            }
+        }
+    }
+
     @Suppress("Detekt.TooGenericExceptionCaught")
     override fun onResume() {
         super.onResume()
@@ -484,18 +592,12 @@ class ChatActivity :
 
         cancelNotificationsForCurrentConversation()
 
-        if (TextUtils.isEmpty(roomToken)) {
-            handleFromNotification()
-        } else {
-            getRoomInfo()
-        }
+        chatViewModel.getRoom(conversationUser!!, roomToken)
 
         actionBar?.show()
 
         setupSwipeToReply()
 
-        layoutManager = binding?.messagesListView?.layoutManager as LinearLayoutManager?
-
         binding?.popupBubbleView?.setRecyclerView(binding?.messagesListView)
 
         binding?.popupBubbleView?.setPopupBubbleListener { context ->
@@ -632,10 +734,8 @@ class ChatActivity :
 
         binding?.messageInputView?.button?.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) }
 
-        if (currentConversation != null && currentConversation?.roomId != null) {
-            loadAvatarForStatusBar()
-            setActionBarTitle()
-        }
+        loadAvatarForStatusBar()
+        setActionBarTitle()
 
         viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar)
     }
@@ -694,8 +794,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,
@@ -1080,68 +1183,6 @@ 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<RoomOverall> {
-                    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 &&
             participantPermissions.hasChatPermission() &&
@@ -1162,46 +1203,11 @@ class ChatActivity :
         }
     }
 
-    private fun handleFromNotification() {
-        var apiVersion = 1
-        // FIXME Can this be called for guests?
-        if (conversationUser != null) {
-            apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+    private fun loadAvatarForStatusBar() {
+        if (currentConversation == null) {
+            return
         }
 
-        Log.d(TAG, "handleFromNotification - getRooms - calling")
-        ncApi.getRooms(credentials, ApiUtils.getUrlForRooms(apiVersion, conversationUser?.baseUrl), false)
-            ?.subscribeOn(Schedulers.io())?.observeOn(AndroidSchedulers.mainThread())
-            ?.subscribe(object : Observer<RoomsOverall> {
-                override fun onSubscribe(d: Disposable) {
-                    disposables.add(d)
-                }
-
-                override fun onNext(roomsOverall: RoomsOverall) {
-                    Log.d(TAG, "handleFromNotification - getRooms - got response")
-                    for (conversation in roomsOverall.ocs!!.data!!) {
-                        if (roomId == conversation.roomId) {
-                            roomToken = conversation.token!!
-                            currentConversation = conversation
-                            participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
-                            setActionBarTitle()
-                            getRoomInfo()
-                            break
-                        }
-                    }
-                }
-
-                override fun onError(e: Throwable) {
-                    Log.e(TAG, "handleFromNotification - getRooms - ERROR: ", e)
-                }
-
-                override fun onComplete() {
-                    // unused atm
-                }
-            })
-    }
-
-    private fun loadAvatarForStatusBar() {
         if (isOneToOneConversation()) {
             var url = ApiUtils.getUrlForAvatar(
                 conversationUser!!.baseUrl,
@@ -1254,18 +1260,18 @@ class ChatActivity :
     }
 
     fun isOneToOneConversation() = currentConversation != null && currentConversation?.type != null &&
-        currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+        currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
 
     private fun isGroupConversation() = currentConversation != null && currentConversation?.type != null &&
-        currentConversation?.type == Conversation.ConversationType.ROOM_GROUP_CALL
+        currentConversation?.type == ConversationType.ROOM_GROUP_CALL
 
     private fun isPublicConversation() = currentConversation != null && currentConversation?.type != null &&
-        currentConversation?.type == Conversation.ConversationType.ROOM_PUBLIC_CALL
+        currentConversation?.type == 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 == ObjectType.ROOM) {
                     Toast.makeText(
                         context,
                         context.resources.getString(R.string.switch_to_main_room),
@@ -1281,7 +1287,6 @@ class ChatActivity :
             }
 
             val bundle = Bundle()
-            bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
             bundle.putString(KEY_ROOM_TOKEN, token)
 
             if (startCallAfterRoomSwitch) {
@@ -1697,8 +1702,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 == LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
+                !ConversationUtils.canModerate(currentConversation!!, conversationUser!!) &&
                 !participantPermissions.canIgnoreLobby()
         }
         return false
@@ -1733,12 +1738,12 @@ class ChatActivity :
     private fun isReadOnlyConversation(): Boolean {
         return currentConversation?.conversationReadOnlyState != null &&
             currentConversation?.conversationReadOnlyState ==
-            Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY
+            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
@@ -2119,7 +2124,7 @@ class ChatActivity :
 
     private fun showConversationInfoScreen() {
         val bundle = Bundle()
-        bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
+
         bundle.putString(KEY_ROOM_TOKEN, roomToken)
         bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, isOneToOneConversation())
 
@@ -2274,65 +2279,8 @@ 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<RoomOverall> {
-                    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)
         } else {
             Log.d(TAG, "sessionID was valid -> skip joinRoom")
 
@@ -2801,9 +2749,9 @@ class ChatActivity :
                         GROUPED_MESSAGES_SAME_AUTHOR_THRESHOLD > 0
                     )
                 chatMessage.isOneToOneConversation =
-                    (currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL)
+                    (currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL)
                 chatMessage.isFormerOneToOneConversation =
-                    (currentConversation?.type == Conversation.ConversationType.FORMER_ONE_TO_ONE)
+                    (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
                 it.addToStart(chatMessage, shouldScroll)
             }
         }
@@ -2845,9 +2793,9 @@ class ChatActivity :
 
             val chatMessage = chatMessageList[i]
             chatMessage.isOneToOneConversation =
-                currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+                currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
             chatMessage.isFormerOneToOneConversation =
-                (currentConversation?.type == Conversation.ConversationType.FORMER_ONE_TO_ONE)
+                (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
             chatMessage.activeUser = conversationUser
         }
 
@@ -3032,10 +2980,9 @@ class ChatActivity :
         val intent = Intent(this, SharedItemsActivity::class.java)
         intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName)
         intent.putExtra(KEY_ROOM_TOKEN, roomToken)
-        intent.putExtra(KEY_USER_ENTITY, conversationUser as Parcelable)
         intent.putExtra(
             SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
-            currentConversation?.isParticipantOwnerOrModerator
+            ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!)
         )
         startActivity(intent)
     }
@@ -3119,12 +3066,11 @@ class ChatActivity :
             val bundle = Bundle()
             bundle.putString(KEY_ROOM_TOKEN, roomToken)
             bundle.putString(KEY_ROOM_ID, roomId)
-            bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
             bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
             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()
@@ -3141,7 +3087,7 @@ class ChatActivity :
                 bundle.putBoolean(BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION, true)
             }
 
-            if (it.objectType == Conversation.ObjectType.ROOM) {
+            if (it.objectType == ObjectType.ROOM) {
                 bundle.putBoolean(KEY_IS_BREAKOUT_ROOM, true)
             }
 
@@ -3156,12 +3102,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())
@@ -3171,7 +3117,7 @@ class ChatActivity :
     override fun onLongClickReactions(chatMessage: ChatMessage) {
         ShowReactionsDialog(
             this,
-            currentConversation,
+            roomToken,
             chatMessage,
             conversationUser,
             participantPermissions.hasChatPermission(),
@@ -3339,48 +3285,15 @@ class ChatActivity :
 
                 override fun onNext(roomOverall: RoomOverall) {
                     val bundle = Bundle()
-                    bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
                     bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
                     bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
 
-                    // FIXME once APIv2+ is used only, the createRoom already returns all the data
-                    ncApi.getRoom(
-                        credentials,
-                        ApiUtils.getUrlForRoom(
-                            apiVersion,
-                            conversationUser?.baseUrl,
-                            roomOverall.ocs!!.data!!.token
-                        )
-                    )
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(object : Observer<RoomOverall> {
-                            override fun onSubscribe(d: Disposable) {
-                                // unused atm
-                            }
-
-                            override fun onNext(roomOverall: RoomOverall) {
-                                bundle.putParcelable(
-                                    KEY_ACTIVE_CONVERSATION,
-                                    Parcels.wrap(roomOverall.ocs!!.data!!)
-                                )
-
-                                leaveRoom {
-                                    val chatIntent = Intent(context, ChatActivity::class.java)
-                                    chatIntent.putExtras(bundle)
-                                    chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                                    startActivity(chatIntent)
-                                }
-                            }
-
-                            override fun onError(e: Throwable) {
-                                Log.e(TAG, e.message, e)
-                            }
-
-                            override fun onComplete() {
-                                // unused atm
-                            }
-                        })
+                    leaveRoom {
+                        val chatIntent = Intent(context, ChatActivity::class.java)
+                        chatIntent.putExtras(bundle)
+                        chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                        startActivity(chatIntent)
+                    }
                 }
 
                 override fun onError(e: Throwable) {
@@ -3471,7 +3384,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 != 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
@@ -3540,7 +3453,7 @@ class ChatActivity :
         messageTemp.isDeleted = true
 
         messageTemp.isOneToOneConversation =
-            currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+            currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
         messageTemp.activeUser = conversationUser
 
         adapter?.update(messageTemp)
@@ -3550,7 +3463,7 @@ class ChatActivity :
         val messageTemp = message as ChatMessage
 
         messageTemp.isOneToOneConversation =
-            currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+            currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
         messageTemp.activeUser = conversationUser
 
         adapter?.update(messageTemp)
@@ -3598,7 +3511,7 @@ class ChatActivity :
         val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) {
             true
         } else {
-            currentConversation!!.canModerate(conversationUser!!)
+            ConversationUtils.canModerate(currentConversation!!, conversationUser!!)
         }
 
         val isOlderThanSixHours = message
@@ -3652,7 +3565,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 != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
             currentConversation?.name != userMentionClickEvent.userId
         ) {
             var apiVersion = 1
@@ -3683,42 +3596,21 @@ class ChatActivity :
                     }
 
                     override fun onNext(roomOverall: RoomOverall) {
-                        val conversationIntent = Intent(context, CallActivity::class.java)
                         val bundle = Bundle()
-                        bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
                         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)
-
-                        if (conversationUser != null) {
-                            bundle.putParcelable(
-                                KEY_ACTIVE_CONVERSATION,
-                                Parcels.wrap(roomOverall.ocs!!.data)
-                            )
-                            conversationIntent.putExtras(bundle)
-
-                            leaveRoom {
-                                val chatIntent = Intent(context, ChatActivity::class.java)
-                                chatIntent.putExtras(bundle)
-                                chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                                startActivity(chatIntent)
-                            }
-                        } else {
-                            conversationIntent.putExtras(bundle)
-                            startActivity(conversationIntent)
-                            Handler().postDelayed(
-                                {
-                                    if (!isDestroyed) {
-                                        finish()
-                                    }
-                                },
-                                POP_CURRENT_CONTROLLER_DELAY
-                            )
+
+                        leaveRoom {
+                            val chatIntent = Intent(context, ChatActivity::class.java)
+                            chatIntent.putExtras(bundle)
+                            chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                            startActivity(chatIntent)
                         }
                     }
 
                     override fun onError(e: Throwable) {
-                        // unused atm
+                        Log.e(TAG, "error after clicking on user mention chip", e)
+                        Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
                     }
 
                     override fun onComplete() {

+ 31 - 0
app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt

@@ -0,0 +1,31 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.chat.data
+
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
+
+import io.reactivex.Observable
+
+interface ChatRepository {
+    fun getRoom(user: User, roomToken: String): Observable<ConversationModel>
+    fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel>
+}

+ 57 - 0
app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt

@@ -0,0 +1,57 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.chat.data
+
+import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.utils.ApiUtils
+import io.reactivex.Observable
+
+class ChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
+    override fun getRoom(
+        user: User,
+        roomToken: String
+    ): Observable<ConversationModel> {
+        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, roomToken)
+        ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
+    }
+
+    override fun joinRoom(
+        user: User,
+        roomToken: String,
+        roomPassword: String
+    ): Observable<ConversationModel> {
+        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 { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
+    }
+}

+ 116 - 0
app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt

@@ -0,0 +1,116 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.chat.viewmodels
+
+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 com.nextcloud.talk.models.domain.ConversationModel
+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 ChatViewModel @Inject constructor(private val repository: ChatRepository) :
+    ViewModel() {
+
+    sealed interface ViewState
+
+    object GetRoomStartState : ViewState
+    object GetRoomErrorState : ViewState
+    open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
+
+    private val _getRoomViewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
+    val getRoomViewState: LiveData<ViewState>
+        get() = _getRoomViewState
+
+    object JoinRoomStartState : ViewState
+    object JoinRoomErrorState : ViewState
+    open class JoinRoomSuccessState(val conversationModel: ConversationModel) : ViewState
+
+    private val _joinRoomViewState: MutableLiveData<ViewState> = MutableLiveData(JoinRoomStartState)
+    val joinRoomViewState: LiveData<ViewState>
+        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())
+            ?.retry(JOIN_ROOM_RETRY_COUNT)
+            ?.subscribe(JoinRoomObserver())
+    }
+
+    inner class GetRoomObserver : Observer<ConversationModel> {
+        override fun onSubscribe(d: Disposable) {
+            // unused atm
+        }
+
+        override fun onNext(conversationModel: 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 JoinRoomObserver : Observer<ConversationModel> {
+        override fun onSubscribe(d: Disposable) {
+            // unused atm
+        }
+
+        override fun onNext(conversationModel: ConversationModel) {
+            _joinRoomViewState.value = JoinRoomSuccessState(conversationModel)
+        }
+
+        override fun onError(e: Throwable) {
+            Log.e(TAG, "Error when fetching room")
+            _joinRoomViewState.value = JoinRoomErrorState
+        }
+
+        override fun onComplete() {
+            // unused atm
+        }
+    }
+
+    companion object {
+        private val TAG = ChatViewModel::class.simpleName
+        const val JOIN_ROOM_RETRY_COUNT: Long = 3
+    }
+}

+ 4 - 42
app/src/main/java/com/nextcloud/talk/contacts/ContactsActivity.kt

@@ -338,46 +338,13 @@ class ContactsActivity :
 
                 override fun onNext(roomOverall: RoomOverall) {
                     val bundle = Bundle()
-                    bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser)
                     bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
                     bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
 
-                    // FIXME once APIv2 or later is used only, the createRoom already returns all the data
-                    ncApi.getRoom(
-                        credentials,
-                        ApiUtils.getUrlForRoom(
-                            apiVersion,
-                            currentUser!!.baseUrl,
-                            roomOverall.ocs!!.data!!.token
-                        )
-                    )
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(object : Observer<RoomOverall> {
-                            override fun onSubscribe(d: Disposable) {
-                                // unused atm
-                            }
-
-                            override fun onNext(roomOverall: RoomOverall) {
-                                bundle.putParcelable(
-                                    BundleKeys.KEY_ACTIVE_CONVERSATION,
-                                    Parcels.wrap(roomOverall.ocs!!.data!!)
-                                )
-
-                                val chatIntent = Intent(context, ChatActivity::class.java)
-                                chatIntent.putExtras(bundle)
-                                chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                                startActivity(chatIntent)
-                            }
-
-                            override fun onError(e: Throwable) {
-                                // unused atm
-                            }
-
-                            override fun onComplete() {
-                                // unused atm
-                            }
-                        })
+                    val chatIntent = Intent(context, ChatActivity::class.java)
+                    chatIntent.putExtras(bundle)
+                    chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                    startActivity(chatIntent)
                 }
 
                 override fun onError(e: Throwable) {
@@ -818,13 +785,8 @@ class ContactsActivity :
 
                 override fun onNext(roomOverall: RoomOverall) {
                     val bundle = Bundle()
-                    bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser)
                     bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
                     bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
-                    bundle.putParcelable(
-                        BundleKeys.KEY_ACTIVE_CONVERSATION,
-                        Parcels.wrap(roomOverall.ocs!!.data!!)
-                    )
 
                     val chatIntent = Intent(context, ChatActivity::class.java)
                     chatIntent.putExtras(bundle)

+ 127 - 81
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt

@@ -23,14 +23,13 @@
  */
 package com.nextcloud.talk.controllers.bottomsheet
 
-import android.content.Intent
 import android.content.res.ColorStateList
 import android.os.Bundle
-import android.os.Parcelable
 import android.text.Editable
 import android.text.InputType
 import android.text.TextUtils
 import android.text.TextWatcher
+import android.util.Log
 import android.view.View
 import android.view.inputmethod.EditorInfo
 import androidx.core.content.res.ResourcesCompat
@@ -40,21 +39,28 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
 import com.google.android.material.textfield.TextInputLayout
 import com.nextcloud.android.common.ui.theme.utils.ColorRole
 import com.nextcloud.talk.R
+import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
 import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.controllers.util.viewBinding
+import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ControllerEntryMenuBinding
 import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.RoomOverall
 import com.nextcloud.talk.users.UserManager
+import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.UriUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
 import com.vanniktech.emoji.EmojiPopup
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
 import okhttp3.internal.immutableListOf
 import org.greenrobot.eventbus.EventBus
-import org.parceler.Parcels
-import org.parceler.Parcels.unwrap
 import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
@@ -65,6 +71,9 @@ class EntryMenuController(args: Bundle) :
     ) {
     private val binding: ControllerEntryMenuBinding? by viewBinding(ControllerEntryMenuBinding::bind)
 
+    @Inject
+    lateinit var ncApi: NcApi
+
     @Inject
     lateinit var eventBus: EventBus
 
@@ -73,12 +82,13 @@ class EntryMenuController(args: Bundle) :
 
     private val operation: ConversationOperationEnum
     private var conversation: Conversation? = null
-    private var shareIntent: Intent? = null
     private val packageName: String
     private val name: String
-    private val callUrl: String
+
     private var emojiPopup: EmojiPopup? = null
     private val originalBundle: Bundle
+    private var currentUser: User? = null
+    private val roomToken: String
 
     override val appBarLayoutType: AppBarLayoutType
         get() = AppBarLayoutType.SEARCH_BAR
@@ -100,74 +110,121 @@ class EntryMenuController(args: Bundle) :
     override fun onViewBound(view: View) {
         super.onViewBound(view)
 
-        if (conversation != null && operation === ConversationOperationEnum.OPS_CODE_RENAME_ROOM) {
-            binding?.textEdit?.setText(conversation!!.name)
-        }
+        currentUser = userManager.currentUser.blockingGet()
 
-        binding?.textEdit?.setOnEditorActionListener { v, actionId, event ->
-            @Suppress("IMPLICIT_BOXING_IN_IDENTITY_EQUALS")
-            if (actionId === EditorInfo.IME_ACTION_DONE && binding?.okButton?.isEnabled == true) {
-                binding?.okButton?.callOnClick()
-                return@setOnEditorActionListener true
-            }
-            false
-        }
+        if (operation == ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM) {
+            var labelText = ""
+            labelText = resources!!.getString(R.string.nc_conversation_link)
+            binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
 
-        textEditAddChangedListener()
-
-        var labelText = ""
-        when (operation) {
-            ConversationOperationEnum.OPS_CODE_INVITE_USERS, ConversationOperationEnum.OPS_CODE_RENAME_ROOM -> {
-                labelText = resources!!.getString(R.string.nc_call_name)
-                binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT
-                binding?.smileyButton?.visibility = View.VISIBLE
-                emojiPopup = binding?.let {
-                    EmojiPopup(
-                        rootView = view,
-                        editText = it.textEdit,
-                        onEmojiPopupShownListener = {
-                            viewThemeUtils.platform.colorImageView(it.smileyButton, ColorRole.PRIMARY)
-                        },
-                        onEmojiPopupDismissListener = {
-                            it.smileyButton.imageTintList = ColorStateList.valueOf(
-                                ResourcesCompat.getColor(resources!!, R.color.medium_emphasis_text, context.theme)
-                            )
-                        },
-                        onEmojiClickListener = {
-                            binding?.textEdit?.editableText?.append(" ")
-                        }
-                    )
-                }
-            }
+            textEditAddChangedListener()
 
-            ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> {
-                // 99 is joining a conversation via password
-                labelText = resources!!.getString(R.string.nc_password)
-                binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
-            }
+            binding?.textInputLayout?.let { viewThemeUtils.material.colorTextInputLayout(it) }
+            binding?.okButton?.let { viewThemeUtils.material.colorMaterialButtonText(it) }
 
-            ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM -> {
-                labelText = resources!!.getString(R.string.nc_conversation_link)
-                binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
-            }
+            binding?.textInputLayout?.hint = labelText
+            binding?.textInputLayout?.requestFocus()
 
-            else -> {
-            }
-        }
-        if (PASSWORD_ENTRY_OPERATIONS.contains(operation)) {
-            binding?.textInputLayout?.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
+            binding?.smileyButton?.setOnClickListener { onSmileyClick() }
+            binding?.okButton?.setOnClickListener { onOkButtonClick() }
         } else {
-            binding?.textInputLayout?.endIconMode = TextInputLayout.END_ICON_NONE
-        }
+            val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
+            ncApi.getRoom(
+                ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
+                ApiUtils.getUrlForRoom(apiVersion, currentUser!!.baseUrl, roomToken)
+            )
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .retry(1)
+                .subscribe(object : Observer<RoomOverall> {
+                    override fun onSubscribe(d: Disposable) {
+                        // unused atm
+                    }
 
-        binding?.textInputLayout?.let { viewThemeUtils.material.colorTextInputLayout(it) }
-        binding?.okButton?.let { viewThemeUtils.material.colorMaterialButtonText(it) }
+                    @Suppress("Detekt.LongMethod")
+                    override fun onNext(roomOverall: RoomOverall) {
+                        conversation = roomOverall.ocs!!.data
+
+                        if (conversation != null && operation === ConversationOperationEnum.OPS_CODE_RENAME_ROOM) {
+                            binding?.textEdit?.setText(conversation!!.name)
+                        }
+
+                        binding?.textEdit?.setOnEditorActionListener { v, actionId, event ->
+                            @Suppress("IMPLICIT_BOXING_IN_IDENTITY_EQUALS")
+                            if (actionId === EditorInfo.IME_ACTION_DONE && binding?.okButton?.isEnabled == true) {
+                                binding?.okButton?.callOnClick()
+                                return@setOnEditorActionListener true
+                            }
+                            false
+                        }
 
-        binding?.textInputLayout?.hint = labelText
-        binding?.textInputLayout?.requestFocus()
+                        textEditAddChangedListener()
 
-        binding?.smileyButton?.setOnClickListener { onSmileyClick() }
-        binding?.okButton?.setOnClickListener { onOkButtonClick() }
+                        var labelText = ""
+                        when (operation) {
+                            ConversationOperationEnum.OPS_CODE_INVITE_USERS,
+                            ConversationOperationEnum.OPS_CODE_RENAME_ROOM -> {
+                                labelText = resources!!.getString(R.string.nc_call_name)
+                                binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT
+                                binding?.smileyButton?.visibility = View.VISIBLE
+                                emojiPopup = binding?.let {
+                                    EmojiPopup(
+                                        rootView = view,
+                                        editText = it.textEdit,
+                                        onEmojiPopupShownListener = {
+                                            viewThemeUtils.platform.colorImageView(it.smileyButton, ColorRole.PRIMARY)
+                                        },
+                                        onEmojiPopupDismissListener = {
+                                            it.smileyButton.imageTintList = ColorStateList.valueOf(
+                                                ResourcesCompat.getColor(
+                                                    resources!!,
+                                                    R.color.medium_emphasis_text,
+                                                    context.theme
+                                                )
+                                            )
+                                        },
+                                        onEmojiClickListener = {
+                                            binding?.textEdit?.editableText?.append(" ")
+                                        }
+                                    )
+                                }
+                            }
+
+                            ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> {
+                                // 99 is joining a conversation via password
+                                labelText = resources!!.getString(R.string.nc_password)
+                                binding?.textEdit?.inputType =
+                                    InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
+                            }
+
+                            else -> {
+                            }
+                        }
+                        if (PASSWORD_ENTRY_OPERATIONS.contains(operation)) {
+                            binding?.textInputLayout?.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
+                        } else {
+                            binding?.textInputLayout?.endIconMode = TextInputLayout.END_ICON_NONE
+                        }
+
+                        binding?.textInputLayout?.let { viewThemeUtils.material.colorTextInputLayout(it) }
+                        binding?.okButton?.let { viewThemeUtils.material.colorMaterialButtonText(it) }
+
+                        binding?.textInputLayout?.hint = labelText
+                        binding?.textInputLayout?.requestFocus()
+
+                        binding?.smileyButton?.setOnClickListener { onSmileyClick() }
+                        binding?.okButton?.setOnClickListener { onOkButtonClick() }
+                    }
+
+                    override fun onError(e: Throwable) {
+                        Log.e("EntryMenuController", "error")
+                    }
+
+                    override fun onComplete() {
+                        // unused atm
+                    }
+                })
+        }
     }
 
     private fun textEditAddChangedListener() {
@@ -242,7 +299,8 @@ class EntryMenuController(args: Bundle) :
         ) {
             val bundle = Bundle()
             conversation!!.name = binding?.textEdit?.text.toString()
-            bundle.putParcelable(BundleKeys.KEY_ROOM, Parcels.wrap<Any>(conversation))
+            bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
+            bundle.putString(BundleKeys.KEY_NEW_ROOM_NAME, binding?.textEdit?.text.toString())
             bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, operation)
             router.pushController(
                 RouterTransaction.with(OperationsMenuController(bundle))
@@ -274,16 +332,9 @@ class EntryMenuController(args: Bundle) :
 
     private fun joinRoom() {
         val bundle = Bundle()
-        bundle.putParcelable(BundleKeys.KEY_ROOM, Parcels.wrap<Any>(conversation))
-        bundle.putString(BundleKeys.KEY_CALL_URL, callUrl)
+        bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
         bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, binding?.textEdit?.text.toString())
         bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, operation)
-        if (originalBundle.containsKey(BundleKeys.KEY_SERVER_CAPABILITIES)) {
-            bundle.putParcelable(
-                BundleKeys.KEY_SERVER_CAPABILITIES,
-                originalBundle.getParcelable(BundleKeys.KEY_SERVER_CAPABILITIES)
-            )
-        }
         router.pushController(
             RouterTransaction.with(OperationsMenuController(bundle))
                 .pushChangeHandler(HorizontalChangeHandler())
@@ -296,15 +347,10 @@ class EntryMenuController(args: Bundle) :
 
         originalBundle = args
         operation = args.getSerializable(BundleKeys.KEY_OPERATION_CODE) as ConversationOperationEnum
-        if (args.containsKey(BundleKeys.KEY_ROOM)) {
-            conversation = unwrap<Conversation>(args.getParcelable<Parcelable>(BundleKeys.KEY_ROOM))
-        }
-        if (args.containsKey(BundleKeys.KEY_SHARE_INTENT)) {
-            shareIntent = unwrap<Intent>(args.getParcelable<Parcelable>(BundleKeys.KEY_SHARE_INTENT))
-        }
+        roomToken = args.getString(KEY_ROOM_TOKEN, "")
         name = args.getString(BundleKeys.KEY_APP_ITEM_NAME, "")
         packageName = args.getString(BundleKeys.KEY_APP_ITEM_PACKAGE_NAME, "")
-        callUrl = args.getString(BundleKeys.KEY_CALL_URL, "")
+        // callUrl = args.getString(BundleKeys.KEY_CALL_URL, "")
     }
 
     companion object {

+ 45 - 69
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt

@@ -21,8 +21,6 @@
  */
 package com.nextcloud.talk.controllers.bottomsheet
 
-import android.content.Intent
-import android.net.Uri
 import android.os.Bundle
 import android.text.TextUtils
 import android.util.Log
@@ -42,7 +40,6 @@ import com.nextcloud.talk.databinding.ControllerOperationsMenuBinding
 import com.nextcloud.talk.events.ConversationsListFetchDataEvent
 import com.nextcloud.talk.events.OpenConversationEvent
 import com.nextcloud.talk.models.RetrofitBucket
-import com.nextcloud.talk.models.json.capabilities.Capabilities
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
@@ -53,19 +50,17 @@ import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.NoSupportedApiException
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
+import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_URL
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TYPE
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INVITED_GROUP
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INVITED_PARTICIPANTS
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NEW_ROOM_NAME
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_OPERATION_CODE
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SERVER_CAPABILITIES
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
 import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
 import io.reactivex.Observer
@@ -100,13 +95,14 @@ class OperationsMenuController(args: Bundle) : BaseController(
     private var currentUser: User? = null
     private val callPassword: String
     private val callUrl: String
+    private var roomToken: String
+    private val roomNameNew: String
     private var baseUrl: String? = null
     private var conversationToken: String? = null
     private var disposable: Disposable? = null
     private var conversationType: ConversationType? = null
     private var invitedUsers: ArrayList<String>? = ArrayList()
     private var invitedGroups: ArrayList<String>? = ArrayList()
-    private var serverCapabilities: Capabilities? = null
     private var credentials: String? = null
     private val conversationName: String
 
@@ -128,39 +124,41 @@ class OperationsMenuController(args: Bundle) : BaseController(
                 baseUrl = callUrl.substring(0, callUrl.indexOf("/call"))
             }
         }
-        if (!TextUtils.isEmpty(baseUrl) && baseUrl != currentUser!!.baseUrl) {
-            if (serverCapabilities != null) {
-                try {
-                    useBundledCapabilitiesForGuest()
-                } catch (e: IOException) {
-                    // Fall back to fetching capabilities again
-                    fetchCapabilitiesForGuest()
-                }
-            } else {
-                fetchCapabilitiesForGuest()
-            }
-        } else {
-            processOperation()
-        }
-    }
 
-    @Throws(IOException::class)
-    private fun useBundledCapabilitiesForGuest() {
-        currentUser = User()
-        currentUser!!.baseUrl = baseUrl
-        currentUser!!.userId = "?"
-        try {
-            currentUser!!.capabilities = serverCapabilities
-        } catch (e: IOException) {
-            Log.e("OperationsMenu", "Failed to serialize capabilities")
-            throw e
-        }
-        try {
-            checkCapabilities(currentUser!!)
+        if (roomToken.isNotEmpty()) {
+            val apiVersion = apiVersion()
+            ncApi.getRoom(
+                credentials,
+                ApiUtils.getUrlForRoom(apiVersion, currentUser!!.baseUrl, roomToken)
+            )
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .retry(1)
+                .subscribe(object : Observer<RoomOverall> {
+                    override fun onSubscribe(d: Disposable) {
+                        disposable = d
+                    }
+
+                    override fun onNext(roomOverall: RoomOverall) {
+                        conversation = roomOverall.ocs!!.data
+
+                        if (!TextUtils.isEmpty(baseUrl) && baseUrl != currentUser!!.baseUrl) {
+                            fetchCapabilitiesForGuest()
+                        } else {
+                            processOperation()
+                        }
+                    }
+
+                    override fun onError(e: Throwable) {
+                        Log.e(TAG, "error while fetching room", e)
+                    }
+
+                    override fun onComplete() {
+                        // unused atm
+                    }
+                })
+        } else {
             processOperation()
-        } catch (e: NoSupportedApiException) {
-            showResultImage(everythingOK = false, isGuestSupportError = false)
-            Log.d(TAG, "No supported server version found", e)
         }
     }
 
@@ -218,6 +216,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
             ConversationOperationEnum.OPS_CODE_MARK_AS_UNREAD -> operationMarkAsUnread()
             ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE,
             ConversationOperationEnum.OPS_CODE_ADD_FAVORITE -> operationToggleFavorite()
+
             ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> operationJoinRoom()
             else -> {
             }
@@ -287,7 +286,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
                 currentUser!!.baseUrl,
                 conversation!!.token
             ),
-            conversation!!.name
+            roomNameNew
         )
             .subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread())
@@ -427,17 +426,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
                     if (conversation!!.hasPassword && conversation!!.isGuest) {
                         eventBus.post(ConversationsListFetchDataEvent())
                         val bundle = Bundle()
-                        bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
-                        bundle.putString(KEY_CALL_URL, callUrl)
-                        try {
-                            bundle.putParcelable(
-                                KEY_SERVER_CAPABILITIES,
-                                Parcels.wrap<Capabilities>(currentUser!!.capabilities)
-                            )
-                        } catch (e: IOException) {
-                            Log.e(TAG, "Failed to parse capabilities for guest")
-                            showResultImage(everythingOK = false, isGuestSupportError = false)
-                        }
+                        bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
                         bundle.putSerializable(KEY_OPERATION_CODE, ConversationOperationEnum.OPS_CODE_JOIN_ROOM)
                         router.pushController(
                             RouterTransaction.with(EntryMenuController(bundle))
@@ -519,16 +508,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
                 binding?.resultTextView?.setText(R.string.nc_all_ok_operation)
             } else {
                 binding?.resultTextView?.setTextColor(resources!!.getColor(R.color.nc_darkRed, null))
-                if (!isGuestSupportError) {
-                    binding?.resultTextView?.setText(R.string.nc_failed_to_perform_operation)
-                } else {
-                    binding?.resultTextView?.setText(R.string.nc_failed_signaling_settings)
-                    binding?.webButton?.setOnClickListener {
-                        val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(callUrl))
-                        startActivity(browserIntent)
-                    }
-                    binding?.webButton?.visibility = View.VISIBLE
-                }
+                binding?.resultTextView?.setText(R.string.nc_failed_to_perform_operation)
             }
             binding?.resultTextView?.visibility = View.VISIBLE
             if (everythingOK) {
@@ -608,6 +588,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
                     override fun onSubscribe(d: Disposable) {
                         // unused atm
                     }
+
                     override fun onNext(addParticipantOverall: AddParticipantOverall) {
                         // unused atm
                     }
@@ -653,6 +634,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
                         override fun onSubscribe(d: Disposable) {
                             // unused atm
                         }
+
                         override fun onNext(addParticipantOverall: AddParticipantOverall) {
                             // unused atm
                         }
@@ -679,8 +661,6 @@ class OperationsMenuController(args: Bundle) : BaseController(
         bundle.putString(KEY_ROOM_TOKEN, conversation!!.token)
         bundle.putString(KEY_ROOM_ID, conversation!!.roomId)
         bundle.putString(KEY_CONVERSATION_NAME, conversation!!.displayName)
-        bundle.putParcelable(KEY_USER_ENTITY, currentUser)
-        bundle.putParcelable(KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
         bundle.putString(KEY_CONVERSATION_PASSWORD, callPassword)
         eventBus.post(OpenConversationEvent(conversation, bundle))
     }
@@ -755,11 +735,10 @@ class OperationsMenuController(args: Bundle) : BaseController(
 
     init {
         operation = args.getSerializable(KEY_OPERATION_CODE) as ConversationOperationEnum?
-        if (args.containsKey(KEY_ROOM)) {
-            conversation = Parcels.unwrap(args.getParcelable(KEY_ROOM))
-        }
         callPassword = args.getString(KEY_CONVERSATION_PASSWORD, "")
         callUrl = args.getString(KEY_CALL_URL, "")
+        roomToken = args.getString(KEY_ROOM_TOKEN, "")
+        roomNameNew = args.getString(KEY_NEW_ROOM_NAME, "")
         if (args.containsKey(KEY_INVITED_PARTICIPANTS)) {
             invitedUsers = args.getStringArrayList(KEY_INVITED_PARTICIPANTS)
         }
@@ -769,9 +748,6 @@ class OperationsMenuController(args: Bundle) : BaseController(
         if (args.containsKey(KEY_CONVERSATION_TYPE)) {
             conversationType = Parcels.unwrap(args.getParcelable(KEY_CONVERSATION_TYPE))
         }
-        if (args.containsKey(KEY_SERVER_CAPABILITIES)) {
-            serverCapabilities = Parcels.unwrap(args.getParcelable(KEY_SERVER_CAPABILITIES))
-        }
         conversationName = args.getString(KEY_CONVERSATION_NAME, "")
     }
 }

+ 6 - 9
app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt

@@ -32,7 +32,6 @@ import android.annotation.SuppressLint
 import android.content.Intent
 import android.graphics.drawable.ColorDrawable
 import android.os.Bundle
-import android.os.Parcelable
 import android.text.TextUtils
 import android.util.Log
 import android.view.Menu
@@ -86,6 +85,7 @@ import com.nextcloud.talk.utils.DateConstants
 import com.nextcloud.talk.utils.DateUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
@@ -95,7 +95,6 @@ import io.reactivex.disposables.Disposable
 import io.reactivex.schedulers.Schedulers
 import org.greenrobot.eventbus.Subscribe
 import org.greenrobot.eventbus.ThreadMode
-import org.parceler.Parcels
 import java.util.Calendar
 import java.util.Collections
 import java.util.Locale
@@ -110,6 +109,9 @@ class ConversationInfoActivity :
     @Inject
     lateinit var ncApi: NcApi
 
+    @Inject
+    lateinit var currentUserProvider: CurrentUserProviderNew
+
     @Inject
     lateinit var conversationsRepository: ConversationsRepository
 
@@ -152,7 +154,8 @@ class ConversationInfoActivity :
         setContentView(binding.root)
         setupSystemColors()
 
-        conversationUser = intent.getParcelableExtra(BundleKeys.KEY_USER_ENTITY)!!
+        conversationUser = currentUserProvider.currentUser.blockingGet()
+
         conversationToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
         hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
         credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
@@ -225,11 +228,6 @@ class ConversationInfoActivity :
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
         if (item.itemId == R.id.edit) {
             val bundle = Bundle()
-            bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
-            bundle.putParcelable(
-                BundleKeys.KEY_ACTIVE_CONVERSATION,
-                Parcels.wrap(conversation)
-            )
             bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
 
             val intent = Intent(this, ConversationInfoEditActivity::class.java)
@@ -270,7 +268,6 @@ class ConversationInfoActivity :
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
         intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
         intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
-        intent.putExtra(BundleKeys.KEY_USER_ENTITY, conversationUser as Parcelable)
         intent.putExtra(SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR, conversation?.isParticipantOwnerOrModerator)
         startActivity(intent)
     }

+ 69 - 100
app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt

@@ -34,34 +34,31 @@ import android.view.View
 import android.widget.Toast
 import androidx.core.net.toFile
 import androidx.core.view.ViewCompat
+import androidx.lifecycle.ViewModelProvider
 import autodagger.AutoInjector
 import com.github.dhaval2404.imagepicker.ImagePicker
 import com.nextcloud.talk.R
 import com.nextcloud.talk.activities.BaseActivity
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ActivityConversationInfoEditBinding
 import com.nextcloud.talk.extensions.loadConversationAvatar
 import com.nextcloud.talk.extensions.loadSystemAvatar
 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.domain.ConversationModel
+import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.json.generic.GenericOverall
-import com.nextcloud.talk.repositories.conversations.ConversationsRepository
 import com.nextcloud.talk.utils.ApiUtils
-import com.nextcloud.talk.utils.Mimetype
 import com.nextcloud.talk.utils.PickImage
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+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 okhttp3.MediaType.Companion.toMediaTypeOrNull
-import okhttp3.MultipartBody
-import okhttp3.RequestBody.Companion.asRequestBody
-import org.parceler.Parcels
 import java.io.File
 import javax.inject.Inject
 
@@ -75,13 +72,18 @@ class ConversationInfoEditActivity :
     lateinit var ncApi: NcApi
 
     @Inject
-    lateinit var conversationsRepository: ConversationsRepository
+    lateinit var currentUserProvider: CurrentUserProviderNew
 
-    private lateinit var conversationToken: String
+    @Inject
+    lateinit var viewModelFactory: ViewModelProvider.Factory
+
+    lateinit var conversationInfoEditViewModel: ConversationInfoEditViewModel
+
+    private lateinit var roomToken: String
     private lateinit var conversationUser: User
     private lateinit var credentials: String
 
-    private var conversation: Conversation? = null
+    private var conversation: ConversationModel? = null
 
     private lateinit var pickImage: PickImage
 
@@ -96,12 +98,14 @@ class ConversationInfoEditActivity :
 
         val extras: Bundle? = intent.extras
 
-        conversationUser = extras?.getParcelable(BundleKeys.KEY_USER_ENTITY)!!
-        conversationToken = extras.getString(BundleKeys.KEY_ROOM_TOKEN)!!
+        conversationUser = currentUserProvider.currentUser.blockingGet()
 
-        if (conversation == null && intent.hasExtra(BundleKeys.KEY_ACTIVE_CONVERSATION)) {
-            conversation = Parcels.unwrap<Conversation>(extras.getParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION))
-        }
+        roomToken = extras?.getString(BundleKeys.KEY_ROOM_TOKEN)!!
+
+        conversationInfoEditViewModel =
+            ViewModelProvider(this, viewModelFactory)[ConversationInfoEditViewModel::class.java]
+
+        conversationInfoEditViewModel.getRoom(conversationUser, roomToken)
 
         viewThemeUtils.material.colorTextInputLayout(binding.conversationNameInputLayout)
         viewThemeUtils.material.colorTextInputLayout(binding.conversationDescriptionInputLayout)
@@ -109,21 +113,57 @@ class ConversationInfoEditActivity :
         credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
 
         pickImage = PickImage(this, conversationUser)
+
+        initObservers()
     }
 
     override fun onResume() {
         super.onResume()
+    }
 
-        loadConversationAvatar()
+    private fun initObservers() {
+        conversationInfoEditViewModel.viewState.observe(this) { state ->
+            when (state) {
+                is ConversationInfoEditViewModel.GetRoomSuccessState -> {
+                    conversation = state.conversationModel
 
-        binding.conversationName.setText(conversation!!.displayName)
+                    binding.conversationName.setText(conversation!!.displayName)
 
-        if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
-            binding.conversationDescription.setText(conversation!!.description)
-        }
+                    if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
+                        binding.conversationDescription.setText(conversation!!.description)
+                    }
+
+                    if (!CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
+                        binding.conversationDescription.isEnabled = false
+                    }
+
+                    loadConversationAvatar()
+                }
+
+                is ConversationInfoEditViewModel.GetRoomErrorState -> {
+                    Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
+                }
+
+                is ConversationInfoEditViewModel.UploadAvatarSuccessState -> {
+                    conversation = state.conversationModel
+                    loadConversationAvatar()
+                }
 
-        if (!CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
-            binding.conversationDescription.isEnabled = false
+                is ConversationInfoEditViewModel.UploadAvatarErrorState -> {
+                    Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
+                }
+
+                is ConversationInfoEditViewModel.DeleteAvatarSuccessState -> {
+                    conversation = state.conversationModel
+                    loadConversationAvatar()
+                }
+
+                is ConversationInfoEditViewModel.DeleteAvatarErrorState -> {
+                    Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
+                }
+
+                else -> {}
+            }
         }
     }
 
@@ -284,98 +324,27 @@ class ConversationInfoEditActivity :
         }
     }
 
-    private fun uploadAvatar(file: File?) {
-        val builder = MultipartBody.Builder()
-        builder.setType(MultipartBody.FORM)
-        builder.addFormDataPart(
-            "file",
-            file!!.name,
-            file.asRequestBody(Mimetype.IMAGE_PREFIX_GENERIC.toMediaTypeOrNull())
-        )
-        val filePart: MultipartBody.Part = MultipartBody.Part.createFormData(
-            "file",
-            file.name,
-            file.asRequestBody(Mimetype.IMAGE_JPG.toMediaTypeOrNull())
-        )
-
-        // upload file
-        ncApi.uploadConversationAvatar(
-            credentials,
-            ApiUtils.getUrlForConversationAvatar(1, conversationUser.baseUrl, conversation!!.token),
-            filePart
-        )
-            .subscribeOn(Schedulers.io())
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribe(object : Observer<RoomOverall> {
-                override fun onSubscribe(d: Disposable) {
-                    // unused atm
-                }
-
-                override fun onNext(roomOverall: RoomOverall) {
-                    conversation = roomOverall.ocs!!.data
-                    loadConversationAvatar()
-                }
-
-                override fun onError(e: Throwable) {
-                    Toast.makeText(
-                        applicationContext,
-                        context.getString(R.string.default_error_msg),
-                        Toast.LENGTH_LONG
-                    ).show()
-                    Log.e(TAG, "Error uploading avatar", e)
-                }
-
-                override fun onComplete() {
-                    // unused atm
-                }
-            })
+    private fun uploadAvatar(file: File) {
+        conversationInfoEditViewModel.uploadConversationAvatar(conversationUser, file, roomToken)
     }
 
     private fun deleteAvatar() {
-        ncApi.deleteConversationAvatar(
-            credentials,
-            ApiUtils.getUrlForConversationAvatar(1, conversationUser.baseUrl, conversationToken)
-        )
-            .subscribeOn(Schedulers.io())
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribe(object : Observer<RoomOverall> {
-                override fun onSubscribe(d: Disposable) {
-                    // unused atm
-                }
-
-                override fun onNext(roomOverall: RoomOverall) {
-                    conversation = roomOverall.ocs!!.data
-                    loadConversationAvatar()
-                }
-
-                override fun onError(e: Throwable) {
-                    Toast.makeText(
-                        applicationContext,
-                        context.getString(R.string.default_error_msg),
-                        Toast.LENGTH_LONG
-                    ).show()
-                    Log.e(TAG, "Failed to delete avatar", e)
-                }
-
-                override fun onComplete() {
-                    // unused atm
-                }
-            })
+        conversationInfoEditViewModel.deleteConversationAvatar(conversationUser, roomToken)
     }
 
     private fun loadConversationAvatar() {
         setupAvatarOptions()
 
         when (conversation!!.type) {
-            Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
+            ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
                 conversation!!.name?.let { binding.avatarImage.loadUserAvatar(conversationUser, it, true, false) }
             }
 
-            Conversation.ConversationType.ROOM_GROUP_CALL, Conversation.ConversationType.ROOM_PUBLIC_CALL -> {
+            ConversationType.ROOM_GROUP_CALL, ConversationType.ROOM_PUBLIC_CALL -> {
                 binding.avatarImage.loadConversationAvatar(conversationUser, conversation!!, false, viewThemeUtils)
             }
 
-            Conversation.ConversationType.ROOM_SYSTEM -> {
+            ConversationType.ROOM_SYSTEM -> {
                 binding.avatarImage.loadSystemAvatar()
             }
 

+ 33 - 0
app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepository.kt

@@ -0,0 +1,33 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.conversationinfoedit.data
+
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
+import io.reactivex.Observable
+import java.io.File
+
+interface ConversationInfoEditRepository {
+
+    fun uploadConversationAvatar(user: User, file: File, roomToken: String): Observable<ConversationModel>
+
+    fun deleteConversationAvatar(user: User, roomToken: String): Observable<ConversationModel>
+}

+ 70 - 0
app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepositoryImpl.kt

@@ -0,0 +1,70 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.conversationinfoedit.data
+
+import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.Mimetype
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
+import io.reactivex.Observable
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import java.io.File
+
+class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserProvider: CurrentUserProviderNew) :
+    ConversationInfoEditRepository {
+
+    val currentUser: User = currentUserProvider.currentUser.blockingGet()
+    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+
+    val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
+
+    override fun uploadConversationAvatar(user: User, file: File, roomToken: String): Observable<ConversationModel> {
+        val builder = MultipartBody.Builder()
+        builder.setType(MultipartBody.FORM)
+        builder.addFormDataPart(
+            "file",
+            file!!.name,
+            file.asRequestBody(Mimetype.IMAGE_PREFIX_GENERIC.toMediaTypeOrNull())
+        )
+        val filePart: MultipartBody.Part = MultipartBody.Part.createFormData(
+            "file",
+            file.name,
+            file.asRequestBody(Mimetype.IMAGE_JPG.toMediaTypeOrNull())
+        )
+
+        return ncApi.uploadConversationAvatar(
+            credentials,
+            ApiUtils.getUrlForConversationAvatar(1, user.baseUrl, roomToken),
+            filePart
+        ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
+    }
+
+    override fun deleteConversationAvatar(user: User, roomToken: String): Observable<ConversationModel> {
+        return ncApi.deleteConversationAvatar(
+            credentials,
+            ApiUtils.getUrlForConversationAvatar(1, user.baseUrl, roomToken)
+        ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
+    }
+}

+ 141 - 0
app/src/main/java/com/nextcloud/talk/conversationinfoedit/viewmodel/ConversationInfoEditViewModel.kt

@@ -0,0 +1,141 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.conversationinfoedit.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.conversationinfoedit.data.ConversationInfoEditRepository
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import java.io.File
+import javax.inject.Inject
+
+class ConversationInfoEditViewModel @Inject constructor(
+    private val repository: ChatRepository,
+    private val conversationInfoEditRepository: ConversationInfoEditRepository
+) : ViewModel() {
+
+    sealed interface ViewState
+
+    object GetRoomStartState : ViewState
+    object GetRoomErrorState : ViewState
+    open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
+
+    object UploadAvatarErrorState : ViewState
+    open class UploadAvatarSuccessState(val conversationModel: ConversationModel) : ViewState
+
+    object DeleteAvatarErrorState : ViewState
+    open class DeleteAvatarSuccessState(val conversationModel: ConversationModel) : ViewState
+
+    private val _viewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
+    val viewState: LiveData<ViewState>
+        get() = _viewState
+
+    fun getRoom(user: User, token: String) {
+        _viewState.value = GetRoomStartState
+        repository.getRoom(user, token)
+            .subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(GetRoomObserver())
+    }
+
+    fun uploadConversationAvatar(user: User, file: File, roomToken: String) {
+        conversationInfoEditRepository.uploadConversationAvatar(user, file, roomToken)
+            .subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(UploadConversationAvatarObserver())
+    }
+
+    fun deleteConversationAvatar(user: User, roomToken: String) {
+        conversationInfoEditRepository.deleteConversationAvatar(user, roomToken)
+            .subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(DeleteConversationAvatarObserver())
+    }
+
+    inner class GetRoomObserver : Observer<ConversationModel> {
+        override fun onSubscribe(d: Disposable) {
+            // unused atm
+        }
+
+        override fun onNext(conversationModel: ConversationModel) {
+            _viewState.value = GetRoomSuccessState(conversationModel)
+        }
+
+        override fun onError(e: Throwable) {
+            Log.e(TAG, "Error when fetching room")
+            _viewState.value = GetRoomErrorState
+        }
+
+        override fun onComplete() {
+            // unused atm
+        }
+    }
+
+    inner class UploadConversationAvatarObserver : Observer<ConversationModel> {
+        override fun onSubscribe(d: Disposable) {
+            // unused atm
+        }
+
+        override fun onNext(conversationModel: ConversationModel) {
+            _viewState.value = UploadAvatarSuccessState(conversationModel)
+        }
+
+        override fun onError(e: Throwable) {
+            Log.e(TAG, "Error when uploading avatar")
+            _viewState.value = UploadAvatarErrorState
+        }
+
+        override fun onComplete() {
+            // unused atm
+        }
+    }
+
+    inner class DeleteConversationAvatarObserver : Observer<ConversationModel> {
+        override fun onSubscribe(d: Disposable) {
+            // unused atm
+        }
+
+        override fun onNext(conversationModel: ConversationModel) {
+            _viewState.value = DeleteAvatarSuccessState(conversationModel)
+        }
+
+        override fun onError(e: Throwable) {
+            Log.e(TAG, "Error when deleting avatar")
+            _viewState.value = DeleteAvatarErrorState
+        }
+
+        override fun onComplete() {
+            // unused atm
+        }
+    }
+
+    companion object {
+        private val TAG = ConversationInfoEditViewModel::class.simpleName
+    }
+}

+ 27 - 37
app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt

@@ -106,17 +106,14 @@ import com.nextcloud.talk.utils.FileUtils
 import com.nextcloud.talk.utils.Mimetype
 import com.nextcloud.talk.utils.ParticipantPermissions
 import com.nextcloud.talk.utils.bundle.BundleKeys
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NEW_CONVERSATION
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARED_TEXT
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isServerEOL
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUnifiedSearchAvailable
@@ -134,7 +131,6 @@ import io.reactivex.schedulers.Schedulers
 import org.apache.commons.lang3.builder.CompareToBuilder
 import org.greenrobot.eventbus.Subscribe
 import org.greenrobot.eventbus.ThreadMode
-import org.parceler.Parcels
 import retrofit2.HttpException
 import java.util.Objects
 import java.util.concurrent.TimeUnit
@@ -1074,7 +1070,6 @@ class ConversationsListActivity :
             if (clickedItem != null) {
                 val conversation = (clickedItem as ConversationItem).model
                 conversationsListBottomDialog = ConversationsListBottomDialog(
-                    this,
                     this,
                     userManager.currentUser.blockingGet(),
                     conversation
@@ -1185,8 +1180,6 @@ class ConversationsListActivity :
         }
 
         val bundle = Bundle()
-        bundle.putParcelable(KEY_USER_ENTITY, currentUser)
-        bundle.putParcelable(KEY_ACTIVE_CONVERSATION, Parcels.wrap(selectedConversation))
         bundle.putString(KEY_ROOM_TOKEN, selectedConversation!!.token)
         bundle.putString(KEY_ROOM_ID, selectedConversation!!.roomId)
         bundle.putString(KEY_SHARED_TEXT, textToPaste)
@@ -1229,38 +1222,35 @@ class ConversationsListActivity :
         if (conversationMenuBundle != null &&
             isInternalUserEqualsCurrentUser(currentUser, conversationMenuBundle)
         ) {
-            val conversation = Parcels.unwrap<Conversation>(conversationMenuBundle!!.getParcelable(KEY_ROOM))
-            if (conversation != null) {
-                binding?.floatingActionButton?.let {
-                    val dialogBuilder = MaterialAlertDialogBuilder(it.context)
-                        .setIcon(
-                            viewThemeUtils.dialog
-                                .colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp)
+            binding?.floatingActionButton?.let {
+                val dialogBuilder = MaterialAlertDialogBuilder(it.context)
+                    .setIcon(
+                        viewThemeUtils.dialog
+                            .colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp)
+                    )
+                    .setTitle(R.string.nc_delete_call)
+                    .setMessage(R.string.nc_delete_conversation_more)
+                    .setPositiveButton(R.string.nc_delete) { _, _ ->
+                        val data = Data.Builder()
+                        data.putLong(
+                            KEY_INTERNAL_USER_ID,
+                            conversationMenuBundle!!.getLong(KEY_INTERNAL_USER_ID)
                         )
-                        .setTitle(R.string.nc_delete_call)
-                        .setMessage(R.string.nc_delete_conversation_more)
-                        .setPositiveButton(R.string.nc_delete) { _, _ ->
-                            val data = Data.Builder()
-                            data.putLong(
-                                KEY_INTERNAL_USER_ID,
-                                conversationMenuBundle!!.getLong(KEY_INTERNAL_USER_ID)
-                            )
-                            data.putString(KEY_ROOM_TOKEN, conversation.token)
-                            conversationMenuBundle = null
-                            deleteConversation(data.build())
-                        }
-                        .setNegativeButton(R.string.nc_cancel) { _, _ ->
-                            conversationMenuBundle = null
-                        }
+                        data.putString(KEY_ROOM_TOKEN, bundle.getString(KEY_ROOM_TOKEN))
+                        conversationMenuBundle = null
+                        deleteConversation(data.build())
+                    }
+                    .setNegativeButton(R.string.nc_cancel) { _, _ ->
+                        conversationMenuBundle = null
+                    }
 
-                    viewThemeUtils.dialog
-                        .colorMaterialAlertDialogBackground(it.context, dialogBuilder)
-                    val dialog = dialogBuilder.show()
-                    viewThemeUtils.platform.colorTextButtons(
-                        dialog.getButton(AlertDialog.BUTTON_POSITIVE),
-                        dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
-                    )
-                }
+                viewThemeUtils.dialog
+                    .colorMaterialAlertDialogBackground(it.context, dialogBuilder)
+                val dialog = dialogBuilder.show()
+                viewThemeUtils.platform.colorTextButtons(
+                    dialog.getButton(AlertDialog.BUTTON_POSITIVE),
+                    dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
+                )
             }
         }
     }

+ 16 - 0
app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt

@@ -26,6 +26,10 @@
 package com.nextcloud.talk.dagger.modules
 
 import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.chat.data.ChatRepository
+import com.nextcloud.talk.chat.data.ChatRepositoryImpl
+import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepository
+import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepositoryImpl
 import com.nextcloud.talk.data.source.local.TalkDatabase
 import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
 import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
@@ -123,4 +127,16 @@ class RepositoryModule {
         TranslateRepository {
         return TranslateRepositoryImpl(ncApi)
     }
+
+    @Provides
+    fun provideChatRepository(ncApi: NcApi):
+        ChatRepository {
+        return ChatRepositoryImpl(ncApi)
+    }
+
+    @Provides
+    fun provideConversationInfoEditRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew):
+        ConversationInfoEditRepository {
+        return ConversationInfoEditRepositoryImpl(ncApi, userProvider)
+    }
 }

+ 19 - 1
app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt

@@ -23,6 +23,9 @@ package com.nextcloud.talk.dagger.modules
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
+import com.nextcloud.talk.callnotification.viewmodel.CallNotificationViewModel
+import com.nextcloud.talk.chat.viewmodels.ChatViewModel
+import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
 import com.nextcloud.talk.messagesearch.MessageSearchViewModel
 import com.nextcloud.talk.openconversations.viewmodels.OpenConversationsViewModel
 import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel
@@ -112,5 +115,20 @@ abstract class ViewModelModule {
     @Binds
     @IntoMap
     @ViewModelKey(OpenConversationsViewModel::class)
-    abstract fun openConversationsViewModelModel(viewModel: OpenConversationsViewModel): ViewModel
+    abstract fun openConversationsViewModel(viewModel: OpenConversationsViewModel): ViewModel
+
+    @Binds
+    @IntoMap
+    @ViewModelKey(ChatViewModel::class)
+    abstract fun chatViewModel(viewModel: ChatViewModel): ViewModel
+
+    @Binds
+    @IntoMap
+    @ViewModelKey(CallNotificationViewModel::class)
+    abstract fun callNotificationViewModel(viewModel: CallNotificationViewModel): ViewModel
+
+    @Binds
+    @IntoMap
+    @ViewModelKey(ConversationInfoEditViewModel::class)
+    abstract fun conversationInfoEditViewModel(viewModel: ConversationInfoEditViewModel): ViewModel
 }

+ 1 - 0
app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt

@@ -94,6 +94,7 @@ abstract class TalkDatabase : RoomDatabase() {
 
             return Room
                 .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName)
+                // comment out openHelperFactory to view the database entries in Android Studio for debugging
                 .openHelperFactory(factory)
                 .addMigrations(Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8)
                 .allowMainThreadQueries()

+ 21 - 4
app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt

@@ -44,6 +44,8 @@ import coil.transform.RoundedCornersTransformation
 import com.amulyakhare.textdrawable.TextDrawable
 import com.nextcloud.talk.R
 import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.utils.ApiUtils
@@ -52,11 +54,26 @@ import com.nextcloud.talk.utils.DisplayUtils
 private const val ROUNDING_PIXEL = 16f
 private const val TAG = "ImageViewExtensions"
 
+@Deprecated("use other constructor that expects com.nextcloud.talk.models.domain.ConversationModel")
 fun ImageView.loadConversationAvatar(
     user: User,
     conversation: Conversation,
     ignoreCache: Boolean,
     viewThemeUtils: ViewThemeUtils?
+): io.reactivex.disposables.Disposable {
+    return loadConversationAvatar(
+        user,
+        ConversationModel.mapToConversationModel(conversation),
+        ignoreCache,
+        viewThemeUtils
+    )
+}
+
+fun ImageView.loadConversationAvatar(
+    user: User,
+    conversation: ConversationModel,
+    ignoreCache: Boolean,
+    viewThemeUtils: ViewThemeUtils?
 ): io.reactivex.disposables.Disposable {
     val imageRequestUri = ApiUtils.getUrlForConversationAvatarWithVersion(
         1,
@@ -68,10 +85,10 @@ fun ImageView.loadConversationAvatar(
 
     if (conversation.avatarVersion.isNullOrEmpty() && viewThemeUtils != null) {
         when (conversation.type) {
-            Conversation.ConversationType.ROOM_GROUP_CALL ->
+            ConversationType.ROOM_GROUP_CALL ->
                 return loadDefaultGroupCallAvatar(viewThemeUtils)
 
-            Conversation.ConversationType.ROOM_PUBLIC_CALL ->
+            ConversationType.ROOM_PUBLIC_CALL ->
                 return loadDefaultPublicCallAvatar(viewThemeUtils)
 
             else -> {}
@@ -82,10 +99,10 @@ fun ImageView.loadConversationAvatar(
     // when no own images are set. (although these default avatars can not be themed for the android app..)
     val errorPlaceholder =
         when (conversation.type) {
-            Conversation.ConversationType.ROOM_GROUP_CALL ->
+            ConversationType.ROOM_GROUP_CALL ->
                 ContextCompat.getDrawable(context, R.drawable.ic_circular_group)
 
-            Conversation.ConversationType.ROOM_PUBLIC_CALL ->
+            ConversationType.ROOM_PUBLIC_CALL ->
                 ContextCompat.getDrawable(context, R.drawable.ic_circular_link)
 
             else -> ContextCompat.getDrawable(context, R.drawable.account_circle_96dp)

+ 4 - 6
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
@@ -94,7 +94,6 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_TIMESTAMP
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SYSTEM_NOTIFICATION_ID
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
 import com.nextcloud.talk.utils.preferences.AppPreferences
 import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
 import io.reactivex.Observable
@@ -197,7 +196,7 @@ 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.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
@@ -680,7 +679,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
         shareRecordingIntent.putExtra(KEY_SYSTEM_NOTIFICATION_ID, systemNotificationId)
         shareRecordingIntent.putExtra(KEY_SHARE_RECORDING_TO_CHAT_URL, shareToChatUrl)
         shareRecordingIntent.putExtra(KEY_ROOM_TOKEN, pushMessage.id)
-        shareRecordingIntent.putExtra(KEY_USER_ENTITY, signatureVerification.user)
 
         val intentFlag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
@@ -924,7 +922,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
         intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
         val bundle = Bundle()
         bundle.putString(KEY_ROOM_TOKEN, pushMessage.id)
-        bundle.putParcelable(KEY_USER_ENTITY, signatureVerification.user)
+        bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
         bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
         intent.putExtras(bundle)
         return intent
@@ -935,7 +933,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
         intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
         val bundle = Bundle()
         bundle.putString(KEY_ROOM_TOKEN, pushMessage.id)
-        bundle.putParcelable(KEY_USER_ENTITY, signatureVerification.user)
+        bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
         bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
         intent.putExtras(bundle)
 

+ 2 - 2
app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt

@@ -53,8 +53,8 @@ import com.nextcloud.talk.utils.FileUtils
 import com.nextcloud.talk.utils.NotificationUtils
 import com.nextcloud.talk.utils.RemoteFileUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
 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
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.preferences.AppPreferences
@@ -244,7 +244,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
         intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
 
         bundle.putString(KEY_ROOM_TOKEN, roomToken)
-        bundle.putParcelable(KEY_USER_ENTITY, currentUser)
+        bundle.putLong(KEY_INTERNAL_USER_ID, currentUser.id!!)
         bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
 
         intent.putExtras(bundle)

+ 136 - 0
app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt

@@ -0,0 +1,136 @@
+package com.nextcloud.talk.models.domain
+
+import com.nextcloud.talk.models.json.conversations.Conversation
+
+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
+) {
+
+    companion object {
+        fun mapToConversationModel(
+            conversation: Conversation
+        ): ConversationModel {
+            return ConversationModel(
+                roomId = conversation.roomId,
+                token = conversation.token,
+                name = conversation.name,
+                displayName = conversation.displayName,
+                description = conversation.description,
+                type = conversation.type?.let { ConversationType.valueOf(it.name) },
+                lastPing = conversation.lastPing,
+                participantType = conversation.participantType?.let { 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,     to do...
+                objectType = conversation.objectType?.let { ObjectType.valueOf(it.name) },
+                notificationLevel = conversation.notificationLevel?.let {
+                    NotificationLevel.valueOf(
+                        it.name
+                    )
+                },
+                conversationReadOnlyState = conversation.conversationReadOnlyState?.let {
+                    ConversationReadOnlyState.valueOf(
+                        it.name
+                    )
+                },
+                lobbyState = conversation.lobbyState?.let { 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
+            )
+        }
+    }
+}
+
+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
+}

+ 9 - 0
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

+ 0 - 3
app/src/main/java/com/nextcloud/talk/openconversations/ListOpenConversationsActivity.kt

@@ -79,10 +79,7 @@ class ListOpenConversationsActivity : BaseActivity() {
     }
 
     private fun adapterOnClick(conversation: OpenConversation) {
-        val user = userProvider.currentUser.blockingGet()
-
         val bundle = Bundle()
-        bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, user)
         bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.roomToken)
 
         val chatIntent = Intent(context, ChatActivity::class.java)

+ 8 - 1
app/src/main/java/com/nextcloud/talk/polls/ui/PollMainDialogFragment.kt

@@ -37,6 +37,7 @@ import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.DialogPollMainBinding
 import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
@@ -48,16 +49,22 @@ class PollMainDialogFragment : DialogFragment() {
     @Inject
     lateinit var viewThemeUtils: ViewThemeUtils
 
+    var currentUserProvider: CurrentUserProviderNew? = null
+        @Inject set
+
     private lateinit var binding: DialogPollMainBinding
     private lateinit var viewModel: PollMainViewModel
 
+    lateinit var user: User
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
 
         viewModel = ViewModelProvider(this, viewModelFactory)[PollMainViewModel::class.java]
 
-        val user: User = arguments?.getParcelable(KEY_USER_ENTITY)!!
+        user = currentUserProvider?.currentUser?.blockingGet()!!
+
         val roomToken = arguments?.getString(KEY_ROOM_TOKEN)!!
         val isOwnerOrModerator = arguments?.getBoolean(KEY_OWNER_OR_MODERATOR)!!
         val pollId = arguments?.getString(KEY_POLL_ID)!!

+ 0 - 5
app/src/main/java/com/nextcloud/talk/receivers/ShareRecordingToChatReceiver.kt

@@ -56,8 +56,6 @@ class ShareRecordingToChatReceiver : BroadcastReceiver() {
     lateinit var currentUser: User
     private var systemNotificationId: Int? = null
     private var link: String? = null
-    var roomToken: String? = null
-    var conversationOfShareTarget: User? = null
 
     init {
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@@ -68,9 +66,6 @@ class ShareRecordingToChatReceiver : BroadcastReceiver() {
         systemNotificationId = intent!!.getIntExtra(KEY_SYSTEM_NOTIFICATION_ID, 0)
         link = intent.getStringExtra(BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL)
 
-        roomToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)
-        conversationOfShareTarget = intent.getParcelableExtra<User>(BundleKeys.KEY_USER_ENTITY)
-
         val id = intent.getLongExtra(KEY_INTERNAL_USER_ID, userManager.currentUser.blockingGet().id!!)
         currentUser = userManager.getUserWithId(id).blockingGet()
 

+ 2 - 3
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<ReactionAddedModel>
 
     fun deleteReaction(
-        currentConversation: Conversation,
+        roomToken: String,
         message: ChatMessage,
         emoji: String
     ): Observable<ReactionDeletedModel>

+ 4 - 5
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<ReactionAddedModel> {
@@ -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<ReactionDeletedModel> {
@@ -62,7 +61,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
             credentials,
             ApiUtils.getUrlForMessageReaction(
                 currentUser.baseUrl,
-                currentConversation.token,
+                roomToken,
                 message.id
             ),
             emoji

+ 7 - 2
app/src/main/java/com/nextcloud/talk/shareditems/activities/SharedItemsActivity.kt

@@ -49,12 +49,15 @@ import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
 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.CurrentUserProviderNew
 import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class SharedItemsActivity : AppCompatActivity() {
 
+    @Inject
+    lateinit var currentUserProvider: CurrentUserProviderNew
+
     @Inject
     lateinit var viewModelFactory: ViewModelProvider.Factory
 
@@ -70,7 +73,9 @@ class SharedItemsActivity : AppCompatActivity() {
 
         val roomToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
         val conversationName = intent.getStringExtra(KEY_CONVERSATION_NAME)
-        val user = intent.getParcelableExtra<User>(KEY_USER_ENTITY)!!
+
+        val user = currentUserProvider.currentUser.blockingGet()
+
         val isUserConversationOwnerOrModerator = intent.getBooleanExtra(KEY_USER_IS_OWNER_OR_MODERATOR, false)
 
         binding = ActivitySharedItemsBinding.inflate(layoutInflater)

+ 4 - 38
app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt

@@ -46,7 +46,6 @@ import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.Disposable
 import io.reactivex.schedulers.Schedulers
-import org.parceler.Parcels
 
 private const val TAG = "ProfileBottomSheet"
 
@@ -144,46 +143,13 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User) {
 
                 override fun onNext(roomOverall: RoomOverall) {
                     val bundle = Bundle()
-                    bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, userModel)
                     bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
                     bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
 
-                    // FIXME once APIv2+ is used only, the createRoom already returns all the data
-                    ncApi.getRoom(
-                        credentials,
-                        ApiUtils.getUrlForRoom(
-                            apiVersion,
-                            userModel.baseUrl,
-                            roomOverall.ocs!!.data!!.token
-                        )
-                    )
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(object : Observer<RoomOverall> {
-                            override fun onSubscribe(d: Disposable) {
-                                // unused atm
-                            }
-
-                            override fun onNext(roomOverall: RoomOverall) {
-                                bundle.putParcelable(
-                                    BundleKeys.KEY_ACTIVE_CONVERSATION,
-                                    Parcels.wrap(roomOverall.ocs!!.data)
-                                )
-
-                                val chatIntent = Intent(context, ChatActivity::class.java)
-                                chatIntent.putExtras(bundle)
-                                chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                                context.startActivity(chatIntent)
-                            }
-
-                            override fun onError(e: Throwable) {
-                                Log.e(TAG, e.message, e)
-                            }
-
-                            override fun onComplete() {
-                                // unused atm
-                            }
-                        })
+                    val chatIntent = Intent(context, ChatActivity::class.java)
+                    chatIntent.putExtras(bundle)
+                    chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                    context.startActivity(chatIntent)
                 }
 
                 override fun onError(e: Throwable) {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java

@@ -203,7 +203,7 @@ public class ChooseAccountDialogFragment extends DialogFragment {
             dismiss();
 
             if (status != null) {
-                SetStatusDialogFragment setStatusDialog = SetStatusDialogFragment.newInstance(user, status);
+                SetStatusDialogFragment setStatusDialog = SetStatusDialogFragment.newInstance(status);
                 setStatusDialog.show(getActivity().getSupportFragmentManager(), "fragment_set_status");
             } else {
                 Log.w(TAG, "status was null");

+ 6 - 11
app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt

@@ -20,7 +20,6 @@
 
 package com.nextcloud.talk.ui.dialog
 
-import android.app.Activity
 import android.os.Bundle
 import android.text.TextUtils
 import android.view.View
@@ -55,16 +54,13 @@ import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_OPERATION_CODE
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
-import org.parceler.Parcels
 import javax.inject.Inject
 
 @AutoInjector(NextcloudTalkApplication::class)
 class ConversationsListBottomDialog(
-    val activity: Activity,
-    val controller: ConversationsListActivity,
+    val activity: ConversationsListActivity,
     val currentUser: User,
     val conversation: Conversation
 ) : BottomSheetDialog(activity) {
@@ -175,9 +171,8 @@ class ConversationsListBottomDialog(
             if (!TextUtils.isEmpty(conversation.token)) {
                 val bundle = Bundle()
                 bundle.putLong(KEY_INTERNAL_USER_ID, currentUser.id!!)
-                bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
-
-                controller.showDeleteConversationDialog(bundle)
+                bundle.putString(KEY_ROOM_TOKEN, conversation.token)
+                activity.showDeleteConversationDialog(bundle)
             }
 
             dismiss()
@@ -198,8 +193,8 @@ class ConversationsListBottomDialog(
 
     private fun executeOperationsMenuController(operation: ConversationOperationEnum) {
         val bundle = Bundle()
-        bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
         bundle.putSerializable(KEY_OPERATION_CODE, operation)
+        bundle.putString(KEY_ROOM_TOKEN, conversation.token)
 
         binding.operationItemsLayout.visibility = View.GONE
 
@@ -211,13 +206,13 @@ class ConversationsListBottomDialog(
                 .popChangeHandler(HorizontalChangeHandler())
         )
 
-        controller.fetchRooms()
+        activity.fetchRooms()
     }
 
     private fun executeEntryMenuController(operation: ConversationOperationEnum) {
         val bundle = Bundle()
-        bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
         bundle.putSerializable(KEY_OPERATION_CODE, operation)
+        bundle.putString(KEY_ROOM_TOKEN, conversation.token)
 
         binding.operationItemsLayout.visibility = View.GONE
 

+ 8 - 6
app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt

@@ -40,10 +40,12 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.chat.ChatActivity
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.DialogMessageActionsBinding
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.domain.ConversationReadOnlyState
+import com.nextcloud.talk.models.domain.ConversationType
 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 +65,7 @@ class MessageActionsDialog(
     private val chatActivity: ChatActivity,
     private val message: ChatMessage,
     private val user: User?,
-    private val currentConversation: Conversation?,
+    private val currentConversation: ConversationModel?,
     private val showMessageDeletionButton: Boolean,
     private val hasChatPermission: Boolean
 ) : BottomSheetDialog(chatActivity) {
@@ -100,7 +102,7 @@ class MessageActionsDialog(
             message.replyable &&
                 hasUserId(user) &&
                 hasUserActorId(message) &&
-                currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+                currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
         )
         initMenuDeleteMessage(showMessageDeletionButton)
         initMenuForwardMessage(
@@ -226,7 +228,7 @@ class MessageActionsDialog(
     }
 
     private fun isPermitted(hasChatPermission: Boolean): Boolean {
-        return hasChatPermission && Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
+        return hasChatPermission && ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
             currentConversation?.conversationReadOnlyState
     }
 
@@ -338,12 +340,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())

+ 6 - 3
app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt

@@ -60,6 +60,7 @@ import com.nextcloud.talk.models.json.status.predefined.PredefinedStatusOverall
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.vanniktech.emoji.EmojiPopup
 import com.vanniktech.emoji.installDisableKeyboardInput
 import com.vanniktech.emoji.installForceSingleEmoji
@@ -113,6 +114,9 @@ class SetStatusDialogFragment :
     @Inject
     lateinit var viewThemeUtils: ViewThemeUtils
 
+    var currentUserProvider: CurrentUserProviderNew? = null
+        @Inject set
+
     lateinit var credentials: String
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -121,7 +125,7 @@ class SetStatusDialogFragment :
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
 
         arguments?.let {
-            currentUser = it.getParcelable(ARG_CURRENT_USER_PARAM)
+            currentUser = currentUserProvider?.currentUser?.blockingGet()
             currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM)
 
             credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
@@ -559,9 +563,8 @@ class SetStatusDialogFragment :
         private val TAG = SetStatusDialogFragment::class.simpleName
 
         @JvmStatic
-        fun newInstance(user: User, status: Status): SetStatusDialogFragment {
+        fun newInstance(status: Status): SetStatusDialogFragment {
             val args = Bundle()
-            args.putParcelable(ARG_CURRENT_USER_PARAM, user)
             args.putParcelable(ARG_CURRENT_STATUS_PARAM, status)
 
             val dialogFragment = SetStatusDialogFragment()

+ 3 - 4
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

+ 89 - 0
app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt

@@ -0,0 +1,89 @@
+package com.nextcloud.talk.utils
+
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.domain.ConversationType
+import com.nextcloud.talk.models.domain.ParticipantType
+import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+
+/*
+ * Nextcloud Talk application
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+object ConversationUtils {
+    private val TAG = ConversationUtils::class.java.simpleName
+
+    fun isPublic(conversation: ConversationModel): Boolean {
+        return ConversationType.ROOM_PUBLIC_CALL == conversation.type
+    }
+
+    fun isGuest(conversation: ConversationModel): Boolean {
+        return ParticipantType.GUEST == conversation.participantType ||
+            ParticipantType.GUEST_MODERATOR == conversation.participantType ||
+            ParticipantType.USER_FOLLOWING_LINK == conversation.participantType
+    }
+
+    fun isParticipantOwnerOrModerator(conversation: ConversationModel): Boolean {
+        return ParticipantType.OWNER == conversation.participantType ||
+            ParticipantType.GUEST_MODERATOR == conversation.participantType ||
+            ParticipantType.MODERATOR == conversation.participantType
+    }
+
+    private fun isLockedOneToOne(conversation: ConversationModel, conversationUser: User): Boolean {
+        return conversation.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
+            CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms")
+    }
+
+    fun canModerate(conversation: ConversationModel, conversationUser: User): Boolean {
+        return isParticipantOwnerOrModerator(conversation) &&
+            !isLockedOneToOne(conversation, conversationUser) &&
+            conversation.type != ConversationType.FORMER_ONE_TO_ONE
+    }
+
+    fun isLobbyViewApplicable(conversation: ConversationModel, conversationUser: User): Boolean {
+        return !canModerate(conversation, conversationUser) &&
+            (
+                conversation.type == ConversationType.ROOM_GROUP_CALL ||
+                    conversation.type == ConversationType.ROOM_PUBLIC_CALL
+                )
+    }
+
+    fun isNameEditable(conversation: ConversationModel, conversationUser: User): Boolean {
+        return canModerate(conversation, conversationUser) &&
+            ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation.type
+    }
+
+    fun canLeave(conversation: ConversationModel): Boolean {
+        return if (conversation.canLeaveConversation != null) {
+            // Available since APIv2
+            conversation.canLeaveConversation!!
+        } else {
+            true
+        }
+    }
+
+    fun canDelete(conversation: ConversationModel, conversationUser: User): Boolean {
+        return if (conversation.canDeleteConversation != null) {
+            // Available since APIv2
+            conversation.canDeleteConversation!!
+        } else {
+            canModerate(conversation, conversationUser)
+            // Fallback for APIv1
+        }
+    }
+}

+ 8 - 1
app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt

@@ -23,6 +23,7 @@
 package com.nextcloud.talk.utils
 
 import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
 
@@ -31,9 +32,15 @@ import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
  */
 class ParticipantPermissions(
     private val user: User,
-    private val conversation: Conversation
+    private val conversation: ConversationModel
 ) {
 
+    @Deprecated("Use ChatRepository.ConversationModel")
+    constructor(user: User, conversation: Conversation) : this(
+        user,
+        ConversationModel.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

+ 1 - 5
app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt

@@ -33,19 +33,17 @@ object BundleKeys {
     const val KEY_BASE_URL = "KEY_BASE_URL"
     const val KEY_IS_ACCOUNT_IMPORT = "KEY_IS_ACCOUNT_IMPORT"
     const val KEY_ORIGINAL_PROTOCOL = "KEY_ORIGINAL_PROTOCOL"
-    const val KEY_ROOM = "KEY_CONVERSATION"
     const val KEY_OPERATION_CODE = "KEY_OPERATION_CODE"
-    const val KEY_SHARE_INTENT = "KEY_SHARE_INTENT"
     const val KEY_APP_ITEM_PACKAGE_NAME = "KEY_APP_ITEM_PACKAGE_NAME"
     const val KEY_APP_ITEM_NAME = "KEY_APP_ITEM_NAME"
     const val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD"
     const val KEY_ROOM_TOKEN = "KEY_ROOM_TOKEN"
     const val KEY_ROOM_ONE_TO_ONE = "KEY_ROOM_ONE_TO_ONE"
-    const val KEY_USER_ENTITY = "KEY_USER_ENTITY"
     const val KEY_NEW_CONVERSATION = "KEY_NEW_CONVERSATION"
     const val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS"
     const val KEY_EXISTING_PARTICIPANTS = "KEY_EXISTING_PARTICIPANTS"
     const val KEY_CALL_URL = "KEY_CALL_URL"
+    const val KEY_NEW_ROOM_NAME = "KEY_NEW_ROOM_NAME"
     const val KEY_MODIFIED_BASE_URL = "KEY_MODIFIED_BASE_URL"
     const val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT"
     const val KEY_NOTIFICATION_SIGNATURE = "KEY_NOTIFICATION_SIGNATURE"
@@ -59,8 +57,6 @@ object BundleKeys {
     const val KEY_RECORDING_STATE = "KEY_RECORDING_STATE"
     const val KEY_CALL_VOICE_ONLY = "KEY_CALL_VOICE_ONLY"
     const val KEY_CALL_WITHOUT_NOTIFICATION = "KEY_CALL_WITHOUT_NOTIFICATION"
-    const val KEY_ACTIVE_CONVERSATION = "KEY_ACTIVE_CONVERSATION"
-    const val KEY_SERVER_CAPABILITIES = "KEY_SERVER_CAPABILITIES"
     const val KEY_FROM_NOTIFICATION_START_CALL = "KEY_FROM_NOTIFICATION_START_CALL"
     const val KEY_ROOM_ID = "KEY_ROOM_ID"
     const val KEY_ARE_CALL_SOUNDS = "KEY_ARE_CALL_SOUNDS"