Преглед изворни кода

pass spreedCapabilities instead user to CapabilitiesUtil

To support federated rooms, capabilities have to be checked from the room which now also has capabilities.
If room is not federated, capabilities fromuser are still checked.
This is why CapabilitiesUtil had to be refactored to accept SpreedCapabilities which can come from room or user.

Other than that, many other changes were made as a result of this change.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Marcel Hibbe пре 1 година
родитељ
комит
754b825096
100 измењених фајлова са 2084 додато и 1554 уклоњено
  1. 3 3
      app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt
  2. 2 2
      app/src/main/java/com/nextcloud/talk/account/ServerSelectionActivity.kt
  3. 37 28
      app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
  4. 3 3
      app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
  5. 3 2
      app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt
  6. 2 2
      app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt
  7. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt
  8. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt
  9. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt
  10. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt
  11. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt
  12. 2 2
      app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt
  13. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt
  14. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt
  15. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt
  16. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt
  17. 1 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt
  18. 5 0
      app/src/main/java/com/nextcloud/talk/api/NcApi.java
  19. 9 8
      app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt
  20. 174 124
      app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  21. 11 3
      app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt
  22. 42 18
      app/src/main/java/com/nextcloud/talk/chat/data/network/NetworkChatRepositoryImpl.kt
  23. 77 38
      app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt
  24. 1 1
      app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFolderListingOperation.kt
  25. 23 10
      app/src/main/java/com/nextcloud/talk/contacts/ContactsActivity.kt
  26. 6 6
      app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt
  27. 196 165
      app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt
  28. 8 4
      app/src/main/java/com/nextcloud/talk/conversationinfo/GuestAccessHelper.kt
  29. 142 0
      app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt
  30. 13 8
      app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt
  31. 4 4
      app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepositoryImpl.kt
  32. 30 15
      app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
  33. 6 0
      app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
  34. 3 3
      app/src/main/java/com/nextcloud/talk/data/user/UserMapper.kt
  35. 1 1
      app/src/main/java/com/nextcloud/talk/data/user/model/User.kt
  36. 4 4
      app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt
  37. 6 6
      app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsRepositoryImpl.kt
  38. 1 1
      app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.java
  39. 1 1
      app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt
  40. 1 1
      app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.java
  41. 1 1
      app/src/main/java/com/nextcloud/talk/jobs/LeaveConversationWorker.java
  42. 14 14
      app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
  43. 2 2
      app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt
  44. 1 1
      app/src/main/java/com/nextcloud/talk/jobs/SignalingSettingsWorker.java
  45. 4 2
      app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt
  46. 5 0
      app/src/main/java/com/nextcloud/talk/location/GeocodingActivity.kt
  47. 5 2
      app/src/main/java/com/nextcloud/talk/location/LocationPickerActivity.kt
  48. 1 1
      app/src/main/java/com/nextcloud/talk/models/RetrofitBucket.kt
  49. 11 3
      app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt
  50. 45 0
      app/src/main/java/com/nextcloud/talk/models/domain/converters/DomainEnumNotificationLevelConverter.kt
  51. 42 0
      app/src/main/java/com/nextcloud/talk/models/json/capabilities/RoomCapabilitiesOCS.kt
  52. 37 0
      app/src/main/java/com/nextcloud/talk/models/json/capabilities/RoomCapabilitiesOverall.kt
  53. 7 7
      app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
  54. 18 4
      app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt
  55. 3 3
      app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversationsRepositoryImpl.kt
  56. 5 5
      app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepositoryImpl.kt
  57. 4 4
      app/src/main/java/com/nextcloud/talk/presenters/MentionAutocompletePresenter.java
  58. 16 11
      app/src/main/java/com/nextcloud/talk/profile/ProfileActivity.kt
  59. 1 1
      app/src/main/java/com/nextcloud/talk/raisehand/RequestAssistanceRepositoryImpl.kt
  60. 3 3
      app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt
  61. 3 3
      app/src/main/java/com/nextcloud/talk/receivers/MarkAsReadReceiver.kt
  62. 1 1
      app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt
  63. 3 3
      app/src/main/java/com/nextcloud/talk/repositories/callrecording/CallRecordingRepositoryImpl.kt
  64. 3 3
      app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt
  65. 3 3
      app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt
  66. 1 1
      app/src/main/java/com/nextcloud/talk/repositories/unifiedsearch/UnifiedSearchRepositoryImpl.kt
  67. 20 15
      app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt
  68. 3 3
      app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepositoryImpl.kt
  69. 4 4
      app/src/main/java/com/nextcloud/talk/translate/viewmodels/TranslateViewModel.kt
  70. 3 3
      app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt
  71. 7 6
      app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
  72. 2 2
      app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java
  73. 1 1
      app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt
  74. 25 14
      app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt
  75. 14 6
      app/src/main/java/com/nextcloud/talk/ui/dialog/DateTimePickerFragment.kt
  76. 16 9
      app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt
  77. 3 3
      app/src/main/java/com/nextcloud/talk/ui/dialog/MoreCallActionsDialog.kt
  78. 6 6
      app/src/main/java/com/nextcloud/talk/ui/dialog/SetStatusDialogFragment.kt
  79. 2 2
      app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt
  80. 4 4
      app/src/main/java/com/nextcloud/talk/upload/chunked/ChunkedFileUploader.kt
  81. 1 1
      app/src/main/java/com/nextcloud/talk/upload/normal/FileUploader.kt
  82. 1 1
      app/src/main/java/com/nextcloud/talk/utils/AccountUtils.kt
  83. 0 559
      app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
  84. 577 0
      app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt
  85. 275 0
      app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt
  86. 11 12
      app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt
  87. 1 2
      app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt
  88. 7 8
      app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt
  89. 1 1
      app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt
  90. 2 2
      app/src/main/java/com/nextcloud/talk/utils/RemoteFileUtils.kt
  91. 2 2
      app/src/main/java/com/nextcloud/talk/utils/ShareUtils.kt
  92. 1 0
      app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
  93. 0 267
      app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt
  94. 4 3
      app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.java
  95. 3 4
      app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt
  96. 1 1
      app/src/main/java/com/nextcloud/talk/webrtc/WebSocketConnectionHelper.java
  97. 3 3
      app/src/test/java/com/nextcloud/talk/utils/ParticipantPermissionsTest.kt
  98. 4 4
      app/src/test/java/com/nextcloud/talk/utils/ShareUtilsTest.kt
  99. 1 1
      detekt.yml
  100. 14 54
      gradle/verification-keyring.keys

+ 3 - 3
app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt

@@ -199,7 +199,7 @@ class AccountVerificationActivity : BaseActivity() {
         val credentials = ApiUtils.getCredentials(username, token)
         val credentials = ApiUtils.getCredentials(username, token)
         cookieManager.cookieStore.removeAll()
         cookieManager.cookieStore.removeAll()
 
 
-        ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
+        ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl!!))
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
             .subscribe(object : Observer<CapabilitiesOverall> {
             .subscribe(object : Observer<CapabilitiesOverall> {
                 override fun onSubscribe(d: Disposable) {
                 override fun onSubscribe(d: Disposable) {
@@ -213,7 +213,7 @@ class AccountVerificationActivity : BaseActivity() {
                             capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features != null &&
                             capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features != null &&
                             !capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features!!.isEmpty()
                             !capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features!!.isEmpty()
                     if (hasTalk) {
                     if (hasTalk) {
-                        fetchProfile(credentials, capabilitiesOverall)
+                        fetchProfile(credentials!!, capabilitiesOverall)
                     } else {
                     } else {
                         if (resources != null) {
                         if (resources != null) {
                             runOnUiThread {
                             runOnUiThread {
@@ -305,7 +305,7 @@ class AccountVerificationActivity : BaseActivity() {
     private fun fetchProfile(credentials: String, capabilitiesOverall: CapabilitiesOverall) {
     private fun fetchProfile(credentials: String, capabilitiesOverall: CapabilitiesOverall) {
         ncApi.getUserProfile(
         ncApi.getUserProfile(
             credentials,
             credentials,
-            ApiUtils.getUrlForUserProfile(baseUrl)
+            ApiUtils.getUrlForUserProfile(baseUrl!!)
         )
         )
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
             .subscribe(object : Observer<UserProfileOverall> {
             .subscribe(object : Observer<UserProfileOverall> {

+ 2 - 2
app/src/main/java/com/nextcloud/talk/account/ServerSelectionActivity.kt

@@ -52,7 +52,7 @@ import com.nextcloud.talk.utils.UriUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT
 import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
 import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
 import io.reactivex.Observer
 import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.android.schedulers.AndroidSchedulers
@@ -336,7 +336,7 @@ class ServerSelectionActivity : BaseActivity() {
 
 
                     if (hasTalk) {
                     if (hasTalk) {
                         runOnUiThread {
                         runOnUiThread {
-                            if (CapabilitiesUtilNew.isServerEOL(capabilities)) {
+                            if (CapabilitiesUtil.isServerEOL(capabilitiesOverall.ocs?.data?.serverVersion?.major!!)) {
                                 if (resources != null) {
                                 if (resources != null) {
                                     runOnUiThread {
                                     runOnUiThread {
                                         setErrorText(resources!!.getString(R.string.nc_settings_server_eol))
                                         setErrorText(resources!!.getString(R.string.nc_settings_server_eol))

+ 37 - 28
app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt

@@ -110,6 +110,7 @@ import com.nextcloud.talk.ui.dialog.AudioOutputDialog
 import com.nextcloud.talk.ui.dialog.MoreCallActionsDialog
 import com.nextcloud.talk.ui.dialog.MoreCallActionsDialog
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.NotificationUtils.cancelExistingNotificationsForRoom
 import com.nextcloud.talk.utils.NotificationUtils.cancelExistingNotificationsForRoom
 import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
 import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
@@ -131,9 +132,9 @@ 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_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH
 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_SWITCH_TO_ROOM
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isCallRecordingAvailable
+import com.nextcloud.talk.utils.CapabilitiesUtil
+import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
+import com.nextcloud.talk.utils.CapabilitiesUtil.isCallRecordingAvailable
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.power.PowerManagerUtils
 import com.nextcloud.talk.utils.power.PowerManagerUtils
@@ -234,7 +235,7 @@ class CallActivity : CallBaseActivity() {
     private var iceServers: MutableList<PeerConnection.IceServer>? = null
     private var iceServers: MutableList<PeerConnection.IceServer>? = null
     private var cameraEnumerator: CameraEnumerator? = null
     private var cameraEnumerator: CameraEnumerator? = null
     private var roomToken: String? = null
     private var roomToken: String? = null
-    var conversationUser: User? = null
+    lateinit var conversationUser: User
     private var conversationName: String? = null
     private var conversationName: String? = null
     private var callSession: String? = null
     private var callSession: String? = null
     private var localStream: MediaStream? = null
     private var localStream: MediaStream? = null
@@ -530,13 +531,13 @@ class CallActivity : CallBaseActivity() {
             )
             )
         }
         }
 
 
-        when (CapabilitiesUtilNew.getRecordingConsentType(conversationUser)) {
-            CapabilitiesUtilNew.RECORDING_CONSENT_NOT_REQUIRED -> initiateCall()
-            CapabilitiesUtilNew.RECORDING_CONSENT_REQUIRED -> askForRecordingConsent()
-            CapabilitiesUtilNew.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> {
+        when (CapabilitiesUtil.getRecordingConsentType(conversationUser!!.capabilities!!.spreedCapability!!)) {
+            CapabilitiesUtil.RECORDING_CONSENT_NOT_REQUIRED -> initiateCall()
+            CapabilitiesUtil.RECORDING_CONSENT_REQUIRED -> askForRecordingConsent()
+            CapabilitiesUtil.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> {
                 val getRoomApiVersion = ApiUtils.getConversationApiVersion(
                 val getRoomApiVersion = ApiUtils.getConversationApiVersion(
-                    conversationUser,
-                    intArrayOf(ApiUtils.APIv4, 1)
+                    conversationUser!!,
+                    intArrayOf(ApiUtils.API_V4, 1)
                 )
                 )
                 ncApi!!.getRoom(credentials, ApiUtils.getUrlForRoom(getRoomApiVersion, baseUrl, roomToken))
                 ncApi!!.getRoom(credentials, ApiUtils.getUrlForRoom(getRoomApiVersion, baseUrl, roomToken))
                     .retry(API_RETRIES)
                     .retry(API_RETRIES)
@@ -571,7 +572,10 @@ class CallActivity : CallBaseActivity() {
 
 
     override fun onResume() {
     override fun onResume() {
         super.onResume()
         super.onResume()
-        if (hasSpreedFeatureCapability(conversationUser, "recording-v1") &&
+        if (hasSpreedFeatureCapability(
+                conversationUser.capabilities!!.spreedCapability!!,
+                SpreedFeatures.RECORDING_V1
+            ) &&
             othersInCall &&
             othersInCall &&
             elapsedSeconds.toInt() >= CALL_TIME_ONE_HOUR
             elapsedSeconds.toInt() >= CALL_TIME_ONE_HOUR
         ) {
         ) {
@@ -1468,7 +1472,7 @@ class CallActivity : CallBaseActivity() {
 
 
     private fun fetchSignalingSettings() {
     private fun fetchSignalingSettings() {
         Log.d(TAG, "fetchSignalingSettings")
         Log.d(TAG, "fetchSignalingSettings")
-        val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.APIv3, 2, 1))
+        val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.API_V3, 2, 1))
         ncApi!!.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl))
         ncApi!!.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl))
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
             .retry(API_RETRIES)
             .retry(API_RETRIES)
@@ -1531,7 +1535,7 @@ class CallActivity : CallBaseActivity() {
     private fun addIceServers(signalingSettingsOverall: SignalingSettingsOverall, apiVersion: Int) {
     private fun addIceServers(signalingSettingsOverall: SignalingSettingsOverall, apiVersion: Int) {
         if (signalingSettingsOverall.ocs!!.settings!!.stunServers != null) {
         if (signalingSettingsOverall.ocs!!.settings!!.stunServers != null) {
             val stunServers = signalingSettingsOverall.ocs!!.settings!!.stunServers
             val stunServers = signalingSettingsOverall.ocs!!.settings!!.stunServers
-            if (apiVersion == ApiUtils.APIv3) {
+            if (apiVersion == ApiUtils.API_V3) {
                 for ((_, urls) in stunServers!!) {
                 for ((_, urls) in stunServers!!) {
                     if (urls != null) {
                     if (urls != null) {
                         for (url in urls) {
                         for (url in urls) {
@@ -1564,7 +1568,7 @@ class CallActivity : CallBaseActivity() {
     }
     }
 
 
     private fun checkCapabilities() {
     private fun checkCapabilities() {
-        ncApi!!.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
+        ncApi!!.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl!!))
             .retry(API_RETRIES)
             .retry(API_RETRIES)
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread())
             .observeOn(AndroidSchedulers.mainThread())
@@ -1600,7 +1604,7 @@ class CallActivity : CallBaseActivity() {
 
 
     private fun joinRoomAndCall() {
     private fun joinRoomAndCall() {
         callSession = ApplicationWideCurrentRoomHolder.getInstance().session
         callSession = ApplicationWideCurrentRoomHolder.getInstance().session
-        val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
         Log.d(TAG, "joinRoomAndCall")
         Log.d(TAG, "joinRoomAndCall")
         Log.d(TAG, "   baseUrl= $baseUrl")
         Log.d(TAG, "   baseUrl= $baseUrl")
         Log.d(TAG, "   roomToken= $roomToken")
         Log.d(TAG, "   roomToken= $roomToken")
@@ -1656,7 +1660,7 @@ class CallActivity : CallBaseActivity() {
         fun getRoomAndContinue() {
         fun getRoomAndContinue() {
             val getRoomApiVersion = ApiUtils.getConversationApiVersion(
             val getRoomApiVersion = ApiUtils.getConversationApiVersion(
                 conversationUser,
                 conversationUser,
-                intArrayOf(ApiUtils.APIv4, 1)
+                intArrayOf(ApiUtils.API_V4, 1)
             )
             )
             ncApi!!.getRoom(credentials, ApiUtils.getUrlForRoom(getRoomApiVersion, baseUrl, roomToken))
             ncApi!!.getRoom(credentials, ApiUtils.getUrlForRoom(getRoomApiVersion, baseUrl, roomToken))
                 .retry(API_RETRIES)
                 .retry(API_RETRIES)
@@ -1715,10 +1719,10 @@ class CallActivity : CallBaseActivity() {
         callParticipantList = CallParticipantList(signalingMessageReceiver)
         callParticipantList = CallParticipantList(signalingMessageReceiver)
         callParticipantList!!.addObserver(callParticipantListObserver)
         callParticipantList!!.addObserver(callParticipantListObserver)
 
 
-        val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
         ncApi!!.joinCall(
         ncApi!!.joinCall(
             credentials,
             credentials,
-            ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken),
+            ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken!!),
             inCallFlag,
             inCallFlag,
             isCallWithoutNotification,
             isCallWithoutNotification,
             recordingConsentGiven
             recordingConsentGiven
@@ -1756,7 +1760,10 @@ class CallActivity : CallBaseActivity() {
     }
     }
 
 
     private fun startCallTimeCounter(callStartTime: Long?) {
     private fun startCallTimeCounter(callStartTime: Long?) {
-        if (callStartTime != null && hasSpreedFeatureCapability(conversationUser, "recording-v1")) {
+        if (callStartTime != null && hasSpreedFeatureCapability(
+                conversationUser!!.capabilities!!.spreedCapability!!, SpreedFeatures.RECORDING_V1
+            )
+        ) {
             binding!!.callDuration.visibility = View.VISIBLE
             binding!!.callDuration.visibility = View.VISIBLE
             val currentTimeInSec = System.currentTimeMillis() / SECOND_IN_MILLIES
             val currentTimeInSec = System.currentTimeMillis() / SECOND_IN_MILLIES
             elapsedSeconds = currentTimeInSec - callStartTime
             elapsedSeconds = currentTimeInSec - callStartTime
@@ -1793,7 +1800,7 @@ class CallActivity : CallBaseActivity() {
     }
     }
 
 
     private fun pullSignalingMessages() {
     private fun pullSignalingMessages() {
-        val signalingApiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.APIv3, 2, 1))
+        val signalingApiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.API_V3, 2, 1))
         val delayOnError = AtomicInteger(0)
         val delayOnError = AtomicInteger(0)
 
 
         ncApi!!.pullSignalingMessages(
         ncApi!!.pullSignalingMessages(
@@ -1801,7 +1808,7 @@ class CallActivity : CallBaseActivity() {
             ApiUtils.getUrlForSignaling(
             ApiUtils.getUrlForSignaling(
                 signalingApiVersion,
                 signalingApiVersion,
                 baseUrl,
                 baseUrl,
-                roomToken
+                roomToken!!
             )
             )
         )
         )
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
@@ -2031,12 +2038,12 @@ class CallActivity : CallBaseActivity() {
 
 
     private fun hangupNetworkCalls(shutDownView: Boolean) {
     private fun hangupNetworkCalls(shutDownView: Boolean) {
         Log.d(TAG, "hangupNetworkCalls. shutDownView=$shutDownView")
         Log.d(TAG, "hangupNetworkCalls. shutDownView=$shutDownView")
-        val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
         if (callParticipantList != null) {
         if (callParticipantList != null) {
             callParticipantList!!.removeObserver(callParticipantListObserver)
             callParticipantList!!.removeObserver(callParticipantListObserver)
             callParticipantList!!.destroy()
             callParticipantList!!.destroy()
         }
         }
-        ncApi!!.leaveCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken))
+        ncApi!!.leaveCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken!!))
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread())
             .observeOn(AndroidSchedulers.mainThread())
             .subscribe(object : Observer<GenericOverall> {
             .subscribe(object : Observer<GenericOverall> {
@@ -2919,10 +2926,10 @@ class CallActivity : CallBaseActivity() {
             val strings: MutableList<String> = ArrayList()
             val strings: MutableList<String> = ArrayList()
             val stringToSend = stringBuilder.toString()
             val stringToSend = stringBuilder.toString()
             strings.add(stringToSend)
             strings.add(stringToSend)
-            val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.APIv3, 2, 1))
+            val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.API_V3, 2, 1))
             ncApi!!.sendSignalingMessages(
             ncApi!!.sendSignalingMessages(
                 credentials,
                 credentials,
-                ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken),
+                ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken!!),
                 strings.toString()
                 strings.toString()
             )
             )
                 .retry(API_RETRIES)
                 .retry(API_RETRIES)
@@ -3099,12 +3106,14 @@ class CallActivity : CallBaseActivity() {
 
 
     val isAllowedToStartOrStopRecording: Boolean
     val isAllowedToStartOrStopRecording: Boolean
         get() = (
         get() = (
-            isCallRecordingAvailable(conversationUser!!) &&
+            isCallRecordingAvailable(conversationUser!!.capabilities!!.spreedCapability!!) &&
                 isModerator
                 isModerator
             )
             )
     val isAllowedToRaiseHand: Boolean
     val isAllowedToRaiseHand: Boolean
-        get() = hasSpreedFeatureCapability(conversationUser, "raise-hand") ||
-            isBreakoutRoom
+        get() = hasSpreedFeatureCapability(
+            conversationUser.capabilities!!.spreedCapability!!,
+            SpreedFeatures.RAISE_HAND
+        ) || isBreakoutRoom
 
 
     private inner class SelfVideoTouchListener : OnTouchListener {
     private inner class SelfVideoTouchListener : OnTouchListener {
         @SuppressLint("ClickableViewAccessibility")
         @SuppressLint("ClickableViewAccessibility")

+ 3 - 3
app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt

@@ -181,7 +181,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
                     val user = userId.substringBeforeLast("@")
                     val user = userId.substringBeforeLast("@")
                     val baseUrl = userId.substringAfterLast("@")
                     val baseUrl = userId.substringAfterLast("@")
 
 
-                    if (userManager.currentUser.blockingGet()?.baseUrl?.endsWith(baseUrl) == true) {
+                    if (userManager.currentUser.blockingGet()?.baseUrl!!.endsWith(baseUrl) == true) {
                         startConversation(user)
                         startConversation(user)
                     } else {
                     } else {
                         Snackbar.make(
                         Snackbar.make(
@@ -200,11 +200,11 @@ class MainActivity : BaseActivity(), ActionBarProvider {
 
 
         val currentUser = userManager.currentUser.blockingGet()
         val currentUser = userManager.currentUser.blockingGet()
 
 
-        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, 1))
         val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
         val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
         val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
         val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
             apiVersion,
             apiVersion,
-            currentUser?.baseUrl,
+            currentUser?.baseUrl!!,
             roomType,
             roomType,
             null,
             null,
             userId,
             userId,

+ 3 - 2
app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt

@@ -50,9 +50,10 @@ import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
 import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
 import com.nextcloud.talk.ui.StatusDrawable
 import com.nextcloud.talk.ui.StatusDrawable
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.DisplayUtils
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
+import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
 import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
 import eu.davidea.flexibleadapter.items.IFilterable
 import eu.davidea.flexibleadapter.items.IFilterable
@@ -312,7 +313,7 @@ class ConversationItem(
         if (model.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
         if (model.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
             viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
             viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
         } else if (model.unreadMention) {
         } else if (model.unreadMention) {
-            if (hasSpreedFeatureCapability(user, "direct-mention-flag")) {
+            if (hasSpreedFeatureCapability(user.capabilities?.spreedCapability!!, SpreedFeatures.DIRECT_MENTION_FLAG)) {
                 if (model.unreadMentionDirect!!) {
                 if (model.unreadMentionDirect!!) {
                     viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
                     viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
                 } else {
                 } else {

+ 2 - 2
app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt

@@ -77,12 +77,12 @@ class CallStartedViewHolder(incomingView: View, payload: Any) :
         val user = userManager.currentUser.blockingGet()
         val user = userManager.currentUser.blockingGet()
         val url: String = if (message.actorType == "guests" || message.actorType == "guest") {
         val url: String = if (message.actorType == "guests" || message.actorType == "guest") {
             ApiUtils.getUrlForGuestAvatar(
             ApiUtils.getUrlForGuestAvatar(
-                user!!.baseUrl,
+                user!!.baseUrl!!,
                 message.actorDisplayName,
                 message.actorDisplayName,
                 true
                 true
             )
             )
         } else {
         } else {
-            ApiUtils.getUrlForAvatar(user!!.baseUrl, message.actorDisplayName, false)
+            ApiUtils.getUrlForAvatar(user!!.baseUrl!!, message.actorDisplayName, false)
         }
         }
 
 
         val imageRequest: ImageRequest = ImageRequest.Builder(context)
         val imageRequest: ImageRequest = ImageRequest.Builder(context)

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt

@@ -188,7 +188,7 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) :
                 binding.messageQuote.quotedMessageImage.load(it) {
                 binding.messageQuote.quotedMessageImage.load(it) {
                     addHeader(
                     addHeader(
                         "Authorization",
                         "Authorization",
-                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                     )
                     )
                 }
                 }
             } ?: run {
             } ?: run {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt

@@ -172,7 +172,7 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) :
                 binding.messageQuote.quotedMessageImage.load(it) {
                 binding.messageQuote.quotedMessageImage.load(it) {
                     addHeader(
                     addHeader(
                         "Authorization",
                         "Authorization",
-                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                     )
                     )
                 }
                 }
             } ?: run {
             } ?: run {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt

@@ -195,7 +195,7 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
                 binding.messageQuote.quotedMessageImage.load(it) {
                 binding.messageQuote.quotedMessageImage.load(it) {
                     addHeader(
                     addHeader(
                         "Authorization",
                         "Authorization",
-                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                     )
                     )
                 }
                 }
             } ?: run {
             } ?: run {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt

@@ -197,7 +197,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
             binding.messageQuote.quotedMessageImage.load(it) {
             binding.messageQuote.quotedMessageImage.load(it) {
                 addHeader(
                 addHeader(
                     "Authorization",
                     "Authorization",
-                    ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                    ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                 )
                 )
             }
             }
         } ?: run {
         } ?: run {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt

@@ -301,7 +301,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
                 binding.messageQuote.quotedMessageImage.load(it) {
                 binding.messageQuote.quotedMessageImage.load(it) {
                     addHeader(
                     addHeader(
                         "Authorization",
                         "Authorization",
-                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                     )
                     )
                 }
                 }
             } ?: run {
             } ?: run {

+ 2 - 2
app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt

@@ -45,8 +45,8 @@ class LinkPreview {
         binding.referenceThumbImage.setImageDrawable(null)
         binding.referenceThumbImage.setImageDrawable(null)
 
 
         if (!message.extractedUrlToPreview.isNullOrEmpty()) {
         if (!message.extractedUrlToPreview.isNullOrEmpty()) {
-            val credentials: String = ApiUtils.getCredentials(message.activeUser?.username, message.activeUser?.token)
-            val openGraphLink = ApiUtils.getUrlForOpenGraph(message.activeUser?.baseUrl)
+            val credentials: String = ApiUtils.getCredentials(message.activeUser?.username, message.activeUser?.token)!!
+            val openGraphLink = ApiUtils.getUrlForOpenGraph(message.activeUser?.baseUrl!!)
             ncApi.getOpenGraph(
             ncApi.getOpenGraph(
                 credentials,
                 credentials,
                 openGraphLink,
                 openGraphLink,

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt

@@ -161,7 +161,7 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) :
                 binding.messageQuote.quotedMessageImage.load(it) {
                 binding.messageQuote.quotedMessageImage.load(it) {
                     addHeader(
                     addHeader(
                         "Authorization",
                         "Authorization",
-                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                     )
                     )
                 }
                 }
             } ?: run {
             } ?: run {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt

@@ -213,7 +213,7 @@ class OutcomingLocationMessageViewHolder(incomingView: View) :
                 binding.messageQuote.quotedMessageImage.load(it) {
                 binding.messageQuote.quotedMessageImage.load(it) {
                     addHeader(
                     addHeader(
                         "Authorization",
                         "Authorization",
-                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                     )
                     )
                 }
                 }
             } ?: run {
             } ?: run {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt

@@ -175,7 +175,7 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
                 binding.messageQuote.quotedMessageImage.load(it) {
                 binding.messageQuote.quotedMessageImage.load(it) {
                     addHeader(
                     addHeader(
                         "Authorization",
                         "Authorization",
-                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                     )
                     )
                 }
                 }
             } ?: run {
             } ?: run {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt

@@ -170,7 +170,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
             binding.messageQuote.quotedMessageImage.load(it) {
             binding.messageQuote.quotedMessageImage.load(it) {
                 addHeader(
                 addHeader(
                     "Authorization",
                     "Authorization",
-                    ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                    ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                 )
                 )
             }
             }
         } ?: run {
         } ?: run {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt

@@ -285,7 +285,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
                 binding.messageQuote.quotedMessageImage.load(it) {
                 binding.messageQuote.quotedMessageImage.load(it) {
                     addHeader(
                     addHeader(
                         "Authorization",
                         "Authorization",
-                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
+                        ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
                     )
                     )
                 }
                 }
             } ?: run {
             } ?: run {

+ 5 - 0
app/src/main/java/com/nextcloud/talk/api/NcApi.java

@@ -24,6 +24,7 @@
 package com.nextcloud.talk.api;
 package com.nextcloud.talk.api;
 
 
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
+import com.nextcloud.talk.models.json.capabilities.RoomCapabilitiesOverall;
 import com.nextcloud.talk.models.json.chat.ChatOverall;
 import com.nextcloud.talk.models.json.chat.ChatOverall;
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage;
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage;
 import com.nextcloud.talk.models.json.chat.ChatShareOverall;
 import com.nextcloud.talk.models.json.chat.ChatShareOverall;
@@ -367,6 +368,10 @@ public interface NcApi {
     @GET
     @GET
     Observable<CapabilitiesOverall> getCapabilities(@Url String url);
     Observable<CapabilitiesOverall> getCapabilities(@Url String url);
 
 
+    @GET
+    Observable<RoomCapabilitiesOverall> getRoomCapabilities(@Header("Authorization") String authorization,
+                                                            @Url String url);
+
     /*
     /*
        QueryMap items are as follows:
        QueryMap items are as follows:
          - "lookIntoFuture": int (0 or 1),
          - "lookIntoFuture": int (0 or 1),

+ 9 - 8
app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt

@@ -50,6 +50,7 @@ import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.json.participants.Participant
 import com.nextcloud.talk.models.json.participants.Participant
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.NotificationUtils
 import com.nextcloud.talk.utils.NotificationUtils
 import com.nextcloud.talk.utils.ParticipantPermissions
 import com.nextcloud.talk.utils.ParticipantPermissions
@@ -57,7 +58,7 @@ 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_CALL_VOICE_ONLY
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
 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_ROOM_TOKEN
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
+import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
 import io.reactivex.disposables.Disposable
 import io.reactivex.disposables.Disposable
 import okhttp3.Cache
 import okhttp3.Cache
 import java.io.IOException
 import java.io.IOException
@@ -148,10 +149,10 @@ class CallNotificationActivity : CallBaseActivity() {
 
 
     private fun initObservers() {
     private fun initObservers() {
         val apiVersion = ApiUtils.getConversationApiVersion(
         val apiVersion = ApiUtils.getConversationApiVersion(
-            userBeingCalled,
+            userBeingCalled!!,
             intArrayOf(
             intArrayOf(
-                ApiUtils.APIv4,
-                ApiUtils.APIv3,
+                ApiUtils.API_V4,
+                ApiUtils.API_V3,
                 1
                 1
             )
             )
         )
         )
@@ -186,10 +187,10 @@ class CallNotificationActivity : CallBaseActivity() {
 
 
                     showAnswerControls()
                     showAnswerControls()
 
 
-                    if (apiVersion >= ApiUtils.APIv3) {
+                    if (apiVersion >= ApiUtils.API_V3) {
                         val hasCallFlags = hasSpreedFeatureCapability(
                         val hasCallFlags = hasSpreedFeatureCapability(
-                            userBeingCalled,
-                            "conversation-call-flags"
+                            userBeingCalled?.capabilities?.spreedCapability!!,
+                            SpreedFeatures.CONVERSATION_CALL_FLAGS
                         )
                         )
                         if (hasCallFlags) {
                         if (hasCallFlags) {
                             if (isInCallWithVideo(currentConversation!!.callFlag)) {
                             if (isInCallWithVideo(currentConversation!!.callFlag)) {
@@ -243,7 +244,7 @@ class CallNotificationActivity : CallBaseActivity() {
             originalBundle!!.putString(KEY_CONVERSATION_NAME, currentConversation!!.displayName)
             originalBundle!!.putString(KEY_CONVERSATION_NAME, currentConversation!!.displayName)
 
 
             val participantPermission = ParticipantPermissions(
             val participantPermission = ParticipantPermissions(
-                userBeingCalled!!,
+                userBeingCalled!!.capabilities!!.spreedCapability!!,
                 currentConversation!!
                 currentConversation!!
             )
             )
             originalBundle!!.putBoolean(
             originalBundle!!.putBoolean(

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

@@ -167,6 +167,7 @@ import com.nextcloud.talk.models.domain.ConversationReadOnlyState
 import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.domain.LobbyState
 import com.nextcloud.talk.models.domain.LobbyState
 import com.nextcloud.talk.models.domain.ObjectType
 import com.nextcloud.talk.models.domain.ObjectType
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.models.json.chat.ChatOverall
 import com.nextcloud.talk.models.json.chat.ChatOverall
 import com.nextcloud.talk.models.json.chat.ReadStatus
 import com.nextcloud.talk.models.json.chat.ReadStatus
@@ -192,6 +193,7 @@ import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
 import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
 import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.AudioUtils
 import com.nextcloud.talk.utils.AudioUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.ContactUtils
 import com.nextcloud.talk.utils.ContactUtils
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.DateConstants
 import com.nextcloud.talk.utils.DateConstants
@@ -218,7 +220,7 @@ 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_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH
 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_SWITCH_TO_ROOM
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.rx.DisposableSet
 import com.nextcloud.talk.utils.rx.DisposableSet
@@ -301,6 +303,8 @@ class ChatActivity :
     var sessionIdAfterRoomJoined: String? = null
     var sessionIdAfterRoomJoined: String? = null
     lateinit var roomToken: String
     lateinit var roomToken: String
     var conversationUser: User? = null
     var conversationUser: User? = null
+    lateinit var spreedCapabilities: SpreedCapability
+    var chatApiVersion: Int = 1
     private var roomPassword: String = ""
     private var roomPassword: String = ""
     var credentials: String? = null
     var credentials: String? = null
     var currentConversation: ConversationModel? = null
     var currentConversation: ConversationModel? = null
@@ -351,6 +355,7 @@ class ChatActivity :
         RELEASED,
         RELEASED,
         ERROR
         ERROR
     }
     }
+
     private val editableBehaviorSubject = BehaviorSubject.createDefault(false)
     private val editableBehaviorSubject = BehaviorSubject.createDefault(false)
     private val editedTextBehaviorSubject = BehaviorSubject.createDefault("")
     private val editedTextBehaviorSubject = BehaviorSubject.createDefault("")
 
 
@@ -541,14 +546,6 @@ class ChatActivity :
         }
         }
         this.lifecycle.addObserver(AudioUtils)
         this.lifecycle.addObserver(AudioUtils)
         this.lifecycle.addObserver(ChatViewModel.LifeCycleObserver)
         this.lifecycle.addObserver(ChatViewModel.LifeCycleObserver)
-
-        chatViewModel.refreshChatParams(
-            setupFieldsForPullChatMessages(
-                false,
-                0,
-                false
-            )
-        )
     }
     }
 
 
     override fun onStop() {
     override fun onStop() {
@@ -587,6 +584,30 @@ class ChatActivity :
                 is ChatViewModel.GetRoomSuccessState -> {
                 is ChatViewModel.GetRoomSuccessState -> {
                     currentConversation = state.conversationModel
                     currentConversation = state.conversationModel
                     logConversationInfos("GetRoomSuccessState")
                     logConversationInfos("GetRoomSuccessState")
+                    chatViewModel.getCapabilities(conversationUser!!, roomToken, currentConversation!!)
+                }
+
+                is ChatViewModel.GetRoomErrorState -> {
+                    Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
+                }
+
+                else -> {}
+            }
+        }
+
+        chatViewModel.getCapabilitiesViewState.observe(this) { state ->
+            when (state) {
+                is ChatViewModel.GetCapabilitiesSuccessState -> {
+                    spreedCapabilities = state.spreedCapabilities
+                    chatApiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
+
+                    initMessageInputView()
+
+                    if (conversationUser?.userId != "?" &&
+                        CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)
+                    ) {
+                        binding.chatToolbar.setOnClickListener { v -> showConversationInfoScreen() }
+                    }
 
 
                     if (adapter == null) {
                     if (adapter == null) {
                         initAdapter()
                         initAdapter()
@@ -597,7 +618,7 @@ class ChatActivity :
 
 
                     loadAvatarForStatusBar()
                     loadAvatarForStatusBar()
                     setActionBarTitle()
                     setActionBarTitle()
-                    participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
+                    participantPermissions = ParticipantPermissions(spreedCapabilities, currentConversation!!)
 
 
                     setupSwipeToReply()
                     setupSwipeToReply()
                     setupMentionAutocomplete()
                     setupMentionAutocomplete()
@@ -626,9 +647,17 @@ class ChatActivity :
                         },
                         },
                         delayForRecursiveCall
                         delayForRecursiveCall
                     )
                     )
+
+                    chatViewModel.refreshChatParams(
+                        setupFieldsForPullChatMessages(
+                            false,
+                            0,
+                            false
+                        )
+                    )
                 }
                 }
 
 
-                is ChatViewModel.GetRoomErrorState -> {
+                is ChatViewModel.GetCapabilitiesErrorState -> {
                     Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
                     Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
                 }
                 }
 
 
@@ -716,6 +745,7 @@ class ChatActivity :
                     }
                     }
                     binding.messagesListView.smoothScrollToPosition(0)
                     binding.messagesListView.smoothScrollToPosition(0)
                 }
                 }
+
                 is ChatViewModel.SendChatMessageErrorState -> {
                 is ChatViewModel.SendChatMessageErrorState -> {
                     if (state.e is HttpException) {
                     if (state.e is HttpException) {
                         val code = state.e.code()
                         val code = state.e.code()
@@ -730,6 +760,7 @@ class ChatActivity :
                         }
                         }
                     }
                     }
                 }
                 }
+
                 else -> {}
                 else -> {}
             }
             }
         }
         }
@@ -753,9 +784,11 @@ class ChatActivity :
                         )
                         )
                     )
                     )
                 }
                 }
+
                 is ChatViewModel.DeleteChatMessageErrorState -> {
                 is ChatViewModel.DeleteChatMessageErrorState -> {
                     Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
                     Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
                 }
                 }
+
                 else -> {}
                 else -> {}
             }
             }
         }
         }
@@ -774,24 +807,22 @@ class ChatActivity :
                         startActivity(chatIntent)
                         startActivity(chatIntent)
                     }
                     }
                 }
                 }
+
                 is ChatViewModel.CreateRoomErrorState -> {
                 is ChatViewModel.CreateRoomErrorState -> {
                     Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
                     Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
                 }
                 }
+
                 else -> {}
                 else -> {}
             }
             }
         }
         }
 
 
-        var apiVersion = 1
-        // FIXME this is a best guess, guests would need to get the capabilities themselves
-        if (conversationUser != null) {
-            apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
-        }
-
-        chatViewModel.getFieldMapForChat.observe(this) { _ ->
-            chatViewModel.pullChatMessages(
-                credentials!!,
-                ApiUtils.getUrlForChat(apiVersion, conversationUser?.baseUrl, roomToken)
-            )
+        chatViewModel.getFieldMapForChat.observe(this) { fieldMap ->
+            if (fieldMap.isNotEmpty()) {
+                chatViewModel.pullChatMessages(
+                    credentials!!,
+                    ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken)
+                )
+            }
         }
         }
 
 
         chatViewModel.pullChatMessageViewState.observe(this) { state ->
         chatViewModel.pullChatMessageViewState.observe(this) { state ->
@@ -865,6 +896,7 @@ class ChatActivity :
                                 )
                                 )
                             )
                             )
                         }
                         }
+
                         HTTP_CODE_NOT_MODIFIED -> {
                         HTTP_CODE_NOT_MODIFIED -> {
                             processHeaderChatLastGiven(state.response, state.lookIntoFuture)
                             processHeaderChatLastGiven(state.response, state.lookIntoFuture)
                             chatViewModel.refreshChatParams(
                             chatViewModel.refreshChatParams(
@@ -875,6 +907,7 @@ class ChatActivity :
                                 )
                                 )
                             )
                             )
                         }
                         }
+
                         HTTP_CODE_PRECONDITION_FAILED -> {
                         HTTP_CODE_PRECONDITION_FAILED -> {
                             processHeaderChatLastGiven(state.response, state.lookIntoFuture)
                             processHeaderChatLastGiven(state.response, state.lookIntoFuture)
                             chatViewModel.refreshChatParams(
                             chatViewModel.refreshChatParams(
@@ -885,6 +918,7 @@ class ChatActivity :
                                 )
                                 )
                             )
                             )
                         }
                         }
+
                         else -> {}
                         else -> {}
                     }
                     }
 
 
@@ -898,12 +932,15 @@ class ChatActivity :
                         collapseSystemMessages()
                         collapseSystemMessages()
                     }
                     }
                 }
                 }
+
                 is ChatViewModel.PullChatMessageCompleteState -> {
                 is ChatViewModel.PullChatMessageCompleteState -> {
                     Log.d(TAG, "PullChatMessageCompleted")
                     Log.d(TAG, "PullChatMessageCompleted")
                 }
                 }
+
                 is ChatViewModel.PullChatMessageErrorState -> {
                 is ChatViewModel.PullChatMessageErrorState -> {
                     Log.d(TAG, "PullChatMessageError")
                     Log.d(TAG, "PullChatMessageError")
                 }
                 }
+
                 else -> {}
                 else -> {}
             }
             }
         }
         }
@@ -916,6 +953,7 @@ class ChatActivity :
                         state.reactionDeletedModel.emoji
                         state.reactionDeletedModel.emoji
                     )
                     )
                 }
                 }
+
                 else -> {}
                 else -> {}
             }
             }
         }
         }
@@ -928,6 +966,7 @@ class ChatActivity :
                         state.reactionAddedModel.emoji
                         state.reactionAddedModel.emoji
                     )
                     )
                 }
                 }
+
                 else -> {}
                 else -> {}
             }
             }
         }
         }
@@ -943,6 +982,7 @@ class ChatActivity :
                                 Snackbar.LENGTH_LONG
                                 Snackbar.LENGTH_LONG
                             ).show()
                             ).show()
                         }
                         }
+
                         HTTP_FORBIDDEN -> {
                         HTTP_FORBIDDEN -> {
                             Snackbar.make(
                             Snackbar.make(
                                 binding.root,
                                 binding.root,
@@ -950,6 +990,7 @@ class ChatActivity :
                                 Snackbar.LENGTH_LONG
                                 Snackbar.LENGTH_LONG
                             ).show()
                             ).show()
                         }
                         }
+
                         HTTP_NOT_FOUND -> {
                         HTTP_NOT_FOUND -> {
                             Snackbar.make(
                             Snackbar.make(
                                 binding.root,
                                 binding.root,
@@ -960,9 +1001,11 @@ class ChatActivity :
                     }
                     }
                     clearEditUI()
                     clearEditUI()
                 }
                 }
+
                 is ChatViewModel.EditMessageErrorState -> {
                 is ChatViewModel.EditMessageErrorState -> {
                     Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
                     Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
                 }
                 }
+
                 else -> {}
                 else -> {}
             }
             }
         }
         }
@@ -980,12 +1023,6 @@ class ChatActivity :
         webSocketInstance?.getSignalingMessageReceiver()?.addListener(localParticipantMessageListener)
         webSocketInstance?.getSignalingMessageReceiver()?.addListener(localParticipantMessageListener)
         webSocketInstance?.getSignalingMessageReceiver()?.addListener(conversationMessageListener)
         webSocketInstance?.getSignalingMessageReceiver()?.addListener(conversationMessageListener)
 
 
-        if (conversationUser?.userId != "?" &&
-            CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "mention-flag")
-        ) {
-            binding.chatToolbar.setOnClickListener { v -> showConversationInfoScreen() }
-        }
-
         initSmileyKeyboardToggler()
         initSmileyKeyboardToggler()
 
 
         themeMessageInputView()
         themeMessageInputView()
@@ -1053,7 +1090,6 @@ class ChatActivity :
             }
             }
         })
         })
 
 
-        initMessageInputView()
         loadAvatarForStatusBar()
         loadAvatarForStatusBar()
         setActionBarTitle()
         setActionBarTitle()
         viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar)
         viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar)
@@ -1061,7 +1097,7 @@ class ChatActivity :
 
 
     private fun initMessageInputView() {
     private fun initMessageInputView() {
         val filters = arrayOfNulls<InputFilter>(1)
         val filters = arrayOfNulls<InputFilter>(1)
-        val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser)
+        val lengthFilter = CapabilitiesUtil.getMessageMaxLength(spreedCapabilities)
 
 
         binding.editView.editMessageView.visibility = View.GONE
         binding.editView.editMessageView.visibility = View.GONE
 
 
@@ -1160,7 +1196,7 @@ class ChatActivity :
             clearEditUI()
             clearEditUI()
         }
         }
 
 
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) {
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_SEND)) {
             binding.messageInputView.button?.setOnLongClickListener {
             binding.messageInputView.button?.setOnLongClickListener {
                 showSendButtonMenu()
                 showSendButtonMenu()
                 true
                 true
@@ -1175,14 +1211,14 @@ class ChatActivity :
         var apiVersion = 1
         var apiVersion = 1
         // FIXME Fix API checking with guests?
         // FIXME Fix API checking with guests?
         if (conversationUser != null) {
         if (conversationUser != null) {
-            apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
+            apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
         }
         }
 
 
         chatViewModel.editChatMessage(
         chatViewModel.editChatMessage(
             credentials!!,
             credentials!!,
             ApiUtils.getUrlForChatMessage(
             ApiUtils.getUrlForChatMessage(
                 apiVersion,
                 apiVersion,
-                conversationUser?.baseUrl,
+                conversationUser?.baseUrl!!,
                 roomToken,
                 roomToken,
                 message.id
                 message.id
             ),
             ),
@@ -2016,7 +2052,7 @@ class ChatActivity :
 
 
     private fun isTypingStatusEnabled(): Boolean {
     private fun isTypingStatusEnabled(): Boolean {
         return webSocketInstance != null &&
         return webSocketInstance != null &&
-            !CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)
+            !CapabilitiesUtil.isTypingStatusPrivate(conversationUser!!)
     }
     }
 
 
     private fun setupSwipeToReply() {
     private fun setupSwipeToReply() {
@@ -2048,7 +2084,7 @@ class ChatActivity :
 
 
         if (isOneToOneConversation()) {
         if (isOneToOneConversation()) {
             var url = ApiUtils.getUrlForAvatar(
             var url = ApiUtils.getUrlForAvatar(
-                conversationUser!!.baseUrl,
+                conversationUser!!.baseUrl!!,
                 currentConversation!!.name,
                 currentConversation!!.name,
                 true
                 true
             )
             )
@@ -2097,18 +2133,19 @@ class ChatActivity :
             }
             }
 
 
             val credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser!!.token)
             val credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser!!.token)
-
-            context.imageLoader.enqueue(
-                ImageRequest.Builder(context)
-                    .data(url)
-                    .addHeader("Authorization", credentials)
-                    .transformations(CircleCropTransformation())
-                    .crossfade(true)
-                    .target(target)
-                    .memoryCachePolicy(CachePolicy.DISABLED)
-                    .diskCachePolicy(CachePolicy.DISABLED)
-                    .build()
-            )
+            if (credentials != null) {
+                context.imageLoader.enqueue(
+                    ImageRequest.Builder(context)
+                        .data(url)
+                        .addHeader("Authorization", credentials)
+                        .transformations(CircleCropTransformation())
+                        .crossfade(true)
+                        .target(target)
+                        .memoryCachePolicy(CachePolicy.DISABLED)
+                        .diskCachePolicy(CachePolicy.DISABLED)
+                        .build()
+                )
+            }
         } else {
         } else {
             binding.chatToolbar.findViewById<FrameLayout>(R.id.chat_toolbar_avatar_container).visibility = View.GONE
             binding.chatToolbar.findViewById<FrameLayout>(R.id.chat_toolbar_avatar_container).visibility = View.GONE
         }
         }
@@ -2463,7 +2500,10 @@ class ChatActivity :
 
 
         val baseUrl = message.activeUser!!.baseUrl
         val baseUrl = message.activeUser!!.baseUrl
         val userId = message.activeUser!!.userId
         val userId = message.activeUser!!.userId
-        val attachmentFolder = CapabilitiesUtilNew.getAttachmentFolder(message.activeUser!!)
+        val attachmentFolder = CapabilitiesUtil.getAttachmentFolder(
+            message.activeUser!!.capabilities!!
+                .spreedCapability!!
+        )
         val fileName = message.selectedIndividualHashMap!!["name"]
         val fileName = message.selectedIndividualHashMap!!["name"]
         var size = message.selectedIndividualHashMap!!["size"]
         var size = message.selectedIndividualHashMap!!["size"]
         if (size == null) {
         if (size == null) {
@@ -2801,16 +2841,16 @@ class ChatActivity :
 
 
     private fun shouldShowLobby(): Boolean {
     private fun shouldShowLobby(): Boolean {
         if (currentConversation != null) {
         if (currentConversation != null) {
-            return CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") &&
+            return CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
                 currentConversation?.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
                 currentConversation?.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
-                !ConversationUtils.canModerate(currentConversation!!, conversationUser!!) &&
+                !ConversationUtils.canModerate(currentConversation!!, spreedCapabilities) &&
                 !participantPermissions.canIgnoreLobby()
                 !participantPermissions.canIgnoreLobby()
         }
         }
         return false
         return false
     }
     }
 
 
     private fun disableCallButtons() {
     private fun disableCallButtons() {
-        if (CapabilitiesUtilNew.isAbleToCall(conversationUser)) {
+        if (CapabilitiesUtil.isAbleToCall(spreedCapabilities)) {
             if (conversationVoiceCallMenuItem != null && conversationVideoMenuItem != null) {
             if (conversationVoiceCallMenuItem != null && conversationVideoMenuItem != null) {
                 conversationVoiceCallMenuItem?.icon?.alpha = SEMI_TRANSPARENT_INT
                 conversationVoiceCallMenuItem?.icon?.alpha = SEMI_TRANSPARENT_INT
                 conversationVideoMenuItem?.icon?.alpha = SEMI_TRANSPARENT_INT
                 conversationVideoMenuItem?.icon?.alpha = SEMI_TRANSPARENT_INT
@@ -2823,7 +2863,7 @@ class ChatActivity :
     }
     }
 
 
     private fun enableCallButtons() {
     private fun enableCallButtons() {
-        if (CapabilitiesUtilNew.isAbleToCall(conversationUser)) {
+        if (CapabilitiesUtil.isAbleToCall(spreedCapabilities)) {
             if (conversationVoiceCallMenuItem != null && conversationVideoMenuItem != null) {
             if (conversationVoiceCallMenuItem != null && conversationVideoMenuItem != null) {
                 conversationVoiceCallMenuItem?.icon?.alpha = FULLY_OPAQUE_INT
                 conversationVoiceCallMenuItem?.icon?.alpha = FULLY_OPAQUE_INT
                 conversationVideoMenuItem?.icon?.alpha = FULLY_OPAQUE_INT
                 conversationVideoMenuItem?.icon?.alpha = FULLY_OPAQUE_INT
@@ -2843,7 +2883,7 @@ class ChatActivity :
 
 
     private fun checkLobbyState() {
     private fun checkLobbyState() {
         if (currentConversation != null &&
         if (currentConversation != null &&
-            ConversationUtils.isLobbyViewApplicable(currentConversation!!, conversationUser!!)
+            ConversationUtils.isLobbyViewApplicable(currentConversation!!, spreedCapabilities)
         ) {
         ) {
             if (shouldShowLobby()) {
             if (shouldShowLobby()) {
                 binding.lobby.lobbyView.visibility = View.VISIBLE
                 binding.lobby.lobbyView.visibility = View.VISIBLE
@@ -3252,6 +3292,7 @@ class ChatActivity :
 
 
         val intent = Intent(this, LocationPickerActivity::class.java)
         val intent = Intent(this, LocationPickerActivity::class.java)
         intent.putExtra(KEY_ROOM_TOKEN, roomToken)
         intent.putExtra(KEY_ROOM_TOKEN, roomToken)
+        intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
         startActivity(intent)
         startActivity(intent)
     }
     }
 
 
@@ -3270,7 +3311,7 @@ class ChatActivity :
         val elevation = MENTION_AUTO_COMPLETE_ELEVATION
         val elevation = MENTION_AUTO_COMPLETE_ELEVATION
         resources?.let {
         resources?.let {
             val backgroundDrawable = ColorDrawable(it.getColor(R.color.bg_default, null))
             val backgroundDrawable = ColorDrawable(it.getColor(R.color.bg_default, null))
-            val presenter = MentionAutocompletePresenter(this, roomToken)
+            val presenter = MentionAutocompletePresenter(this, roomToken, chatApiVersion)
             val callback = MentionAutocompleteCallback(
             val callback = MentionAutocompleteCallback(
                 this,
                 this,
                 conversationUser!!,
                 conversationUser!!,
@@ -3430,12 +3471,6 @@ class ChatActivity :
 
 
         if (!validSessionId()) {
         if (!validSessionId()) {
             Log.d(TAG, "sessionID was not valid -> joinRoom")
             Log.d(TAG, "sessionID was not valid -> joinRoom")
-            var apiVersion = 1
-            // FIXME Fix API checking with guests?
-            if (conversationUser != null) {
-                apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
-            }
-
             val startNanoTime = System.nanoTime()
             val startNanoTime = System.nanoTime()
             Log.d(TAG, "joinRoomWithPassword - joinRoom - calling: $startNanoTime")
             Log.d(TAG, "joinRoomWithPassword - joinRoom - calling: $startNanoTime")
 
 
@@ -3458,7 +3493,7 @@ class ChatActivity :
         var apiVersion = 1
         var apiVersion = 1
         // FIXME Fix API checking with guests?
         // FIXME Fix API checking with guests?
         if (conversationUser != null) {
         if (conversationUser != null) {
-            apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+            apiVersion = ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1))
         }
         }
 
 
         val startNanoTime = System.nanoTime()
         val startNanoTime = System.nanoTime()
@@ -3467,7 +3502,7 @@ class ChatActivity :
             credentials!!,
             credentials!!,
             ApiUtils.getUrlForParticipantsActive(
             ApiUtils.getUrlForParticipantsActive(
                 apiVersion,
                 apiVersion,
-                conversationUser?.baseUrl,
+                conversationUser?.baseUrl!!,
                 roomToken
                 roomToken
             ),
             ),
             funToCallWhenLeaveSuccessful
             funToCallWhenLeaveSuccessful
@@ -3513,11 +3548,9 @@ class ChatActivity :
 
 
     private fun sendMessage(message: CharSequence, replyTo: Int?, sendWithoutNotification: Boolean) {
     private fun sendMessage(message: CharSequence, replyTo: Int?, sendWithoutNotification: Boolean) {
         if (conversationUser != null) {
         if (conversationUser != null) {
-            val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
-
             chatViewModel.sendChatMessage(
             chatViewModel.sendChatMessage(
                 credentials!!,
                 credentials!!,
-                ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl, roomToken),
+                ApiUtils.getUrlForChat(chatApiVersion, conversationUser!!.baseUrl!!, roomToken),
                 message,
                 message,
                 conversationUser!!.displayName ?: "",
                 conversationUser!!.displayName ?: "",
                 replyTo ?: 0,
                 replyTo ?: 0,
@@ -3637,7 +3670,7 @@ class ChatActivity :
             }
             }
         }
         }
 
 
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "message-expiration")) {
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)) {
             deleteExpiredMessages()
             deleteExpiredMessages()
         }
         }
     }
     }
@@ -3871,53 +3904,58 @@ class ChatActivity :
         if (conversationUser?.userId == "?") {
         if (conversationUser?.userId == "?") {
             menu.removeItem(R.id.conversation_info)
             menu.removeItem(R.id.conversation_info)
         } else {
         } else {
-            conversationInfoMenuItem = menu.findItem(R.id.conversation_info)
+            loadAvatarForStatusBar()
+            setActionBarTitle()
+        }
+        return true
+    }
+
+    override fun onPrepareOptionsMenu(menu: Menu): Boolean {
+        super.onPrepareOptionsMenu(menu)
 
 
-            if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "rich-object-list-media")) {
+        if (this::spreedCapabilities.isInitialized) {
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.READ_ONLY_ROOMS)) {
+                checkShowCallButtons()
+            }
+
+            val searchItem = menu.findItem(R.id.conversation_search)
+            searchItem.isVisible = CapabilitiesUtil.isUnifiedSearchAvailable(spreedCapabilities)
+
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(
+                    spreedCapabilities,
+                    SpreedFeatures.RICH_OBJECT_LIST_MEDIA
+                )
+            ) {
                 conversationSharedItemsItem = menu.findItem(R.id.shared_items)
                 conversationSharedItemsItem = menu.findItem(R.id.shared_items)
             } else {
             } else {
                 menu.removeItem(R.id.shared_items)
                 menu.removeItem(R.id.shared_items)
             }
             }
 
 
-            loadAvatarForStatusBar()
-            setActionBarTitle()
-        }
-
-        if (CapabilitiesUtilNew.isAbleToCall(conversationUser)) {
-            conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
-            conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
+            if (CapabilitiesUtil.isAbleToCall(spreedCapabilities)) {
+                conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
+                conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
 
 
-            if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-call")) {
-                Handler().post {
-                    findViewById<View?>(R.id.conversation_voice_call)?.setOnLongClickListener {
-                        showCallButtonMenu(true)
-                        true
+                if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_CALL)) {
+                    Handler().post {
+                        findViewById<View?>(R.id.conversation_voice_call)?.setOnLongClickListener {
+                            showCallButtonMenu(true)
+                            true
+                        }
                     }
                     }
-                }
 
 
-                Handler().post {
-                    findViewById<View?>(R.id.conversation_video_call)?.setOnLongClickListener {
-                        showCallButtonMenu(false)
-                        true
+                    Handler().post {
+                        findViewById<View?>(R.id.conversation_video_call)?.setOnLongClickListener {
+                            showCallButtonMenu(false)
+                            true
+                        }
                     }
                     }
                 }
                 }
+            } else {
+                menu.removeItem(R.id.conversation_video_call)
+                menu.removeItem(R.id.conversation_voice_call)
             }
             }
-        } else {
-            menu.removeItem(R.id.conversation_video_call)
-            menu.removeItem(R.id.conversation_voice_call)
         }
         }
-        return true
-    }
 
 
-    override fun onPrepareOptionsMenu(menu: Menu): Boolean {
-        super.onPrepareOptionsMenu(menu)
-        conversationUser?.let {
-            if (CapabilitiesUtilNew.hasSpreedFeatureCapability(it, "read-only-rooms")) {
-                checkShowCallButtons()
-            }
-            val searchItem = menu.findItem(R.id.conversation_search)
-            searchItem.isVisible = CapabilitiesUtilNew.isUnifiedSearchAvailable(it)
-        }
         return true
         return true
     }
     }
 
 
@@ -4054,7 +4092,7 @@ class ChatActivity :
     private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
     private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
         currentConversation?.let {
         currentConversation?.let {
             if (conversationUser != null) {
             if (conversationUser != null) {
-                val pp = ParticipantPermissions(conversationUser!!, it)
+                val pp = ParticipantPermissions(spreedCapabilities, it)
                 if (!pp.canStartCall() && currentConversation?.hasCall == false) {
                 if (!pp.canStartCall() && currentConversation?.hasCall == false) {
                     Snackbar.make(binding.root, R.string.startCallForbidden, Snackbar.LENGTH_LONG).show()
                     Snackbar.make(binding.root, R.string.startCallForbidden, Snackbar.LENGTH_LONG).show()
                 } else {
                 } else {
@@ -4074,7 +4112,7 @@ class ChatActivity :
             bundle.putString(KEY_ROOM_TOKEN, roomToken)
             bundle.putString(KEY_ROOM_TOKEN, roomToken)
             bundle.putString(KEY_ROOM_ID, roomId)
             bundle.putString(KEY_ROOM_ID, roomId)
             bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
             bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
-            bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl)
+            bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl!!)
             bundle.putString(KEY_CONVERSATION_NAME, it.displayName)
             bundle.putString(KEY_CONVERSATION_NAME, it.displayName)
             bundle.putInt(KEY_RECORDING_STATE, it.callRecording)
             bundle.putInt(KEY_RECORDING_STATE, it.callRecording)
             bundle.putBoolean(KEY_IS_MODERATOR, ConversationUtils.isParticipantOwnerOrModerator(it))
             bundle.putBoolean(KEY_IS_MODERATOR, ConversationUtils.isParticipantOwnerOrModerator(it))
@@ -4147,7 +4185,8 @@ class ChatActivity :
                 conversationUser,
                 conversationUser,
                 currentConversation,
                 currentConversation,
                 isShowMessageDeletionButton(message),
                 isShowMessageDeletionButton(message),
-                participantPermissions.hasChatPermission()
+                participantPermissions.hasChatPermission(),
+                spreedCapabilities
             ).show()
             ).show()
         }
         }
     }
     }
@@ -4156,7 +4195,7 @@ class ChatActivity :
         return ChatMessage.MessageType.SYSTEM_MESSAGE == message.getCalculateMessageType()
         return ChatMessage.MessageType.SYSTEM_MESSAGE == message.getCalculateMessageType()
     }
     }
 
 
-    fun deleteMessage(message: IMessage?) {
+    fun deleteMessage(message: IMessage) {
         if (!participantPermissions.hasChatPermission()) {
         if (!participantPermissions.hasChatPermission()) {
             Log.w(
             Log.w(
                 TAG,
                 TAG,
@@ -4168,28 +4207,28 @@ class ChatActivity :
             var apiVersion = 1
             var apiVersion = 1
             // FIXME Fix API checking with guests?
             // FIXME Fix API checking with guests?
             if (conversationUser != null) {
             if (conversationUser != null) {
-                apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
+                apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
             }
             }
 
 
             chatViewModel.deleteChatMessages(
             chatViewModel.deleteChatMessages(
                 credentials!!,
                 credentials!!,
                 ApiUtils.getUrlForChatMessage(
                 ApiUtils.getUrlForChatMessage(
                     apiVersion,
                     apiVersion,
-                    conversationUser?.baseUrl,
+                    conversationUser?.baseUrl!!,
                     roomToken,
                     roomToken,
-                    message?.id
+                    message.id!!
                 ),
                 ),
-                message?.id!!
+                message.id!!
             )
             )
         }
         }
     }
     }
 
 
     fun replyPrivately(message: IMessage?) {
     fun replyPrivately(message: IMessage?) {
         val apiVersion =
         val apiVersion =
-            ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+            ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1))
         val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
         val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
             apiVersion,
             apiVersion,
-            conversationUser?.baseUrl,
+            conversationUser?.baseUrl!!,
             "1",
             "1",
             null,
             null,
             message?.user?.id?.substring(INVITE_LENGTH),
             message?.user?.id?.substring(INVITE_LENGTH),
@@ -4215,10 +4254,14 @@ class ChatActivity :
 
 
     fun remindMeLater(message: ChatMessage?) {
     fun remindMeLater(message: ChatMessage?) {
         Log.d(TAG, "remindMeLater called")
         Log.d(TAG, "remindMeLater called")
+
+        val chatApiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(ApiUtils.API_V1, 1))
+
         val newFragment: DialogFragment = DateTimePickerFragment.newInstance(
         val newFragment: DialogFragment = DateTimePickerFragment.newInstance(
             roomToken,
             roomToken,
             message!!.id,
             message!!.id,
-            chatViewModel
+            chatViewModel,
+            chatApiVersion
         )
         )
         newFragment.show(supportFragmentManager, DateTimePickerFragment.TAG)
         newFragment.show(supportFragmentManager, DateTimePickerFragment.TAG)
     }
     }
@@ -4229,8 +4272,8 @@ class ChatActivity :
             chatViewModel.setChatReadMarker(
             chatViewModel.setChatReadMarker(
                 credentials!!,
                 credentials!!,
                 ApiUtils.getUrlForChatReadMarker(
                 ApiUtils.getUrlForChatReadMarker(
-                    ApiUtils.getChatApiVersion(conversationUser, intArrayOf(ApiUtils.APIv1)),
-                    conversationUser?.baseUrl,
+                    ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(ApiUtils.API_V1)),
+                    conversationUser?.baseUrl!!,
                     roomToken
                     roomToken
                 ),
                 ),
                 chatMessage.previousMessageId
                 chatMessage.previousMessageId
@@ -4312,10 +4355,10 @@ class ChatActivity :
     }
     }
 
 
     fun shareToNotes(message: ChatMessage, roomToken: String) {
     fun shareToNotes(message: ChatMessage, roomToken: String) {
-        val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
+        val apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
         val type = message.getCalculateMessageType()
         val type = message.getCalculateMessageType()
         var shareUri: Uri? = null
         var shareUri: Uri? = null
-        var data: HashMap<String?, String?>?
+        val data: HashMap<String?, String?>?
         var metaData: String = ""
         var metaData: String = ""
         var objectId: String = ""
         var objectId: String = ""
         if (message.hasFileAttachment()) {
         if (message.hasFileAttachment()) {
@@ -4335,9 +4378,9 @@ class ChatActivity :
         } else if (message.hasGeoLocation()) {
         } else if (message.hasGeoLocation()) {
             data = message.messageParameters?.get("object")
             data = message.messageParameters?.get("object")
             objectId = data?.get("id")!!
             objectId = data?.get("id")!!
-            val name = data.get("name")!!
-            val lat = data.get("latitude")!!
-            val lon = data.get("longitude")!!
+            val name = data["name"]!!
+            val lat = data["latitude"]!!
+            val lon = data["longitude"]!!
             metaData =
             metaData =
                 "{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," +
                 "{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," +
                 "\"longitude\":\"$lon\",\"name\":\"$name\"}"
                 "\"longitude\":\"$lon\",\"name\":\"$name\"}"
@@ -4348,6 +4391,7 @@ class ChatActivity :
                 uploadFile(shareUri.toString(), true, token = roomToken)
                 uploadFile(shareUri.toString(), true, token = roomToken)
                 Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
                 Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
             }
             }
+
             ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE -> {
             ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE -> {
                 val caption = if (message.message != "{file}") message.message else ""
                 val caption = if (message.message != "{file}") message.message else ""
                 if (null != shareUri) {
                 if (null != shareUri) {
@@ -4364,25 +4408,28 @@ class ChatActivity :
                     }
                     }
                 }
                 }
             }
             }
+
             ChatMessage.MessageType.SINGLE_NC_GEOLOCATION_MESSAGE -> {
             ChatMessage.MessageType.SINGLE_NC_GEOLOCATION_MESSAGE -> {
                 chatViewModel.shareLocationToNotes(
                 chatViewModel.shareLocationToNotes(
                     credentials!!,
                     credentials!!,
-                    ApiUtils.getUrlToSendLocation(apiVersion, conversationUser!!.baseUrl, roomToken),
+                    ApiUtils.getUrlToSendLocation(apiVersion, conversationUser!!.baseUrl!!, roomToken),
                     "geo-location",
                     "geo-location",
                     objectId,
                     objectId,
                     metaData
                     metaData
                 )
                 )
                 Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
                 Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
             }
             }
+
             ChatMessage.MessageType.REGULAR_TEXT_MESSAGE -> {
             ChatMessage.MessageType.REGULAR_TEXT_MESSAGE -> {
                 chatViewModel.shareToNotes(
                 chatViewModel.shareToNotes(
                     credentials!!,
                     credentials!!,
-                    ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl, roomToken),
+                    ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl!!, roomToken),
                     message.message!!,
                     message.message!!,
                     conversationUser!!.displayName!!
                     conversationUser!!.displayName!!
                 )
                 )
                 Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
                 Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
             }
             }
+
             else -> {}
             else -> {}
         }
         }
     }
     }
@@ -4456,7 +4503,10 @@ class ChatActivity :
     }
     }
 
 
     private fun showMicrophoneButton(show: Boolean) {
     private fun showMicrophoneButton(show: Boolean) {
-        if (show && CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "voice-message-sharing")) {
+        if (show && CapabilitiesUtil.hasSpreedFeatureCapability(
+                spreedCapabilities, SpreedFeatures.VOICE_MESSAGE_SHARING
+            )
+        ) {
             Log.d(TAG, "Microphone shown")
             Log.d(TAG, "Microphone shown")
             binding.messageInputView.messageSendButton.visibility = View.GONE
             binding.messageInputView.messageSendButton.visibility = View.GONE
             binding.messageInputView.recordAudioButton.visibility = View.VISIBLE
             binding.messageInputView.recordAudioButton.visibility = View.VISIBLE
@@ -4542,7 +4592,7 @@ class ChatActivity :
             !isUserAllowedByPrivileges -> false
             !isUserAllowedByPrivileges -> false
             message.systemMessageType != ChatMessage.SystemMessageType.DUMMY -> false
             message.systemMessageType != ChatMessage.SystemMessageType.DUMMY -> false
             message.isDeleted -> false
             message.isDeleted -> false
-            !CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "delete-messages") -> false
+            !CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.DELETE_MESSAGES) -> false
             !participantPermissions.hasChatPermission() -> false
             !participantPermissions.hasChatPermission() -> false
             else -> true
             else -> true
         }
         }
@@ -4554,7 +4604,7 @@ class ChatActivity :
         val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) {
         val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) {
             true
             true
         } else {
         } else {
-            ConversationUtils.canModerate(currentConversation!!, conversationUser!!)
+            ConversationUtils.canModerate(currentConversation!!, spreedCapabilities)
         }
         }
         return isUserAllowedByPrivileges
         return isUserAllowedByPrivileges
     }
     }
@@ -4628,12 +4678,12 @@ class ChatActivity :
             var apiVersion = 1
             var apiVersion = 1
             // FIXME Fix API checking with guests?
             // FIXME Fix API checking with guests?
             if (conversationUser != null) {
             if (conversationUser != null) {
-                apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+                apiVersion = ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1))
             }
             }
 
 
             val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
             val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
                 apiVersion,
                 apiVersion,
-                conversationUser?.baseUrl,
+                conversationUser?.baseUrl!!,
                 "1",
                 "1",
                 null,
                 null,
                 userMentionClickEvent.userId,
                 userMentionClickEvent.userId,

+ 11 - 3
app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt

@@ -22,6 +22,7 @@ package com.nextcloud.talk.chat.data
 
 
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
 import com.nextcloud.talk.models.json.conversations.RoomOverall
 import com.nextcloud.talk.models.json.conversations.RoomOverall
 import com.nextcloud.talk.models.json.conversations.RoomsOverall
 import com.nextcloud.talk.models.json.conversations.RoomsOverall
@@ -33,10 +34,17 @@ import retrofit2.Response
 @Suppress("LongParameterList", "TooManyFunctions")
 @Suppress("LongParameterList", "TooManyFunctions")
 interface ChatRepository {
 interface ChatRepository {
     fun getRoom(user: User, roomToken: String): Observable<ConversationModel>
     fun getRoom(user: User, roomToken: String): Observable<ConversationModel>
+    fun getCapabilities(user: User, roomToken: String): Observable<SpreedCapability>
     fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel>
     fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel>
-    fun setReminder(user: User, roomToken: String, messageId: String, timeStamp: Int): Observable<Reminder>
-    fun getReminder(user: User, roomToken: String, messageId: String): Observable<Reminder>
-    fun deleteReminder(user: User, roomToken: String, messageId: String): Observable<GenericOverall>
+    fun setReminder(
+        user: User,
+        roomToken: String,
+        messageId: String,
+        timeStamp: Int,
+        chatApiVersion: Int
+    ): Observable<Reminder>
+    fun getReminder(user: User, roomToken: String, messageId: String, apiVersion: Int): Observable<Reminder>
+    fun deleteReminder(user: User, roomToken: String, messageId: String, apiVersion: Int): Observable<GenericOverall>
     fun shareToNotes(
     fun shareToNotes(
         credentials: String,
         credentials: String,
         url: String,
         url: String,

+ 42 - 18
app/src/main/java/com/nextcloud/talk/chat/data/network/NetworkChatRepositoryImpl.kt

@@ -24,6 +24,7 @@ import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.chat.data.ChatRepository
 import com.nextcloud.talk.chat.data.ChatRepository
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
 import com.nextcloud.talk.models.json.conversations.RoomOverall
 import com.nextcloud.talk.models.json.conversations.RoomOverall
 import com.nextcloud.talk.models.json.conversations.RoomsOverall
 import com.nextcloud.talk.models.json.conversations.RoomsOverall
@@ -35,55 +36,78 @@ import retrofit2.Response
 
 
 class NetworkChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
 class NetworkChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
     override fun getRoom(user: User, roomToken: String): Observable<ConversationModel> {
     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))
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
+        val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
 
 
         return ncApi.getRoom(
         return ncApi.getRoom(
             credentials,
             credentials,
-            ApiUtils.getUrlForRoom(apiVersion, user.baseUrl, roomToken)
+            ApiUtils.getUrlForRoom(apiVersion, user.baseUrl!!, roomToken)
         ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
         ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
     }
     }
 
 
+    override fun getCapabilities(user: User, roomToken: String): Observable<SpreedCapability> {
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
+        val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
+
+        return ncApi.getRoomCapabilities(
+            credentials,
+            ApiUtils.getUrlForRoomCapabilities(apiVersion, user.baseUrl!!, roomToken)
+        ).map { it.ocs?.data }
+    }
+
     override fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel> {
     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))
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
+        val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, 1))
 
 
         return ncApi.joinRoom(
         return ncApi.joinRoom(
             credentials,
             credentials,
-            ApiUtils.getUrlForParticipantsActive(apiVersion, user.baseUrl, roomToken),
+            ApiUtils.getUrlForParticipantsActive(apiVersion, user.baseUrl!!, roomToken),
             roomPassword
             roomPassword
         ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
         ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
     }
     }
 
 
-    override fun setReminder(user: User, roomToken: String, messageId: String, timeStamp: Int): Observable<Reminder> {
-        val credentials: String = ApiUtils.getCredentials(user.username, user.token)
-        val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1))
+    override fun setReminder(
+        user: User,
+        roomToken: String,
+        messageId: String,
+        timeStamp: Int,
+        chatApiVersion: Int
+    ): Observable<Reminder> {
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
         return ncApi.setReminder(
         return ncApi.setReminder(
             credentials,
             credentials,
-            ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion),
+            ApiUtils.getUrlForReminder(user, roomToken, messageId, chatApiVersion),
             timeStamp
             timeStamp
         ).map {
         ).map {
             it.ocs!!.data
             it.ocs!!.data
         }
         }
     }
     }
 
 
-    override fun getReminder(user: User, roomToken: String, messageId: String): Observable<Reminder> {
-        val credentials: String = ApiUtils.getCredentials(user.username, user.token)
-        val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1))
+    override fun getReminder(
+        user: User,
+        roomToken: String,
+        messageId: String,
+        chatApiVersion: Int
+    ): Observable<Reminder> {
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
         return ncApi.getReminder(
         return ncApi.getReminder(
             credentials,
             credentials,
-            ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion)
+            ApiUtils.getUrlForReminder(user, roomToken, messageId, chatApiVersion)
         ).map {
         ).map {
             it.ocs!!.data
             it.ocs!!.data
         }
         }
     }
     }
 
 
-    override fun deleteReminder(user: User, roomToken: String, messageId: String): Observable<GenericOverall> {
-        val credentials: String = ApiUtils.getCredentials(user.username, user.token)
-        val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1))
+    override fun deleteReminder(
+        user: User,
+        roomToken: String,
+        messageId: String,
+        chatApiVersion: Int
+    ): Observable<GenericOverall> {
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
         return ncApi.deleteReminder(
         return ncApi.deleteReminder(
             credentials,
             credentials,
-            ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion)
+            ApiUtils.getUrlForReminder(user, roomToken, messageId, chatApiVersion)
         ).map {
         ).map {
             it
             it
         }
         }

+ 77 - 38
app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt

@@ -31,6 +31,7 @@ import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.models.domain.ReactionAddedModel
 import com.nextcloud.talk.models.domain.ReactionAddedModel
 import com.nextcloud.talk.models.domain.ReactionDeletedModel
 import com.nextcloud.talk.models.domain.ReactionDeletedModel
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
 import com.nextcloud.talk.models.json.conversations.RoomOverall
 import com.nextcloud.talk.models.json.conversations.RoomOverall
@@ -105,6 +106,14 @@ class ChatViewModel @Inject constructor(
     val getRoomViewState: LiveData<ViewState>
     val getRoomViewState: LiveData<ViewState>
         get() = _getRoomViewState
         get() = _getRoomViewState
 
 
+    object GetCapabilitiesStartState : ViewState
+    object GetCapabilitiesErrorState : ViewState
+    open class GetCapabilitiesSuccessState(val spreedCapabilities: SpreedCapability) : ViewState
+
+    private val _getCapabilitiesViewState: MutableLiveData<ViewState> = MutableLiveData(GetCapabilitiesStartState)
+    val getCapabilitiesViewState: LiveData<ViewState>
+        get() = _getCapabilitiesViewState
+
     object JoinRoomStartState : ViewState
     object JoinRoomStartState : ViewState
     object JoinRoomErrorState : ViewState
     object JoinRoomErrorState : ViewState
     open class JoinRoomSuccessState(val conversationModel: ConversationModel) : ViewState
     open class JoinRoomSuccessState(val conversationModel: ConversationModel) : ViewState
@@ -184,6 +193,36 @@ class ChatViewModel @Inject constructor(
             ?.subscribe(GetRoomObserver())
             ?.subscribe(GetRoomObserver())
     }
     }
 
 
+    fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
+        _getCapabilitiesViewState.value = GetCapabilitiesStartState
+
+        if (conversationModel.remoteServer.isNullOrEmpty()) {
+            _getCapabilitiesViewState.value = GetCapabilitiesSuccessState(user.capabilities!!.spreedCapability!!)
+        } else {
+            chatRepository.getCapabilities(user, token)
+                .subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.subscribe(object : Observer<SpreedCapability> {
+                    override fun onSubscribe(d: Disposable) {
+                        LifeCycleObserver.disposableSet.add(d)
+                    }
+
+                    override fun onNext(spreedCapabilities: SpreedCapability) {
+                        _getCapabilitiesViewState.value = GetCapabilitiesSuccessState(spreedCapabilities)
+                    }
+
+                    override fun onError(e: Throwable) {
+                        Log.e(TAG, "Error when fetching spreed capabilities", e)
+                        _getCapabilitiesViewState.value = GetCapabilitiesErrorState
+                    }
+
+                    override fun onComplete() {
+                        // unused atm
+                    }
+                })
+        }
+    }
+
     fun joinRoom(user: User, token: String, roomPassword: String) {
     fun joinRoom(user: User, token: String, roomPassword: String) {
         _joinRoomViewState.value = JoinRoomStartState
         _joinRoomViewState.value = JoinRoomStartState
         chatRepository.joinRoom(user, token, roomPassword)
         chatRepository.joinRoom(user, token, roomPassword)
@@ -193,6 +232,43 @@ class ChatViewModel @Inject constructor(
             ?.subscribe(JoinRoomObserver())
             ?.subscribe(JoinRoomObserver())
     }
     }
 
 
+    fun setReminder(user: User, roomToken: String, messageId: String, timestamp: Int, chatApiVersion: Int) {
+        chatRepository.setReminder(user, roomToken, messageId, timestamp, chatApiVersion)
+            .subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(SetReminderObserver())
+    }
+
+    fun getReminder(user: User, roomToken: String, messageId: String, chatApiVersion: Int) {
+        chatRepository.getReminder(user, roomToken, messageId, chatApiVersion)
+            .subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(GetReminderObserver())
+    }
+
+    fun deleteReminder(user: User, roomToken: String, messageId: String, chatApiVersion: Int) {
+        chatRepository.deleteReminder(user, roomToken, messageId, chatApiVersion)
+            .subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(object : Observer<GenericOverall> {
+                override fun onSubscribe(d: Disposable) {
+                    LifeCycleObserver.disposableSet.add(d)
+                }
+
+                override fun onNext(genericOverall: GenericOverall) {
+                    _getReminderExistState.value = GetReminderStartState
+                }
+
+                override fun onError(e: Throwable) {
+                    Log.d(TAG, "Error when deleting reminder", e)
+                }
+
+                override fun onComplete() {
+                    // unused atm
+                }
+            })
+    }
+
     fun leaveRoom(credentials: String, url: String, funToCallWhenLeaveSuccessful: (() -> Unit)?) {
     fun leaveRoom(credentials: String, url: String, funToCallWhenLeaveSuccessful: (() -> Unit)?) {
         val startNanoTime = System.nanoTime()
         val startNanoTime = System.nanoTime()
         chatRepository.leaveRoom(credentials, url)
         chatRepository.leaveRoom(credentials, url)
@@ -357,43 +433,6 @@ class ChatViewModel @Inject constructor(
             })
             })
     }
     }
 
 
-    fun setReminder(user: User, roomToken: String, messageId: String, timestamp: Int) {
-        chatRepository.setReminder(user, roomToken, messageId, timestamp)
-            .subscribeOn(Schedulers.io())
-            ?.observeOn(AndroidSchedulers.mainThread())
-            ?.subscribe(SetReminderObserver())
-    }
-
-    fun getReminder(user: User, roomToken: String, messageId: String) {
-        chatRepository.getReminder(user, roomToken, messageId)
-            .subscribeOn(Schedulers.io())
-            ?.observeOn(AndroidSchedulers.mainThread())
-            ?.subscribe(GetReminderObserver())
-    }
-
-    fun deleteReminder(user: User, roomToken: String, messageId: String) {
-        chatRepository.deleteReminder(user, roomToken, messageId)
-            .subscribeOn(Schedulers.io())
-            ?.observeOn(AndroidSchedulers.mainThread())
-            ?.subscribe(object : Observer<GenericOverall> {
-                override fun onSubscribe(d: Disposable) {
-                    LifeCycleObserver.disposableSet.add(d)
-                }
-
-                override fun onNext(genericOverall: GenericOverall) {
-                    _getReminderExistState.value = GetReminderStartState
-                }
-
-                override fun onError(e: Throwable) {
-                    Log.d(TAG, "Error when deleting reminder $e")
-                }
-
-                override fun onComplete() {
-                    // unused atm
-                }
-            })
-    }
-
     fun shareToNotes(credentials: String, url: String, message: String, displayName: String) {
     fun shareToNotes(credentials: String, url: String, message: String, displayName: String) {
         chatRepository.shareToNotes(credentials, url, message, displayName)
         chatRepository.shareToNotes(credentials, url, message, displayName)
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
@@ -522,7 +561,7 @@ class ChatViewModel @Inject constructor(
 
 
     inner class GetRoomObserver : Observer<ConversationModel> {
     inner class GetRoomObserver : Observer<ConversationModel> {
         override fun onSubscribe(d: Disposable) {
         override fun onSubscribe(d: Disposable) {
-            LifeCycleObserver.disposableSet.add(d)
+            // unused atm
         }
         }
 
 
         override fun onNext(conversationModel: ConversationModel) {
         override fun onNext(conversationModel: ConversationModel) {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFolderListingOperation.kt

@@ -65,7 +65,7 @@ class ReadFolderListingOperation(okHttpClient: OkHttpClient, currentUser: User,
                 ApiUtils.getCredentials(
                 ApiUtils.getCredentials(
                     currentUser.username,
                     currentUser.username,
                     currentUser.token
                     currentUser.token
-                ),
+                )!!,
                 "Authorization"
                 "Authorization"
             )
             )
         )
         )

+ 23 - 10
app/src/main/java/com/nextcloud/talk/contacts/ContactsActivity.kt

@@ -68,9 +68,10 @@ import com.nextcloud.talk.models.json.participants.Participant
 import com.nextcloud.talk.openconversations.ListOpenConversationsActivity
 import com.nextcloud.talk.openconversations.ListOpenConversationsActivity
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.UserIdUtils.getIdForUser
 import com.nextcloud.talk.utils.UserIdUtils.getIdForUser
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.SelectableAdapter
 import eu.davidea.flexibleadapter.SelectableAdapter
 import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
 import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
@@ -318,10 +319,10 @@ class ContactsActivity :
     }
     }
 
 
     private fun createRoom(roomType: String, sourceType: String?, userId: String) {
     private fun createRoom(roomType: String, sourceType: String?, userId: String) {
-        val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser!!, intArrayOf(ApiUtils.API_V4, 1))
         val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
         val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
             apiVersion,
             apiVersion,
-            currentUser!!.baseUrl,
+            currentUser!!.baseUrl!!,
             roomType,
             roomType,
             sourceType,
             sourceType,
             userId,
             userId,
@@ -438,7 +439,7 @@ class ContactsActivity :
         userHeaderItems = HashMap()
         userHeaderItems = HashMap()
         val query = adapter!!.getFilter(String::class.java)
         val query = adapter!!.getFilter(String::class.java)
         val retrofitBucket: RetrofitBucket =
         val retrofitBucket: RetrofitBucket =
-            ApiUtils.getRetrofitBucketForContactsSearchFor14(currentUser!!.baseUrl, query)
+            ApiUtils.getRetrofitBucketForContactsSearchFor14(currentUser!!.baseUrl!!, query)
         val modifiedQueryMap: HashMap<String, Any?> = HashMap(retrofitBucket.queryMap)
         val modifiedQueryMap: HashMap<String, Any?> = HashMap(retrofitBucket.queryMap)
         modifiedQueryMap["limit"] = CONTACTS_BATCH_SIZE
         modifiedQueryMap["limit"] = CONTACTS_BATCH_SIZE
         if (isAddingParticipantsView) {
         if (isAddingParticipantsView) {
@@ -450,13 +451,21 @@ class ContactsActivity :
         if (!isAddingParticipantsView) {
         if (!isAddingParticipantsView) {
             // groups
             // groups
             shareTypesList.add("1")
             shareTypesList.add("1")
-        } else if (CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails")) {
+        } else if (CapabilitiesUtil.hasSpreedFeatureCapability(
+                currentUser?.capabilities?.spreedCapability!!,
+                SpreedFeatures.INVITE_GROUPS_AND_MAILS
+            )
+        ) {
             // groups
             // groups
             shareTypesList.add("1")
             shareTypesList.add("1")
             // emails
             // emails
             shareTypesList.add("4")
             shareTypesList.add("4")
         }
         }
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "circles-support")) {
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(
+                currentUser?.capabilities?.spreedCapability!!,
+                SpreedFeatures.CIRCLES_SUPPORT
+            )
+        ) {
             // circles
             // circles
             shareTypesList.add("7")
             shareTypesList.add("7")
         }
         }
@@ -745,8 +754,12 @@ class ContactsActivity :
     private fun updateSelection(contactItem: ContactItem) {
     private fun updateSelection(contactItem: ContactItem) {
         contactItem.model.selected = !contactItem.model.selected
         contactItem.model.selected = !contactItem.model.selected
         updateSelectionLists(contactItem.model)
         updateSelectionLists(contactItem.model)
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "last-room-activity") &&
-            !CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails") &&
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(
+                currentUser?.capabilities?.spreedCapability!!, SpreedFeatures.LAST_ROOM_ACTIVITY
+            ) &&
+            !CapabilitiesUtil.hasSpreedFeatureCapability(
+                currentUser?.capabilities?.spreedCapability!!, SpreedFeatures.INVITE_GROUPS_AND_MAILS
+            ) &&
             isValidGroupSelection(contactItem, contactItem.model, adapter)
             isValidGroupSelection(contactItem, contactItem.model, adapter)
         ) {
         ) {
             val currentItems: List<ContactItem> = adapter?.currentItems as List<ContactItem>
             val currentItems: List<ContactItem> = adapter?.currentItems as List<ContactItem>
@@ -771,10 +784,10 @@ class ContactsActivity :
         if ("groups" == contactItem.model.source) {
         if ("groups" == contactItem.model.source) {
             roomType = "2"
             roomType = "2"
         }
         }
-        val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser!!, intArrayOf(ApiUtils.API_V4, 1))
         val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
         val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
             apiVersion,
             apiVersion,
-            currentUser!!.baseUrl,
+            currentUser!!.baseUrl!!,
             roomType,
             roomType,
             null,
             null,
             contactItem.model.calculatedActorId,
             contactItem.model.calculatedActorId,

+ 6 - 6
app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt

@@ -36,16 +36,16 @@ class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
     ConversationRepository {
     ConversationRepository {
 
 
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
-    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
 
 
     override fun renameConversation(roomToken: String, roomNameNew: String): Observable<GenericOverall> {
     override fun renameConversation(roomToken: String, roomNameNew: String): Observable<GenericOverall> {
-        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
+        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
 
 
         return ncApi.renameRoom(
         return ncApi.renameRoom(
             credentials,
             credentials,
             ApiUtils.getUrlForRoom(
             ApiUtils.getUrlForRoom(
                 apiVersion,
                 apiVersion,
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken
                 roomToken
             ),
             ),
             roomNameNew
             roomNameNew
@@ -59,12 +59,12 @@ class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
         roomName: String,
         roomName: String,
         conversationType: Conversation.ConversationType?
         conversationType: Conversation.ConversationType?
     ): Observable<RoomOverall> {
     ): Observable<RoomOverall> {
-        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
+        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
 
 
         val retrofitBucket: RetrofitBucket = if (conversationType == Conversation.ConversationType.ROOM_PUBLIC_CALL) {
         val retrofitBucket: RetrofitBucket = if (conversationType == Conversation.ConversationType.ROOM_PUBLIC_CALL) {
             ApiUtils.getRetrofitBucketForCreateRoom(
             ApiUtils.getRetrofitBucketForCreateRoom(
                 apiVersion,
                 apiVersion,
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 ROOM_TYPE_PUBLIC,
                 ROOM_TYPE_PUBLIC,
                 null,
                 null,
                 null,
                 null,
@@ -73,7 +73,7 @@ class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
         } else {
         } else {
             ApiUtils.getRetrofitBucketForCreateRoom(
             ApiUtils.getRetrofitBucketForCreateRoom(
                 apiVersion,
                 apiVersion,
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 ROOM_TYPE_GROUP,
                 ROOM_TYPE_GROUP,
                 null,
                 null,
                 null,
                 null,

+ 196 - 165
app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt

@@ -40,6 +40,7 @@ import android.view.View
 import android.view.View.GONE
 import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.view.View.VISIBLE
 import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.app.AlertDialog
+import androidx.lifecycle.ViewModelProvider
 import androidx.work.Data
 import androidx.work.Data
 import androidx.work.OneTimeWorkRequest
 import androidx.work.OneTimeWorkRequest
 import androidx.work.WorkManager
 import androidx.work.WorkManager
@@ -61,6 +62,7 @@ import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
 import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
 import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
 import com.nextcloud.talk.contacts.ContactsActivity
 import com.nextcloud.talk.contacts.ContactsActivity
 import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
 import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
+import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
 import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
 import com.nextcloud.talk.events.EventStatus
 import com.nextcloud.talk.events.EventStatus
@@ -71,9 +73,11 @@ import com.nextcloud.talk.extensions.loadUserAvatar
 import com.nextcloud.talk.jobs.DeleteConversationWorker
 import com.nextcloud.talk.jobs.DeleteConversationWorker
 import com.nextcloud.talk.jobs.LeaveConversationWorker
 import com.nextcloud.talk.jobs.LeaveConversationWorker
 import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.json.conversations.Conversation
-import com.nextcloud.talk.models.json.conversations.RoomOverall
-import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
+import com.nextcloud.talk.models.domain.ConversationType
+import com.nextcloud.talk.models.domain.LobbyState
+import com.nextcloud.talk.models.domain.NotificationLevel
+import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelConverter
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.generic.GenericOverall
 import com.nextcloud.talk.models.json.generic.GenericOverall
 import com.nextcloud.talk.models.json.participants.Participant
 import com.nextcloud.talk.models.json.participants.Participant
 import com.nextcloud.talk.models.json.participants.Participant.ActorType.CIRCLES
 import com.nextcloud.talk.models.json.participants.Participant.ActorType.CIRCLES
@@ -83,11 +87,12 @@ import com.nextcloud.talk.models.json.participants.ParticipantsOverall
 import com.nextcloud.talk.repositories.conversations.ConversationsRepository
 import com.nextcloud.talk.repositories.conversations.ConversationsRepository
 import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
 import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.DateConstants
 import com.nextcloud.talk.utils.DateConstants
 import com.nextcloud.talk.utils.DateUtils
 import com.nextcloud.talk.utils.DateUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
 import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.FlexibleAdapter
@@ -109,6 +114,9 @@ class ConversationInfoActivity :
     FlexibleAdapter.OnItemClickListener {
     FlexibleAdapter.OnItemClickListener {
     private lateinit var binding: ActivityConversationInfoBinding
     private lateinit var binding: ActivityConversationInfoBinding
 
 
+    @Inject
+    lateinit var viewModelFactory: ViewModelProvider.Factory
+
     @Inject
     @Inject
     lateinit var ncApi: NcApi
     lateinit var ncApi: NcApi
 
 
@@ -121,6 +129,10 @@ class ConversationInfoActivity :
     @Inject
     @Inject
     lateinit var dateUtils: DateUtils
     lateinit var dateUtils: DateUtils
 
 
+    lateinit var viewModel: ConversationInfoViewModel
+
+    private lateinit var spreedCapabilities: SpreedCapability
+
     private lateinit var conversationToken: String
     private lateinit var conversationToken: String
     private lateinit var conversationUser: User
     private lateinit var conversationUser: User
     private var hasAvatarSpacing: Boolean = false
     private var hasAvatarSpacing: Boolean = false
@@ -129,7 +141,9 @@ class ConversationInfoActivity :
     private var participantsDisposable: Disposable? = null
     private var participantsDisposable: Disposable? = null
 
 
     private var databaseStorageModule: DatabaseStorageModule? = null
     private var databaseStorageModule: DatabaseStorageModule? = null
-    private var conversation: Conversation? = null
+
+    // private var conversation: Conversation? = null
+    private var conversation: ConversationModel? = null
 
 
     private var adapter: FlexibleAdapter<ParticipantItem>? = null
     private var adapter: FlexibleAdapter<ParticipantItem>? = null
     private var userItems: MutableList<ParticipantItem> = ArrayList()
     private var userItems: MutableList<ParticipantItem> = ArrayList()
@@ -157,11 +171,26 @@ class ConversationInfoActivity :
         setContentView(binding.root)
         setContentView(binding.root)
         setupSystemColors()
         setupSystemColors()
 
 
+        viewModel =
+            ViewModelProvider(this, viewModelFactory)[ConversationInfoViewModel::class.java]
+
         conversationUser = currentUserProvider.currentUser.blockingGet()
         conversationUser = currentUserProvider.currentUser.blockingGet()
 
 
         conversationToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
         conversationToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
         hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
         hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
-        credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
+        credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
+
+        initObservers()
+    }
+
+    override fun onStart() {
+        super.onStart()
+        this.lifecycle.addObserver(ConversationInfoViewModel.LifeCycleObserver)
+    }
+
+    override fun onStop() {
+        super.onStop()
+        this.lifecycle.removeObserver(ConversationInfoViewModel.LifeCycleObserver)
     }
     }
 
 
     override fun onResume() {
     override fun onResume() {
@@ -176,13 +205,7 @@ class ConversationInfoActivity :
         binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog() }
         binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog() }
         binding.addParticipantsAction.setOnClickListener { addParticipants() }
         binding.addParticipantsAction.setOnClickListener { addParticipants() }
 
 
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "rich-object-list-media")) {
-            binding.sharedItemsButton.setOnClickListener { showSharedItems() }
-        } else {
-            binding.sharedItems.visibility = GONE
-        }
-
-        fetchRoomInfo()
+        viewModel.getRoom(conversationUser, conversationToken)
 
 
         themeTextViews()
         themeTextViews()
         themeSwitchPreferences()
         themeSwitchPreferences()
@@ -192,6 +215,35 @@ class ConversationInfoActivity :
         binding.progressBar.let { viewThemeUtils.platform.colorCircularProgressBar(it, ColorRole.PRIMARY) }
         binding.progressBar.let { viewThemeUtils.platform.colorCircularProgressBar(it, ColorRole.PRIMARY) }
     }
     }
 
 
+    private fun initObservers() {
+        viewModel.viewState.observe(this) { state ->
+            when (state) {
+                is ConversationInfoViewModel.GetRoomSuccessState -> {
+                    conversation = state.conversationModel
+                    viewModel.getCapabilities(conversationUser, conversationToken, conversation!!)
+                }
+
+                is ConversationInfoViewModel.GetRoomErrorState -> {
+                    Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
+                }
+
+                else -> {}
+            }
+        }
+
+        viewModel.getCapabilitiesViewState.observe(this) { state ->
+            when (state) {
+                is ConversationInfoViewModel.GetCapabilitiesSuccessState -> {
+                    spreedCapabilities = state.spreedCapabilities
+
+                    handleConversation()
+                }
+
+                else -> {}
+            }
+        }
+    }
+
     private fun setupActionBar() {
     private fun setupActionBar() {
         setSupportActionBar(binding.conversationInfoToolbar)
         setSupportActionBar(binding.conversationInfoToolbar)
         binding.conversationInfoToolbar.setNavigationOnClickListener {
         binding.conversationInfoToolbar.setNavigationOnClickListener {
@@ -217,7 +269,7 @@ class ConversationInfoActivity :
     fun showOptionsMenu() {
     fun showOptionsMenu() {
         if (::optionsMenu.isInitialized) {
         if (::optionsMenu.isInitialized) {
             optionsMenu.clear()
             optionsMenu.clear()
-            if (CapabilitiesUtilNew.isConversationAvatarEndpointAvailable(conversationUser)) {
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.AVATAR)) {
                 menuInflater.inflate(R.menu.menu_conversation_info, optionsMenu)
                 menuInflater.inflate(R.menu.menu_conversation_info, optionsMenu)
             }
             }
         }
         }
@@ -273,19 +325,22 @@ class ConversationInfoActivity :
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
         intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
         intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
         intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
         intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
-        intent.putExtra(SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR, conversation?.isParticipantOwnerOrModerator)
+        intent.putExtra(
+            SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
+            ConversationUtils.isParticipantOwnerOrModerator(conversation!!)
+        )
         startActivity(intent)
         startActivity(intent)
     }
     }
 
 
     private fun setupWebinaryView() {
     private fun setupWebinaryView() {
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") &&
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
             webinaryRoomType(conversation!!) &&
             webinaryRoomType(conversation!!) &&
-            conversation!!.canModerate(conversationUser)
+            ConversationUtils.canModerate(conversation!!, spreedCapabilities)
         ) {
         ) {
             binding.webinarInfoView.webinarSettings.visibility = VISIBLE
             binding.webinarInfoView.webinarSettings.visibility = VISIBLE
 
 
             val isLobbyOpenToModeratorsOnly =
             val isLobbyOpenToModeratorsOnly =
-                conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
+                conversation!!.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY
             binding.webinarInfoView.lobbySwitch.isChecked = isLobbyOpenToModeratorsOnly
             binding.webinarInfoView.lobbySwitch.isChecked = isLobbyOpenToModeratorsOnly
 
 
             reconfigureLobbyTimerView()
             reconfigureLobbyTimerView()
@@ -320,9 +375,9 @@ class ConversationInfoActivity :
         }
         }
     }
     }
 
 
-    private fun webinaryRoomType(conversation: Conversation): Boolean {
-        return conversation.type == Conversation.ConversationType.ROOM_GROUP_CALL ||
-            conversation.type == Conversation.ConversationType.ROOM_PUBLIC_CALL
+    private fun webinaryRoomType(conversation: ConversationModel): Boolean {
+        return conversation.type == ConversationType.ROOM_GROUP_CALL ||
+            conversation.type == ConversationType.ROOM_PUBLIC_CALL
     }
     }
 
 
     private fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
     private fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
@@ -337,9 +392,9 @@ class ConversationInfoActivity :
         }
         }
 
 
         conversation!!.lobbyState = if (isChecked) {
         conversation!!.lobbyState = if (isChecked) {
-            Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
+            LobbyState.LOBBY_STATE_MODERATORS_ONLY
         } else {
         } else {
-            Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
+            LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
         }
         }
 
 
         if (
         if (
@@ -370,11 +425,11 @@ class ConversationInfoActivity :
             0
             0
         }
         }
 
 
-        val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
 
 
         ncApi.setLobbyForConversation(
         ncApi.setLobbyForConversation(
             ApiUtils.getCredentials(conversationUser.username, conversationUser.token),
             ApiUtils.getCredentials(conversationUser.username, conversationUser.token),
-            ApiUtils.getUrlForRoomWebinaryLobby(apiVersion, conversationUser.baseUrl, conversation!!.token),
+            ApiUtils.getUrlForRoomWebinaryLobby(apiVersion, conversationUser.baseUrl!!, conversation!!.token),
             state,
             state,
             conversation!!.lobbyTimer
             conversation!!.lobbyTimer
         )
         )
@@ -487,7 +542,7 @@ class ConversationInfoActivity :
 
 
     private fun getListOfParticipants() {
     private fun getListOfParticipants() {
         // FIXME Fix API checking with guests?
         // FIXME Fix API checking with guests?
-        val apiVersion: Int = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion: Int = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
 
 
         val fieldMap = HashMap<String, Boolean>()
         val fieldMap = HashMap<String, Boolean>()
         fieldMap["includeStatus"] = true
         fieldMap["includeStatus"] = true
@@ -496,7 +551,7 @@ class ConversationInfoActivity :
             credentials,
             credentials,
             ApiUtils.getUrlForParticipants(
             ApiUtils.getUrlForParticipants(
                 apiVersion,
                 apiVersion,
-                conversationUser.baseUrl,
+                conversationUser.baseUrl!!,
                 conversationToken
                 conversationToken
             ),
             ),
             fieldMap
             fieldMap
@@ -586,11 +641,11 @@ class ConversationInfoActivity :
     }
     }
 
 
     private fun clearHistory() {
     private fun clearHistory() {
-        val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
+        val apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
 
 
         ncApi.clearChatHistory(
         ncApi.clearChatHistory(
             credentials,
             credentials,
-            ApiUtils.getUrlForChat(apiVersion, conversationUser.baseUrl, conversationToken)
+            ApiUtils.getUrlForChat(apiVersion, conversationUser.baseUrl!!, conversationToken)
         )
         )
             ?.subscribeOn(Schedulers.io())
             ?.subscribeOn(Schedulers.io())
             ?.observeOn(AndroidSchedulers.mainThread())
             ?.observeOn(AndroidSchedulers.mainThread())
@@ -631,123 +686,99 @@ class ConversationInfoActivity :
         }
         }
     }
     }
 
 
-    private fun fetchRoomInfo() {
-        val apiVersion: Int
-        // FIXME Fix API checking with guests?
-        apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
-
-        ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, conversationUser.baseUrl, conversationToken))
-            ?.subscribeOn(Schedulers.io())
-            ?.observeOn(AndroidSchedulers.mainThread())
-            ?.subscribe(object : Observer<RoomOverall> {
-                override fun onSubscribe(d: Disposable) {
-                    roomDisposable = d
-                }
-
-                @Suppress("Detekt.TooGenericExceptionCaught")
-                override fun onNext(roomOverall: RoomOverall) {
-                    conversation = roomOverall.ocs!!.data
-
-                    val conversationCopy = conversation
+    @Suppress("LongMethod")
+    private fun handleConversation() {
+        val conversationCopy = conversation!!
 
 
-                    if (conversationCopy!!.canModerate(conversationUser)) {
-                        binding.addParticipantsAction.visibility = VISIBLE
-                        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(
-                                conversationUser,
-                                "clear-history"
-                            )
-                        ) {
-                            binding.clearConversationHistory.visibility = VISIBLE
-                        } else {
-                            binding.clearConversationHistory.visibility = GONE
-                        }
-                        showOptionsMenu()
-                    } else {
-                        binding.addParticipantsAction.visibility = GONE
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.RICH_OBJECT_LIST_MEDIA)) {
+            binding.sharedItemsButton.setOnClickListener { showSharedItems() }
+        } else {
+            binding.sharedItems.visibility = GONE
+        }
 
 
-                        if (ConversationUtils.isNoteToSelfConversation(
-                                ConversationModel.mapToConversationModel(conversation!!)
-                            )
-                        ) {
-                            binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
-                        } else {
-                            binding.clearConversationHistory.visibility = GONE
-                        }
-                    }
+        if (ConversationUtils.canModerate(conversationCopy, spreedCapabilities)) {
+            binding.addParticipantsAction.visibility = VISIBLE
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(
+                    spreedCapabilities,
+                    SpreedFeatures.CLEAR_HISTORY
+                )
+            ) {
+                binding.clearConversationHistory.visibility = VISIBLE
+            } else {
+                binding.clearConversationHistory.visibility = GONE
+            }
+            showOptionsMenu()
+        } else {
+            binding.addParticipantsAction.visibility = GONE
 
 
-                    if (!isDestroyed) {
-                        binding.dangerZoneOptions.visibility = VISIBLE
+            if (ConversationUtils.isNoteToSelfConversation(conversation)) {
+                binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
+            } else {
+                binding.clearConversationHistory.visibility = GONE
+            }
+        }
 
 
-                        setupWebinaryView()
+        if (!isDestroyed) {
+            binding.dangerZoneOptions.visibility = VISIBLE
 
 
-                        if (!conversation!!.canLeave()) {
-                            binding.leaveConversationAction.visibility = GONE
-                        } else {
-                            binding.leaveConversationAction.visibility = VISIBLE
-                        }
+            setupWebinaryView()
 
 
-                        if (!conversation!!.canDelete(conversationUser)) {
-                            binding.deleteConversationAction.visibility = GONE
-                        } else {
-                            binding.deleteConversationAction.visibility = VISIBLE
-                        }
+            if (ConversationUtils.canLeave(conversation!!)) {
+                binding.leaveConversationAction.visibility = GONE
+            } else {
+                binding.leaveConversationAction.visibility = VISIBLE
+            }
 
 
-                        if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
-                            binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
-                        }
+            if (ConversationUtils.canDelete(conversation!!, spreedCapabilities)) {
+                binding.deleteConversationAction.visibility = GONE
+            } else {
+                binding.deleteConversationAction.visibility = VISIBLE
+            }
 
 
-                        if (conversation!!.notificationCalls === null) {
-                            binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
-                        } else {
-                            binding.notificationSettingsView.callNotificationsSwitch.isChecked =
-                                (conversationCopy.notificationCalls == 1)
-                        }
+            if (ConversationType.ROOM_SYSTEM == conversation!!.type) {
+                binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
+            }
 
 
-                        getListOfParticipants()
+            if (conversation!!.notificationCalls === null) {
+                binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
+            } else {
+                binding.notificationSettingsView.callNotificationsSwitch.isChecked =
+                    (conversationCopy.notificationCalls == 1)
+            }
 
 
-                        binding.progressBar.visibility = GONE
+            getListOfParticipants()
 
 
-                        binding.conversationInfoName.visibility = VISIBLE
+            binding.progressBar.visibility = GONE
 
 
-                        binding.displayNameText.text = conversation!!.displayName
+            binding.conversationInfoName.visibility = VISIBLE
 
 
-                        if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
-                            binding.descriptionText.text = conversation!!.description
-                            binding.conversationDescription.visibility = VISIBLE
-                        }
+            binding.displayNameText.text = conversation!!.displayName
 
 
-                        loadConversationAvatar()
-                        adjustNotificationLevelUI()
-                        initRecordingConsentOption()
-                        initExpiringMessageOption()
-
-                        binding.let {
-                            GuestAccessHelper(
-                                this@ConversationInfoActivity,
-                                it,
-                                conversation!!,
-                                conversationUser
-                            ).setupGuestAccess()
-                        }
-                        if (ConversationUtils.isNoteToSelfConversation(
-                                ConversationModel.mapToConversationModel(conversation!!)
-                            )
-                        ) {
-                            binding.notificationSettingsView.notificationSettings.visibility = GONE
-                        } else {
-                            binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
-                        }
-                    }
-                }
+            if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
+                binding.descriptionText.text = conversation!!.description
+                binding.conversationDescription.visibility = VISIBLE
+            }
 
 
-                override fun onError(e: Throwable) {
-                    Log.e(TAG, "failed to fetch room info", e)
-                }
+            loadConversationAvatar()
+            adjustNotificationLevelUI()
+            initRecordingConsentOption()
+            initExpiringMessageOption()
 
 
-                override fun onComplete() {
-                    roomDisposable!!.dispose()
-                }
-            })
+            binding.let {
+                GuestAccessHelper(
+                    this@ConversationInfoActivity,
+                    it,
+                    conversation!!,
+                    spreedCapabilities,
+                    conversationUser
+                ).setupGuestAccess()
+            }
+            if (ConversationUtils.isNoteToSelfConversation(conversation!!)) {
+                binding.notificationSettingsView.notificationSettings.visibility = GONE
+            } else {
+                binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
+            }
+        }
     }
     }
 
 
     private fun initRecordingConsentOption() {
     private fun initRecordingConsentOption() {
@@ -781,13 +812,13 @@ class ConversationInfoActivity :
             }
             }
         }
         }
 
 
-        if (conversation!!.isParticipantOwnerOrModerator &&
-            !ConversationUtils.isNoteToSelfConversation(ConversationModel.mapToConversationModel(conversation!!))
+        if (ConversationUtils.isParticipantOwnerOrModerator(conversation!!) &&
+            !ConversationUtils.isNoteToSelfConversation(conversation!!)
         ) {
         ) {
-            when (CapabilitiesUtilNew.getRecordingConsentType(conversationUser)) {
-                CapabilitiesUtilNew.RECORDING_CONSENT_NOT_REQUIRED -> hide()
-                CapabilitiesUtilNew.RECORDING_CONSENT_REQUIRED -> showAlwaysRequiredInfo()
-                CapabilitiesUtilNew.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> showSwitch()
+            when (CapabilitiesUtil.getRecordingConsentType(spreedCapabilities)) {
+                CapabilitiesUtil.RECORDING_CONSENT_NOT_REQUIRED -> hide()
+                CapabilitiesUtil.RECORDING_CONSENT_REQUIRED -> showAlwaysRequiredInfo()
+                CapabilitiesUtil.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> showSwitch()
             }
             }
         } else {
         } else {
             hide()
             hide()
@@ -801,11 +832,11 @@ class ConversationInfoActivity :
             RECORDING_CONSENT_NOT_REQUIRED_FOR_CONVERSATION
             RECORDING_CONSENT_NOT_REQUIRED_FOR_CONVERSATION
         }
         }
 
 
-        val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
 
 
         ncApi.setRecordingConsent(
         ncApi.setRecordingConsent(
             ApiUtils.getCredentials(conversationUser.username, conversationUser.token),
             ApiUtils.getCredentials(conversationUser.username, conversationUser.token),
-            ApiUtils.getUrlForRecordingConsent(apiVersion, conversationUser.baseUrl, conversation!!.token),
+            ApiUtils.getUrlForRecordingConsent(apiVersion, conversationUser.baseUrl!!, conversation!!.token),
             state
             state
         )
         )
             ?.subscribeOn(Schedulers.io())
             ?.subscribeOn(Schedulers.io())
@@ -831,8 +862,8 @@ class ConversationInfoActivity :
     }
     }
 
 
     private fun initExpiringMessageOption() {
     private fun initExpiringMessageOption() {
-        if (conversation!!.isParticipantOwnerOrModerator &&
-            CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "message-expiration")
+        if (ConversationUtils.isParticipantOwnerOrModerator(conversation!!) &&
+            CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)
         ) {
         ) {
             databaseStorageModule?.setMessageExpiration(conversation!!.messageExpiration)
             databaseStorageModule?.setMessageExpiration(conversation!!.messageExpiration)
             val value = databaseStorageModule!!.getString("conversation_settings_dropdown", "")
             val value = databaseStorageModule!!.getString("conversation_settings_dropdown", "")
@@ -855,13 +886,16 @@ class ConversationInfoActivity :
 
 
     private fun adjustNotificationLevelUI() {
     private fun adjustNotificationLevelUI() {
         if (conversation != null) {
         if (conversation != null) {
-            if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "notification-levels")) {
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.NOTIFICATION_LEVELS)) {
                 binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.isEnabled = true
                 binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.isEnabled = true
                 binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.alpha = 1.0f
                 binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.alpha = 1.0f
 
 
-                if (conversation!!.notificationLevel != Conversation.NotificationLevel.DEFAULT) {
+                if (conversation!!.notificationLevel != NotificationLevel.DEFAULT) {
                     val stringValue: String =
                     val stringValue: String =
-                        when (EnumNotificationLevelConverter().convertToInt(conversation!!.notificationLevel)) {
+                        when (
+                            DomainEnumNotificationLevelConverter()
+                                .convertToInt(conversation!!.notificationLevel!!)
+                        ) {
                             NOTIFICATION_LEVEL_ALWAYS -> resources.getString(R.string.nc_notify_me_always)
                             NOTIFICATION_LEVEL_ALWAYS -> resources.getString(R.string.nc_notify_me_always)
                             NOTIFICATION_LEVEL_MENTION -> resources.getString(R.string.nc_notify_me_mention)
                             NOTIFICATION_LEVEL_MENTION -> resources.getString(R.string.nc_notify_me_mention)
                             NOTIFICATION_LEVEL_NEVER -> resources.getString(R.string.nc_notify_me_never)
                             NOTIFICATION_LEVEL_NEVER -> resources.getString(R.string.nc_notify_me_never)
@@ -885,9 +919,9 @@ class ConversationInfoActivity :
         }
         }
     }
     }
 
 
-    private fun setProperNotificationValue(conversation: Conversation?) {
-        if (conversation!!.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
-            if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "mention-flag")) {
+    private fun setProperNotificationValue(conversation: ConversationModel?) {
+        if (conversation!!.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)) {
                 binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.setText(
                 binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.setText(
                     resources.getString(R.string.nc_notify_me_always)
                     resources.getString(R.string.nc_notify_me_always)
                 )
                 )
@@ -905,7 +939,7 @@ class ConversationInfoActivity :
 
 
     private fun loadConversationAvatar() {
     private fun loadConversationAvatar() {
         when (conversation!!.type) {
         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 {
                 conversation!!.name?.let {
                     binding.avatarImage.loadUserAvatar(
                     binding.avatarImage.loadUserAvatar(
                         conversationUser,
                         conversationUser,
@@ -916,7 +950,7 @@ class ConversationInfoActivity :
                 }
                 }
             }
             }
 
 
-            Conversation.ConversationType.ROOM_GROUP_CALL, Conversation.ConversationType.ROOM_PUBLIC_CALL -> {
+            ConversationType.ROOM_GROUP_CALL, ConversationType.ROOM_PUBLIC_CALL -> {
                 binding.avatarImage.loadConversationAvatar(
                 binding.avatarImage.loadConversationAvatar(
                     conversationUser,
                     conversationUser,
                     conversation!!,
                     conversation!!,
@@ -925,15 +959,12 @@ class ConversationInfoActivity :
                 )
                 )
             }
             }
 
 
-            Conversation.ConversationType.ROOM_SYSTEM -> {
+            ConversationType.ROOM_SYSTEM -> {
                 binding.avatarImage.loadSystemAvatar()
                 binding.avatarImage.loadSystemAvatar()
             }
             }
 
 
-            Conversation.ConversationType.DUMMY -> {
-                if (ConversationUtils.isNoteToSelfConversation(
-                        ConversationModel.mapToConversationModel(conversation!!)
-                    )
-                ) {
+            ConversationType.DUMMY -> {
+                if (ConversationUtils.isNoteToSelfConversation(conversation!!)) {
                     binding.avatarImage.loadNoteToSelfAvatar()
                     binding.avatarImage.loadNoteToSelfAvatar()
                 }
                 }
             }
             }
@@ -971,7 +1002,7 @@ class ConversationInfoActivity :
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoomModerators(
                 ApiUtils.getUrlForRoomModerators(
                     apiVersion,
                     apiVersion,
-                    conversationUser.baseUrl,
+                    conversationUser.baseUrl!!,
                     conversation!!.token
                     conversation!!.token
                 ),
                 ),
                 participant.attendeeId
                 participant.attendeeId
@@ -986,7 +1017,7 @@ class ConversationInfoActivity :
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoomModerators(
                 ApiUtils.getUrlForRoomModerators(
                     apiVersion,
                     apiVersion,
-                    conversationUser.baseUrl,
+                    conversationUser.baseUrl!!,
                     conversation!!.token
                     conversation!!.token
                 ),
                 ),
                 participant.attendeeId
                 participant.attendeeId
@@ -1022,7 +1053,7 @@ class ConversationInfoActivity :
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoomModerators(
                 ApiUtils.getUrlForRoomModerators(
                     apiVersion,
                     apiVersion,
-                    conversationUser.baseUrl,
+                    conversationUser.baseUrl!!,
                     conversation!!.token
                     conversation!!.token
                 ),
                 ),
                 participant.userId
                 participant.userId
@@ -1035,7 +1066,7 @@ class ConversationInfoActivity :
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoomModerators(
                 ApiUtils.getUrlForRoomModerators(
                     apiVersion,
                     apiVersion,
-                    conversationUser.baseUrl,
+                    conversationUser.baseUrl!!,
                     conversation!!.token
                     conversation!!.token
                 ),
                 ),
                 participant.userId
                 participant.userId
@@ -1047,12 +1078,12 @@ class ConversationInfoActivity :
     }
     }
 
 
     private fun removeAttendeeFromConversation(apiVersion: Int, participant: Participant) {
     private fun removeAttendeeFromConversation(apiVersion: Int, participant: Participant) {
-        if (apiVersion >= ApiUtils.APIv4) {
+        if (apiVersion >= ApiUtils.API_V4) {
             ncApi.removeAttendeeFromConversation(
             ncApi.removeAttendeeFromConversation(
                 credentials,
                 credentials,
                 ApiUtils.getUrlForAttendees(
                 ApiUtils.getUrlForAttendees(
                     apiVersion,
                     apiVersion,
-                    conversationUser.baseUrl,
+                    conversationUser.baseUrl!!,
                     conversation!!.token
                     conversation!!.token
                 ),
                 ),
                 participant.attendeeId
                 participant.attendeeId
@@ -1084,7 +1115,7 @@ class ConversationInfoActivity :
                 ncApi.removeParticipantFromConversation(
                 ncApi.removeParticipantFromConversation(
                     credentials,
                     credentials,
                     ApiUtils.getUrlForRemovingParticipantFromConversation(
                     ApiUtils.getUrlForRemovingParticipantFromConversation(
-                        conversationUser.baseUrl,
+                        conversationUser.baseUrl!!,
                         conversation!!.token,
                         conversation!!.token,
                         true
                         true
                     ),
                     ),
@@ -1114,7 +1145,7 @@ class ConversationInfoActivity :
                 ncApi.removeParticipantFromConversation(
                 ncApi.removeParticipantFromConversation(
                     credentials,
                     credentials,
                     ApiUtils.getUrlForRemovingParticipantFromConversation(
                     ApiUtils.getUrlForRemovingParticipantFromConversation(
-                        conversationUser.baseUrl,
+                        conversationUser.baseUrl!!,
                         conversation!!.token,
                         conversation!!.token,
                         false
                         false
                     ),
                     ),
@@ -1146,14 +1177,14 @@ class ConversationInfoActivity :
 
 
     @SuppressLint("CheckResult")
     @SuppressLint("CheckResult")
     override fun onItemClick(view: View?, position: Int): Boolean {
     override fun onItemClick(view: View?, position: Int): Boolean {
-        if (!conversation!!.canModerate(conversationUser)) {
+        if (ConversationUtils.canModerate(conversation!!, spreedCapabilities)) {
             return true
             return true
         }
         }
 
 
         val userItem = adapter?.getItem(position) as ParticipantItem
         val userItem = adapter?.getItem(position) as ParticipantItem
         val participant = userItem.model
         val participant = userItem.model
 
 
-        val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+        val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
 
 
         if (participant.calculatedActorType == USERS && participant.calculatedActorId == conversationUser.userId) {
         if (participant.calculatedActorType == USERS && participant.calculatedActorId == conversationUser.userId) {
             if (participant.attendeePin?.isNotEmpty() == true) {
             if (participant.attendeePin?.isNotEmpty() == true) {
@@ -1277,7 +1308,7 @@ class ConversationInfoActivity :
                         // Pin, nothing to do
                         // Pin, nothing to do
                     } else if (actionToTrigger == 1) {
                     } else if (actionToTrigger == 1) {
                         // Promote/demote
                         // Promote/demote
-                        if (apiVersion >= ApiUtils.APIv4) {
+                        if (apiVersion >= ApiUtils.API_V4) {
                             toggleModeratorStatus(apiVersion, participant)
                             toggleModeratorStatus(apiVersion, participant)
                         } else {
                         } else {
                             toggleModeratorStatusLegacy(apiVersion, participant)
                             toggleModeratorStatusLegacy(apiVersion, participant)

+ 8 - 4
app/src/main/java/com/nextcloud/talk/conversationinfo/GuestAccessHelper.kt

@@ -11,8 +11,11 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
 import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
 import com.nextcloud.talk.databinding.DialogPasswordBinding
 import com.nextcloud.talk.databinding.DialogPasswordBinding
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.domain.ConversationType
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.repositories.conversations.ConversationsRepository
 import com.nextcloud.talk.repositories.conversations.ConversationsRepository
+import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.Mimetype
 import com.nextcloud.talk.utils.Mimetype
 import com.nextcloud.talk.utils.ShareUtils
 import com.nextcloud.talk.utils.ShareUtils
 import io.reactivex.Observer
 import io.reactivex.Observer
@@ -23,7 +26,8 @@ import io.reactivex.schedulers.Schedulers
 class GuestAccessHelper(
 class GuestAccessHelper(
     private val activity: ConversationInfoActivity,
     private val activity: ConversationInfoActivity,
     private val binding: ActivityConversationInfoBinding,
     private val binding: ActivityConversationInfoBinding,
-    private val conversation: Conversation,
+    private val conversation: ConversationModel,
+    private val spreedCapabilities: SpreedCapability,
     private val conversationUser: User
     private val conversationUser: User
 ) {
 ) {
 
 
@@ -32,13 +36,13 @@ class GuestAccessHelper(
     private val context = activity.context
     private val context = activity.context
 
 
     fun setupGuestAccess() {
     fun setupGuestAccess() {
-        if (conversation.canModerate(conversationUser)) {
+        if (ConversationUtils.canModerate(conversation, spreedCapabilities)) {
             binding.guestAccessView.guestAccessSettings.visibility = View.VISIBLE
             binding.guestAccessView.guestAccessSettings.visibility = View.VISIBLE
         } else {
         } else {
             binding.guestAccessView.guestAccessSettings.visibility = View.GONE
             binding.guestAccessView.guestAccessSettings.visibility = View.GONE
         }
         }
 
 
-        if (conversation.type == Conversation.ConversationType.ROOM_PUBLIC_CALL) {
+        if (conversation.type == ConversationType.ROOM_PUBLIC_CALL) {
             binding.guestAccessView.allowGuestsSwitch.isChecked = true
             binding.guestAccessView.allowGuestsSwitch.isChecked = true
             showAllOptions()
             showAllOptions()
             if (conversation.hasPassword) {
             if (conversation.hasPassword) {

+ 142 - 0
app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt

@@ -0,0 +1,142 @@
+/*
+ * 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.conversationinfo.viewmodel
+
+import android.util.Log
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+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 com.nextcloud.talk.models.json.capabilities.SpreedCapability
+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 ConversationInfoViewModel @Inject constructor(
+    private val chatRepository: ChatRepository
+) : ViewModel() {
+
+    object LifeCycleObserver : DefaultLifecycleObserver {
+        enum class LifeCycleFlag {
+            PAUSED,
+            RESUMED
+        }
+        lateinit var currentLifeCycleFlag: LifeCycleFlag
+        public val disposableSet = mutableSetOf<Disposable>()
+
+        override fun onResume(owner: LifecycleOwner) {
+            super.onResume(owner)
+            currentLifeCycleFlag = LifeCycleFlag.RESUMED
+        }
+
+        override fun onPause(owner: LifecycleOwner) {
+            super.onPause(owner)
+            currentLifeCycleFlag = LifeCycleFlag.PAUSED
+            disposableSet.forEach { disposable -> disposable.dispose() }
+            disposableSet.clear()
+        }
+    }
+
+    sealed interface ViewState
+
+    object GetRoomStartState : ViewState
+    object GetRoomErrorState : ViewState
+    open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
+
+    private val _viewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
+    val viewState: LiveData<ViewState>
+        get() = _viewState
+
+    object GetCapabilitiesStartState : ViewState
+    object GetCapabilitiesErrorState : ViewState
+    open class GetCapabilitiesSuccessState(val spreedCapabilities: SpreedCapability) : ViewState
+
+    private val _getCapabilitiesViewState: MutableLiveData<ViewState> = MutableLiveData(GetCapabilitiesStartState)
+    val getCapabilitiesViewState: LiveData<ViewState>
+        get() = _getCapabilitiesViewState
+
+    fun getRoom(user: User, token: String) {
+        _viewState.value = GetRoomStartState
+        chatRepository.getRoom(user, token)
+            .subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(GetRoomObserver())
+    }
+
+    fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
+        _getCapabilitiesViewState.value = GetCapabilitiesStartState
+
+        if (conversationModel.remoteServer.isNullOrEmpty()) {
+            _getCapabilitiesViewState.value = GetCapabilitiesSuccessState(user.capabilities!!.spreedCapability!!)
+        } else {
+            chatRepository.getCapabilities(user, token)
+                .subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.subscribe(object : Observer<SpreedCapability> {
+                    override fun onSubscribe(d: Disposable) {
+                        LifeCycleObserver.disposableSet.add(d)
+                    }
+
+                    override fun onNext(spreedCapabilities: SpreedCapability) {
+                        _getCapabilitiesViewState.value = GetCapabilitiesSuccessState(spreedCapabilities)
+                    }
+
+                    override fun onError(e: Throwable) {
+                        Log.e(TAG, "Error when fetching spreed capabilities", e)
+                        _getCapabilitiesViewState.value = GetCapabilitiesErrorState
+                    }
+
+                    override fun onComplete() {
+                        // unused atm
+                    }
+                })
+        }
+    }
+
+    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
+        }
+    }
+
+    companion object {
+        private val TAG = ConversationInfoViewModel::class.simpleName
+    }
+}

+ 13 - 8
app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt

@@ -49,11 +49,12 @@ import com.nextcloud.talk.extensions.loadSystemAvatar
 import com.nextcloud.talk.extensions.loadUserAvatar
 import com.nextcloud.talk.extensions.loadUserAvatar
 import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.domain.ConversationType
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.generic.GenericOverall
 import com.nextcloud.talk.models.json.generic.GenericOverall
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import com.nextcloud.talk.utils.PickImage
 import com.nextcloud.talk.utils.PickImage
 import com.nextcloud.talk.utils.bundle.BundleKeys
 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.database.user.CurrentUserProviderNew
 import io.reactivex.Observer
 import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.android.schedulers.AndroidSchedulers
@@ -87,6 +88,8 @@ class ConversationInfoEditActivity :
 
 
     private lateinit var pickImage: PickImage
     private lateinit var pickImage: PickImage
 
 
+    private lateinit var spreedCapabilities: SpreedCapability
+
     override fun onCreate(savedInstanceState: Bundle?) {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         super.onCreate(savedInstanceState)
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@@ -110,7 +113,7 @@ class ConversationInfoEditActivity :
         viewThemeUtils.material.colorTextInputLayout(binding.conversationNameInputLayout)
         viewThemeUtils.material.colorTextInputLayout(binding.conversationNameInputLayout)
         viewThemeUtils.material.colorTextInputLayout(binding.conversationDescriptionInputLayout)
         viewThemeUtils.material.colorTextInputLayout(binding.conversationDescriptionInputLayout)
 
 
-        credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
+        credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
 
 
         pickImage = PickImage(this, conversationUser)
         pickImage = PickImage(this, conversationUser)
 
 
@@ -127,13 +130,15 @@ class ConversationInfoEditActivity :
                 is ConversationInfoEditViewModel.GetRoomSuccessState -> {
                 is ConversationInfoEditViewModel.GetRoomSuccessState -> {
                     conversation = state.conversationModel
                     conversation = state.conversationModel
 
 
+                    spreedCapabilities = conversationUser.capabilities!!.spreedCapability!!
+
                     binding.conversationName.setText(conversation!!.displayName)
                     binding.conversationName.setText(conversation!!.displayName)
 
 
                     if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
                     if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
                         binding.conversationDescription.setText(conversation!!.description)
                         binding.conversationDescription.setText(conversation!!.description)
                     }
                     }
 
 
-                    if (!CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
+                    if (!CapabilitiesUtil.isConversationDescriptionEndpointAvailable(spreedCapabilities)) {
                         binding.conversationDescription.isEnabled = false
                         binding.conversationDescription.isEnabled = false
                     }
                     }
 
 
@@ -221,13 +226,13 @@ class ConversationInfoEditActivity :
 
 
     private fun saveConversationNameAndDescription() {
     private fun saveConversationNameAndDescription() {
         val apiVersion =
         val apiVersion =
-            ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
+            ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
 
 
         ncApi.renameRoom(
         ncApi.renameRoom(
             credentials,
             credentials,
             ApiUtils.getUrlForRoom(
             ApiUtils.getUrlForRoom(
                 apiVersion,
                 apiVersion,
-                conversationUser.baseUrl,
+                conversationUser.baseUrl!!,
                 conversation!!.token
                 conversation!!.token
             ),
             ),
             binding.conversationName.text.toString()
             binding.conversationName.text.toString()
@@ -241,7 +246,7 @@ class ConversationInfoEditActivity :
                 }
                 }
 
 
                 override fun onNext(genericOverall: GenericOverall) {
                 override fun onNext(genericOverall: GenericOverall) {
-                    if (CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
+                    if (CapabilitiesUtil.isConversationDescriptionEndpointAvailable(spreedCapabilities)) {
                         saveConversationDescription()
                         saveConversationDescription()
                     } else {
                     } else {
                         finish()
                         finish()
@@ -265,13 +270,13 @@ class ConversationInfoEditActivity :
 
 
     fun saveConversationDescription() {
     fun saveConversationDescription() {
         val apiVersion =
         val apiVersion =
-            ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
+            ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
 
 
         ncApi.setConversationDescription(
         ncApi.setConversationDescription(
             credentials,
             credentials,
             ApiUtils.getUrlForConversationDescription(
             ApiUtils.getUrlForConversationDescription(
                 apiVersion,
                 apiVersion,
-                conversationUser.baseUrl,
+                conversationUser.baseUrl!!,
                 conversation!!.token
                 conversation!!.token
             ),
             ),
             binding.conversationDescription.text.toString()
             binding.conversationDescription.text.toString()

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

@@ -36,9 +36,9 @@ class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserPr
     ConversationInfoEditRepository {
     ConversationInfoEditRepository {
 
 
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
-    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
 
 
-    val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
+    val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
 
 
     override fun uploadConversationAvatar(user: User, file: File, roomToken: String): Observable<ConversationModel> {
     override fun uploadConversationAvatar(user: User, file: File, roomToken: String): Observable<ConversationModel> {
         val builder = MultipartBody.Builder()
         val builder = MultipartBody.Builder()
@@ -56,7 +56,7 @@ class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserPr
 
 
         return ncApi.uploadConversationAvatar(
         return ncApi.uploadConversationAvatar(
             credentials,
             credentials,
-            ApiUtils.getUrlForConversationAvatar(1, user.baseUrl, roomToken),
+            ApiUtils.getUrlForConversationAvatar(1, user.baseUrl!!, roomToken),
             filePart
             filePart
         ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
         ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
     }
     }
@@ -64,7 +64,7 @@ class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserPr
     override fun deleteConversationAvatar(user: User, roomToken: String): Observable<ConversationModel> {
     override fun deleteConversationAvatar(user: User, roomToken: String): Observable<ConversationModel> {
         return ncApi.deleteConversationAvatar(
         return ncApi.deleteConversationAvatar(
             credentials,
             credentials,
-            ApiUtils.getUrlForConversationAvatar(1, user.baseUrl, roomToken)
+            ApiUtils.getUrlForConversationAvatar(1, user.baseUrl!!, roomToken)
         ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
         ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
     }
     }
 }
 }

+ 30 - 15
app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt

@@ -115,6 +115,7 @@ import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
 import com.nextcloud.talk.ui.dialog.FilterConversationFragment
 import com.nextcloud.talk.ui.dialog.FilterConversationFragment
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.ClosedInterfaceImpl
 import com.nextcloud.talk.utils.ClosedInterfaceImpl
 import com.nextcloud.talk.utils.FileUtils
 import com.nextcloud.talk.utils.FileUtils
 import com.nextcloud.talk.utils.Mimetype
 import com.nextcloud.talk.utils.Mimetype
@@ -130,10 +131,10 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NEW_CONVERSATION
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
 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_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARED_TEXT
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARED_TEXT
-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
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUserStatusAvailable
+import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
+import com.nextcloud.talk.utils.CapabilitiesUtil.isServerEOL
+import com.nextcloud.talk.utils.CapabilitiesUtil.isUnifiedSearchAvailable
+import com.nextcloud.talk.utils.CapabilitiesUtil.isUserStatusAvailable
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.power.PowerManagerUtils
 import com.nextcloud.talk.utils.power.PowerManagerUtils
 import com.nextcloud.talk.utils.rx.SearchViewObservable.Companion.observeSearchView
 import com.nextcloud.talk.utils.rx.SearchViewObservable.Companion.observeSearchView
@@ -283,11 +284,11 @@ class ConversationsListActivity :
         }
         }
         currentUser = userManager.currentUser.blockingGet()
         currentUser = userManager.currentUser.blockingGet()
         if (currentUser != null) {
         if (currentUser != null) {
-            if (isServerEOL(currentUser!!.capabilities)) {
+            if (isServerEOL(currentUser!!.serverVersion!!.major)) {
                 showServerEOLDialog()
                 showServerEOLDialog()
                 return
                 return
             }
             }
-            if (isUnifiedSearchAvailable(currentUser!!)) {
+            if (isUnifiedSearchAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
                 searchHelper = MessageSearchHelper(unifiedSearchRepository)
                 searchHelper = MessageSearchHelper(unifiedSearchRepository)
             }
             }
             credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
             credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
@@ -423,7 +424,7 @@ class ConversationsListActivity :
     private fun loadUserAvatar(target: Target) {
     private fun loadUserAvatar(target: Target) {
         if (currentUser != null) {
         if (currentUser != null) {
             val url = ApiUtils.getUrlForAvatar(
             val url = ApiUtils.getUrlForAvatar(
-                currentUser!!.baseUrl,
+                currentUser!!.baseUrl!!,
                 currentUser!!.userId,
                 currentUser!!.userId,
                 true
                 true
             )
             )
@@ -433,7 +434,7 @@ class ConversationsListActivity :
             context.imageLoader.enqueue(
             context.imageLoader.enqueue(
                 ImageRequest.Builder(context)
                 ImageRequest.Builder(context)
                     .data(url)
                     .data(url)
-                    .addHeader("Authorization", credentials)
+                    .addHeader("Authorization", credentials!!)
                     .placeholder(R.drawable.ic_user)
                     .placeholder(R.drawable.ic_user)
                     .transformations(CircleCropTransformation())
                     .transformations(CircleCropTransformation())
                     .crossfade(true)
                     .crossfade(true)
@@ -698,7 +699,10 @@ class ConversationsListActivity :
         isRefreshing = true
         isRefreshing = true
         conversationItems = ArrayList()
         conversationItems = ArrayList()
         conversationItemsWithHeader = ArrayList()
         conversationItemsWithHeader = ArrayList()
-        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
+        val apiVersion = ApiUtils.getConversationApiVersion(
+            currentUser!!,
+            intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)
+        )
         val startNanoTime = System.nanoTime()
         val startNanoTime = System.nanoTime()
         Log.d(TAG, "fetchData - getRooms - calling: $startNanoTime")
         Log.d(TAG, "fetchData - getRooms - calling: $startNanoTime")
         roomsQueryDisposable = ncApi.getRooms(
         roomsQueryDisposable = ncApi.getRooms(
@@ -868,11 +872,15 @@ class ConversationsListActivity :
     private fun fetchOpenConversations(apiVersion: Int) {
     private fun fetchOpenConversations(apiVersion: Int) {
         searchableConversationItems.clear()
         searchableConversationItems.clear()
         searchableConversationItems.addAll(conversationItemsWithHeader)
         searchableConversationItems.addAll(conversationItemsWithHeader)
-        if (hasSpreedFeatureCapability(currentUser, "listable-rooms")) {
+        if (hasSpreedFeatureCapability(
+                currentUser!!.capabilities!!.spreedCapability!!,
+                SpreedFeatures.LISTABLE_ROOMS
+            )
+        ) {
             val openConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
             val openConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
             openConversationsQueryDisposable = ncApi.getOpenConversations(
             openConversationsQueryDisposable = ncApi.getOpenConversations(
                 credentials,
                 credentials,
-                ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl)
+                ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl!!)
             )
             )
                 .subscribeOn(Schedulers.io())
                 .subscribeOn(Schedulers.io())
                 .observeOn(AndroidSchedulers.mainThread())
                 .observeOn(AndroidSchedulers.mainThread())
@@ -1087,7 +1095,7 @@ class ConversationsListActivity :
             clearMessageSearchResults()
             clearMessageSearchResults()
             adapter!!.setFilter(filter)
             adapter!!.setFilter(filter)
             adapter!!.filterItems()
             adapter!!.filterItems()
-            if (isUnifiedSearchAvailable(currentUser!!)) {
+            if (isUnifiedSearchAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
                 startMessageSearch(filter)
                 startMessageSearch(filter)
             }
             }
         } else {
         } else {
@@ -1173,7 +1181,11 @@ class ConversationsListActivity :
     private fun handleConversation(conversation: Conversation?) {
     private fun handleConversation(conversation: Conversation?) {
         selectedConversation = conversation
         selectedConversation = conversation
         if (selectedConversation != null) {
         if (selectedConversation != null) {
-            val hasChatPermission = ParticipantPermissions(currentUser!!, selectedConversation!!).hasChatPermission()
+            val hasChatPermission = ParticipantPermissions(
+                currentUser!!.capabilities!!.spreedCapability!!,
+                selectedConversation!!
+            )
+                .hasChatPermission()
             if (showShareToScreen) {
             if (showShareToScreen) {
                 if (hasChatPermission &&
                 if (hasChatPermission &&
                     !isReadOnlyConversation(selectedConversation!!) &&
                     !isReadOnlyConversation(selectedConversation!!) &&
@@ -1197,7 +1209,10 @@ class ConversationsListActivity :
     }
     }
 
 
     private fun shouldShowLobby(conversation: Conversation): Boolean {
     private fun shouldShowLobby(conversation: Conversation): Boolean {
-        val participantPermissions = ParticipantPermissions(currentUser!!, conversation)
+        val participantPermissions = ParticipantPermissions(
+            currentUser!!.capabilities?.spreedCapability!!,
+            conversation
+        )
         return conversation.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
         return conversation.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
             !conversation.canModerate(currentUser!!) &&
             !conversation.canModerate(currentUser!!) &&
             !participantPermissions.canIgnoreLobby()
             !participantPermissions.canIgnoreLobby()
@@ -1511,7 +1526,7 @@ class ConversationsListActivity :
                 .setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
                 .setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
                     val intent = Intent(context, WebViewLoginActivity::class.java)
                     val intent = Intent(context, WebViewLoginActivity::class.java)
                     val bundle = Bundle()
                     val bundle = Bundle()
-                    bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl)
+                    bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl!!)
                     bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
                     bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
                     intent.putExtras(bundle)
                     intent.putExtras(bundle)
                     startActivity(intent)
                     startActivity(intent)

+ 6 - 0
app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt

@@ -27,6 +27,7 @@ import com.nextcloud.talk.callnotification.viewmodel.CallNotificationViewModel
 import com.nextcloud.talk.chat.viewmodels.ChatViewModel
 import com.nextcloud.talk.chat.viewmodels.ChatViewModel
 import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
 import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
 import com.nextcloud.talk.conversation.viewmodel.RenameConversationViewModel
 import com.nextcloud.talk.conversation.viewmodel.RenameConversationViewModel
+import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
 import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
 import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
 import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
 import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
 import com.nextcloud.talk.invitation.viewmodels.InvitationsViewModel
 import com.nextcloud.talk.invitation.viewmodels.InvitationsViewModel
@@ -137,6 +138,11 @@ abstract class ViewModelModule {
     @ViewModelKey(CallNotificationViewModel::class)
     @ViewModelKey(CallNotificationViewModel::class)
     abstract fun callNotificationViewModel(viewModel: CallNotificationViewModel): ViewModel
     abstract fun callNotificationViewModel(viewModel: CallNotificationViewModel): ViewModel
 
 
+    @Binds
+    @IntoMap
+    @ViewModelKey(ConversationInfoViewModel::class)
+    abstract fun conversationInfoViewModel(viewModel: ConversationInfoViewModel): ViewModel
+
     @Binds
     @Binds
     @IntoMap
     @IntoMap
     @ViewModelKey(ConversationInfoEditViewModel::class)
     @ViewModelKey(ConversationInfoEditViewModel::class)

+ 3 - 3
app/src/main/java/com/nextcloud/talk/data/user/UserMapper.kt

@@ -36,7 +36,7 @@ object UserMapper {
                 entity.id,
                 entity.id,
                 entity.userId,
                 entity.userId,
                 entity.username,
                 entity.username,
-                entity.baseUrl,
+                entity.baseUrl!!,
                 entity.token,
                 entity.token,
                 entity.displayName,
                 entity.displayName,
                 entity.pushConfigurationState,
                 entity.pushConfigurationState,
@@ -52,8 +52,8 @@ object UserMapper {
 
 
     fun toEntity(model: User): UserEntity {
     fun toEntity(model: User): UserEntity {
         val userEntity = when (val id = model.id) {
         val userEntity = when (val id = model.id) {
-            null -> UserEntity(userId = model.userId, username = model.username, baseUrl = model.baseUrl)
-            else -> UserEntity(id, model.userId, model.username, model.baseUrl)
+            null -> UserEntity(userId = model.userId, username = model.username, baseUrl = model.baseUrl!!)
+            else -> UserEntity(id, model.userId, model.username, model.baseUrl!!)
         }
         }
         userEntity.apply {
         userEntity.apply {
             token = model.token
             token = model.token

+ 1 - 1
app/src/main/java/com/nextcloud/talk/data/user/model/User.kt

@@ -45,7 +45,7 @@ data class User(
     var scheduledForDeletion: Boolean = FALSE
     var scheduledForDeletion: Boolean = FALSE
 ) : Parcelable {
 ) : Parcelable {
 
 
-    fun getCredentials(): String = ApiUtils.getCredentials(username, token)
+    fun getCredentials(): String = ApiUtils.getCredentials(username, token)!!
 
 
     fun hasSpreedFeatureCapability(capabilityName: String): Boolean {
     fun hasSpreedFeatureCapability(capabilityName: String): Boolean {
         return capabilities?.spreedCapability?.features?.contains(capabilityName) ?: false
         return capabilities?.spreedCapability?.features?.contains(capabilityName) ?: false

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

@@ -118,7 +118,7 @@ fun ImageView.loadUserAvatar(
     ignoreCache: Boolean
     ignoreCache: Boolean
 ): io.reactivex.disposables.Disposable {
 ): io.reactivex.disposables.Disposable {
     val imageRequestUri = ApiUtils.getUrlForAvatar(
     val imageRequestUri = ApiUtils.getUrlForAvatar(
-        user.baseUrl,
+        user.baseUrl!!,
         avatarId,
         avatarId,
         requestBigSize
         requestBigSize
     )
     )
@@ -155,7 +155,7 @@ private fun ImageView.loadAvatarInternal(
             user?.let {
             user?.let {
                 addHeader(
                 addHeader(
                     "Authorization",
                     "Authorization",
-                    ApiUtils.getCredentials(user.username, user.token)
+                    ApiUtils.getCredentials(user.username, user.token)!!
                 )
                 )
             }
             }
             transformations(CircleCropTransformation())
             transformations(CircleCropTransformation())
@@ -196,7 +196,7 @@ fun ImageView.loadThumbnail(url: String, user: User): io.reactivex.disposables.D
     ) {
     ) {
         requestBuilder.addHeader(
         requestBuilder.addHeader(
             "Authorization",
             "Authorization",
-            ApiUtils.getCredentials(user.username, user.token)
+            ApiUtils.getCredentials(user.username, user.token)!!
         )
         )
     }
     }
 
 
@@ -222,7 +222,7 @@ fun ImageView.loadImage(url: String, user: User, placeholder: Drawable? = null):
     ) {
     ) {
         requestBuilder.addHeader(
         requestBuilder.addHeader(
             "Authorization",
             "Authorization",
-            ApiUtils.getCredentials(user.username, user.token)
+            ApiUtils.getCredentials(user.username, user.token)!!
         )
         )
     }
     }
 
 

+ 6 - 6
app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsRepositoryImpl.kt

@@ -29,29 +29,29 @@ class InvitationsRepositoryImpl(private val ncApi: NcApi) :
     InvitationsRepository {
     InvitationsRepository {
 
 
     override fun fetchInvitations(user: User): Observable<InvitationsModel> {
     override fun fetchInvitations(user: User): Observable<InvitationsModel> {
-        val credentials: String = ApiUtils.getCredentials(user.username, user.token)
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
 
 
         return ncApi.getInvitations(
         return ncApi.getInvitations(
             credentials,
             credentials,
-            ApiUtils.getUrlForInvitation(user.baseUrl)
+            ApiUtils.getUrlForInvitation(user.baseUrl!!)
         ).map { mapToInvitationsModel(user, it.ocs?.data!!) }
         ).map { mapToInvitationsModel(user, it.ocs?.data!!) }
     }
     }
 
 
     override fun acceptInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel> {
     override fun acceptInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel> {
-        val credentials: String = ApiUtils.getCredentials(user.username, user.token)
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
 
 
         return ncApi.acceptInvitation(
         return ncApi.acceptInvitation(
             credentials,
             credentials,
-            ApiUtils.getUrlForInvitationAccept(user.baseUrl, invitation.id)
+            ApiUtils.getUrlForInvitationAccept(user.baseUrl!!, invitation.id)
         ).map { InvitationActionModel(ActionEnum.ACCEPT, it.ocs?.meta?.statusCode!!, invitation) }
         ).map { InvitationActionModel(ActionEnum.ACCEPT, it.ocs?.meta?.statusCode!!, invitation) }
     }
     }
 
 
     override fun rejectInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel> {
     override fun rejectInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel> {
-        val credentials: String = ApiUtils.getCredentials(user.username, user.token)
+        val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
 
 
         return ncApi.rejectInvitation(
         return ncApi.rejectInvitation(
             credentials,
             credentials,
-            ApiUtils.getUrlForInvitationReject(user.baseUrl, invitation.id)
+            ApiUtils.getUrlForInvitationReject(user.baseUrl!!, invitation.id)
         ).map { InvitationActionModel(ActionEnum.REJECT, it.ocs?.meta?.statusCode!!, invitation) }
         ).map { InvitationActionModel(ActionEnum.REJECT, it.ocs?.meta?.statusCode!!, invitation) }
     }
     }
 
 

+ 1 - 1
app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.java

@@ -70,7 +70,7 @@ public class AddParticipantsToConversation extends Worker {
                 data.getLong(BundleKeys.KEY_INTERNAL_USER_ID, -1))
                 data.getLong(BundleKeys.KEY_INTERNAL_USER_ID, -1))
                 .blockingGet();
                 .blockingGet();
 
 
-        int apiVersion = ApiUtils.getConversationApiVersion(user, new int[] {ApiUtils.APIv4, 1});
+        int apiVersion = ApiUtils.getConversationApiVersion(user, new int[] {ApiUtils.API_V4, 1});
 
 
         String conversationToken = data.getString(BundleKeys.KEY_TOKEN);
         String conversationToken = data.getString(BundleKeys.KEY_TOKEN);
         String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken());
         String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken());

+ 1 - 1
app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt

@@ -129,7 +129,7 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
 
 
             ncApi.searchContactsByPhoneNumber(
             ncApi.searchContactsByPhoneNumber(
                 ApiUtils.getCredentials(currentUser.username, currentUser.token),
                 ApiUtils.getCredentials(currentUser.username, currentUser.token),
-                ApiUtils.getUrlForSearchByNumber(currentUser.baseUrl),
+                ApiUtils.getUrlForSearchByNumber(currentUser.baseUrl!!),
                 json.toRequestBody("application/json".toMediaTypeOrNull())
                 json.toRequestBody("application/json".toMediaTypeOrNull())
             )
             )
                 .subscribeOn(Schedulers.io())
                 .subscribeOn(Schedulers.io())

+ 1 - 1
app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.java

@@ -80,7 +80,7 @@ public class DeleteConversationWorker extends Worker {
         User operationUser = userManager.getUserWithId(operationUserId).blockingGet();
         User operationUser = userManager.getUserWithId(operationUserId).blockingGet();
 
 
         if (operationUser != null) {
         if (operationUser != null) {
-            int apiVersion = ApiUtils.getConversationApiVersion(operationUser, new int[]{ApiUtils.APIv4, 1});
+            int apiVersion = ApiUtils.getConversationApiVersion(operationUser, new int[]{ApiUtils.API_V4, 1});
 
 
             String credentials = ApiUtils.getCredentials(operationUser.getUsername(), operationUser.getToken());
             String credentials = ApiUtils.getCredentials(operationUser.getUsername(), operationUser.getToken());
             ncApi = retrofit
             ncApi = retrofit

+ 1 - 1
app/src/main/java/com/nextcloud/talk/jobs/LeaveConversationWorker.java

@@ -92,7 +92,7 @@ public class LeaveConversationWorker extends Worker {
                                                       EventStatus.EventType.CONVERSATION_UPDATE,
                                                       EventStatus.EventType.CONVERSATION_UPDATE,
                                                       true);
                                                       true);
 
 
-            int apiVersion = ApiUtils.getConversationApiVersion(operationUser, new int[] {ApiUtils.APIv4, 1});
+            int apiVersion = ApiUtils.getConversationApiVersion(operationUser, new int[] {ApiUtils.API_V4, 1});
 
 
             ncApi.removeSelfFromRoom(credentials, ApiUtils.getUrlForParticipantsSelf(apiVersion,
             ncApi.removeSelfFromRoom(credentials, ApiUtils.getUrlForParticipantsSelf(apiVersion,
                                                                                      operationUser.getBaseUrl(),
                                                                                      operationUser.getBaseUrl(),

+ 14 - 14
app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt

@@ -248,7 +248,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
 
 
         val soundUri = getCallRingtoneUri(applicationContext, appPreferences)
         val soundUri = getCallRingtoneUri(applicationContext, appPreferences)
         val notificationChannelId = NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_CALLS_V4.name
         val notificationChannelId = NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_CALLS_V4.name
-        val uri = Uri.parse(signatureVerification.user!!.baseUrl)
+        val uri = Uri.parse(signatureVerification.user!!.baseUrl!!)
         val baseUrl = uri.host
         val baseUrl = uri.host
 
 
         val notification =
         val notification =
@@ -279,7 +279,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
         credentials = ApiUtils.getCredentials(
         credentials = ApiUtils.getCredentials(
             signatureVerification.user!!.username,
             signatureVerification.user!!.username,
             signatureVerification.user!!.token
             signatureVerification.user!!.token
-        )
+        )!!
         ncApi = retrofit!!.newBuilder().client(
         ncApi = retrofit!!.newBuilder().client(
             okHttpClient!!.newBuilder().cookieJar(
             okHttpClient!!.newBuilder().cookieJar(
                 JavaNetCookieJar(
                 JavaNetCookieJar(
@@ -338,7 +338,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
         ncApi.getNcNotification(
         ncApi.getNcNotification(
             credentials,
             credentials,
             ApiUtils.getUrlForNcNotificationWithId(
             ApiUtils.getUrlForNcNotificationWithId(
-                user!!.baseUrl,
+                user!!.baseUrl!!,
                 (pushMessage.notificationId!!).toString()
                 (pushMessage.notificationId!!).toString()
             )
             )
         )
         )
@@ -451,7 +451,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
             0
             0
         }
         }
         val pendingIntent = PendingIntent.getActivity(context, requestCode, intent, intentFlag)
         val pendingIntent = PendingIntent.getActivity(context, requestCode, intent, intentFlag)
-        val uri = Uri.parse(signatureVerification.user!!.baseUrl)
+        val uri = Uri.parse(signatureVerification.user!!.baseUrl!!)
         val baseUrl = uri.host
         val baseUrl = uri.host
 
 
         var contentTitle: CharSequence? = ""
         var contentTitle: CharSequence? = ""
@@ -601,12 +601,12 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
             val baseUrl = signatureVerification.user!!.baseUrl
             val baseUrl = signatureVerification.user!!.baseUrl
             val avatarUrl = if ("user" == userType) {
             val avatarUrl = if ("user" == userType) {
                 ApiUtils.getUrlForAvatar(
                 ApiUtils.getUrlForAvatar(
-                    baseUrl,
+                    baseUrl!!,
                     notificationUser.id,
                     notificationUser.id,
                     false
                     false
                 )
                 )
             } else {
             } else {
-                ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.name, false)
+                ApiUtils.getUrlForGuestAvatar(baseUrl!!, notificationUser.name, false)
             }
             }
             person.setIcon(loadAvatarSync(avatarUrl, context!!))
             person.setIcon(loadAvatarSync(avatarUrl, context!!))
         }
         }
@@ -840,8 +840,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
         var inCallOnDifferentDevice = false
         var inCallOnDifferentDevice = false
 
 
         val apiVersion = ApiUtils.getConversationApiVersion(
         val apiVersion = ApiUtils.getConversationApiVersion(
-            signatureVerification.user,
-            intArrayOf(ApiUtils.APIv4, 1)
+            signatureVerification.user!!,
+            intArrayOf(ApiUtils.API_V4, 1)
         )
         )
 
 
         var isCallNotificationVisible = true
         var isCallNotificationVisible = true
@@ -850,8 +850,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
             credentials,
             credentials,
             ApiUtils.getUrlForCall(
             ApiUtils.getUrlForCall(
                 apiVersion,
                 apiVersion,
-                signatureVerification.user!!.baseUrl,
-                pushMessage.id
+                signatureVerification.user!!.baseUrl!!,
+                pushMessage.id!!
             )
             )
         )
         )
             .repeatWhen { completed ->
             .repeatWhen { completed ->
@@ -920,10 +920,10 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
 
 
         if (isOngoingCallNotificationVisible) {
         if (isOngoingCallNotificationVisible) {
             val apiVersion = ApiUtils.getConversationApiVersion(
             val apiVersion = ApiUtils.getConversationApiVersion(
-                signatureVerification.user,
+                signatureVerification.user!!,
                 intArrayOf(
                 intArrayOf(
-                    ApiUtils.APIv4,
-                    ApiUtils.APIv3,
+                    ApiUtils.API_V4,
+                    ApiUtils.API_V3,
                     1
                     1
                 )
                 )
             )
             )
@@ -931,7 +931,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoom(
                 ApiUtils.getUrlForRoom(
                     apiVersion,
                     apiVersion,
-                    signatureVerification.user?.baseUrl,
+                    signatureVerification.user?.baseUrl!!,
                     pushMessage.id
                     pushMessage.id
                 )
                 )
             )
             )

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

@@ -62,7 +62,7 @@ class ShareOperationWorker(context: Context, workerParams: WorkerParameters) : W
         for (filePath in filesArray) {
         for (filePath in filesArray) {
             ncApi.createRemoteShare(
             ncApi.createRemoteShare(
                 credentials,
                 credentials,
-                ApiUtils.getSharingUrl(baseUrl),
+                ApiUtils.getSharingUrl(baseUrl!!),
                 filePath,
                 filePath,
                 roomToken,
                 roomToken,
                 "10",
                 "10",
@@ -87,7 +87,7 @@ class ShareOperationWorker(context: Context, workerParams: WorkerParameters) : W
 
 
         val operationsUser = userManager.getUserWithId(userId).blockingGet()
         val operationsUser = userManager.getUserWithId(userId).blockingGet()
         baseUrl = operationsUser.baseUrl
         baseUrl = operationsUser.baseUrl
-        credentials = ApiUtils.getCredentials(operationsUser.username, operationsUser.token)
+        credentials = ApiUtils.getCredentials(operationsUser.username, operationsUser.token)!!
     }
     }
 
 
     companion object {
     companion object {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/jobs/SignalingSettingsWorker.java

@@ -85,7 +85,7 @@ public class SignalingSettingsWorker extends Worker {
 
 
         for (User user : userEntityObjectList) {
         for (User user : userEntityObjectList) {
 
 
-            int apiVersion = ApiUtils.getSignalingApiVersion(user, new int[] {ApiUtils.APIv3, 2, 1});
+            int apiVersion = ApiUtils.getSignalingApiVersion(user, new int[] {ApiUtils.API_V3, 2, 1});
 
 
             ncApi.getSignalingSettings(
             ncApi.getSignalingSettings(
                     ApiUtils.getCredentials(user.getUsername(), user.getToken()),
                     ApiUtils.getCredentials(user.getUsername(), user.getToken()),

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

@@ -56,7 +56,7 @@ 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_FROM_NOTIFICATION_START_CALL
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
 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_ROOM_TOKEN
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.preferences.AppPreferences
 import com.nextcloud.talk.utils.preferences.AppPreferences
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
@@ -186,7 +186,9 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
     }
     }
 
 
     private fun getRemotePath(currentUser: User): String {
     private fun getRemotePath(currentUser: User): String {
-        var remotePath = CapabilitiesUtilNew.getAttachmentFolder(currentUser)!! + "/" + fileName
+        var remotePath = CapabilitiesUtil.getAttachmentFolder(
+            currentUser.capabilities!!.spreedCapability!!
+        ) + "/" + fileName
         remotePath = RemoteFileUtils.getNewPathIfFileExists(
         remotePath = RemoteFileUtils.getNewPathIfFileExists(
             ncApi,
             ncApi,
             currentUser,
             currentUser,

+ 5 - 0
app/src/main/java/com/nextcloud/talk/location/GeocodingActivity.kt

@@ -64,6 +64,7 @@ class GeocodingActivity :
     lateinit var okHttpClient: OkHttpClient
     lateinit var okHttpClient: OkHttpClient
 
 
     lateinit var roomToken: String
     lateinit var roomToken: String
+    private var chatApiVersion: Int = 1
     private var nominatimClient: TalkJsonNominatimClient? = null
     private var nominatimClient: TalkJsonNominatimClient? = null
 
 
     private var searchItem: MenuItem? = null
     private var searchItem: MenuItem? = null
@@ -86,6 +87,7 @@ class GeocodingActivity :
         Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))
         Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))
 
 
         roomToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
         roomToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
+        chatApiVersion = intent.getIntExtra(BundleKeys.KEY_CHAT_API_VERSION, 1)
 
 
         recyclerView = findViewById(R.id.geocoding_results)
         recyclerView = findViewById(R.id.geocoding_results)
         recyclerView.layoutManager = LinearLayoutManager(this)
         recyclerView.layoutManager = LinearLayoutManager(this)
@@ -130,6 +132,7 @@ class GeocodingActivity :
                 val intent = Intent(this@GeocodingActivity, LocationPickerActivity::class.java)
                 val intent = Intent(this@GeocodingActivity, LocationPickerActivity::class.java)
                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                 intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
                 intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
+                intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
                 intent.putExtra(BundleKeys.KEY_GEOCODING_RESULT, geocodingResult)
                 intent.putExtra(BundleKeys.KEY_GEOCODING_RESULT, geocodingResult)
                 startActivity(intent)
                 startActivity(intent)
             }
             }
@@ -158,6 +161,7 @@ class GeocodingActivity :
                 val intent = Intent(this@GeocodingActivity, LocationPickerActivity::class.java)
                 val intent = Intent(this@GeocodingActivity, LocationPickerActivity::class.java)
                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                 intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
                 intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
+                intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
                 intent.putExtra(BundleKeys.KEY_GEOCODING_RESULT, geocodingResult)
                 intent.putExtra(BundleKeys.KEY_GEOCODING_RESULT, geocodingResult)
                 startActivity(intent)
                 startActivity(intent)
             }
             }
@@ -217,6 +221,7 @@ class GeocodingActivity :
                     val intent = Intent(context, LocationPickerActivity::class.java)
                     val intent = Intent(context, LocationPickerActivity::class.java)
                     intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                     intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                     intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
                     intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
+                    intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
                     startActivity(intent)
                     startActivity(intent)
                     return true
                     return true
                 }
                 }

+ 5 - 2
app/src/main/java/com/nextcloud/talk/location/LocationPickerActivity.kt

@@ -58,6 +58,7 @@ import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.bundle.BundleKeys
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CHAT_API_VERSION
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_GEOCODING_RESULT
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_GEOCODING_RESULT
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
 import fr.dudie.nominatim.client.TalkJsonNominatimClient
 import fr.dudie.nominatim.client.TalkJsonNominatimClient
@@ -103,6 +104,7 @@ class LocationPickerActivity :
     var nominatimClient: TalkJsonNominatimClient? = null
     var nominatimClient: TalkJsonNominatimClient? = null
 
 
     lateinit var roomToken: String
     lateinit var roomToken: String
+    private var chatApiVersion: Int = 1
     var geocodingResult: GeocodingResult? = null
     var geocodingResult: GeocodingResult? = null
 
 
     var myLocation: GeoPoint = GeoPoint(COORDINATE_ZERO, COORDINATE_ZERO)
     var myLocation: GeoPoint = GeoPoint(COORDINATE_ZERO, COORDINATE_ZERO)
@@ -130,6 +132,7 @@ class LocationPickerActivity :
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
 
 
         roomToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
         roomToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
+        chatApiVersion = intent.getIntExtra(KEY_CHAT_API_VERSION, 1)
         geocodingResult = intent.getParcelableExtra(KEY_GEOCODING_RESULT)
         geocodingResult = intent.getParcelableExtra(KEY_GEOCODING_RESULT)
 
 
         if (savedInstanceState != null) {
         if (savedInstanceState != null) {
@@ -244,6 +247,7 @@ class LocationPickerActivity :
             val intent = Intent(this, GeocodingActivity::class.java)
             val intent = Intent(this, GeocodingActivity::class.java)
             intent.putExtra(BundleKeys.KEY_GEOCODING_QUERY, query)
             intent.putExtra(BundleKeys.KEY_GEOCODING_QUERY, query)
             intent.putExtra(KEY_ROOM_TOKEN, roomToken)
             intent.putExtra(KEY_ROOM_TOKEN, roomToken)
+            intent.putExtra(KEY_CHAT_API_VERSION, chatApiVersion)
             startActivity(intent)
             startActivity(intent)
         }
         }
         return true
         return true
@@ -465,11 +469,10 @@ class LocationPickerActivity :
                 "\"longitude\":\"$selectedLon\",\"name\":\"$locationNameToShare\"}"
                 "\"longitude\":\"$selectedLon\",\"name\":\"$locationNameToShare\"}"
 
 
         val currentUser = userManager.currentUser.blockingGet()
         val currentUser = userManager.currentUser.blockingGet()
-        val apiVersion = ApiUtils.getChatApiVersion(currentUser, intArrayOf(1))
 
 
         ncApi.sendLocation(
         ncApi.sendLocation(
             ApiUtils.getCredentials(currentUser.username, currentUser.token),
             ApiUtils.getCredentials(currentUser.username, currentUser.token),
-            ApiUtils.getUrlToSendLocation(apiVersion, currentUser.baseUrl, roomToken),
+            ApiUtils.getUrlToSendLocation(chatApiVersion, currentUser.baseUrl!!, roomToken),
             "geo-location",
             "geo-location",
             objectId,
             objectId,
             metaData
             metaData

+ 1 - 1
app/src/main/java/com/nextcloud/talk/models/RetrofitBucket.kt

@@ -27,5 +27,5 @@ import kotlinx.parcelize.Parcelize
 @Parcelize
 @Parcelize
 data class RetrofitBucket(
 data class RetrofitBucket(
     var url: String? = null,
     var url: String? = null,
-    var queryMap: Map<String, String>? = null
+    var queryMap: MutableMap<String, String>? = null
 ) : Parcelable
 ) : Parcelable

+ 11 - 3
app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt

@@ -3,7 +3,7 @@ package com.nextcloud.talk.models.domain
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.Conversation
 
 
 class ConversationModel(
 class ConversationModel(
-    var roomId: String?,
+    var roomId: String? = null,
     var token: String? = null,
     var token: String? = null,
     var name: String? = null,
     var name: String? = null,
     var displayName: String? = null,
     var displayName: String? = null,
@@ -42,7 +42,11 @@ class ConversationModel(
     var statusClearAt: Long? = 0,
     var statusClearAt: Long? = 0,
     var callRecording: Int = 0,
     var callRecording: Int = 0,
     var avatarVersion: String? = null,
     var avatarVersion: String? = null,
-    var hasCustomAvatar: Boolean? = null
+    var hasCustomAvatar: Boolean? = null,
+    var callStartTime: Long? = null,
+    var recordingConsentRequired: Int = 0,
+    var remoteServer: String? = null,
+    var remoteToken: String? = null
 ) {
 ) {
 
 
     companion object {
     companion object {
@@ -95,7 +99,11 @@ class ConversationModel(
                 statusClearAt = conversation.statusClearAt,
                 statusClearAt = conversation.statusClearAt,
                 callRecording = conversation.callRecording,
                 callRecording = conversation.callRecording,
                 avatarVersion = conversation.avatarVersion,
                 avatarVersion = conversation.avatarVersion,
-                hasCustomAvatar = conversation.hasCustomAvatar
+                hasCustomAvatar = conversation.hasCustomAvatar,
+                callStartTime = conversation.callStartTime,
+                recordingConsentRequired = conversation.recordingConsentRequired,
+                remoteServer = conversation.remoteServer,
+                remoteToken = conversation.remoteToken
             )
             )
         }
         }
     }
     }

+ 45 - 0
app/src/main/java/com/nextcloud/talk/models/domain/converters/DomainEnumNotificationLevelConverter.kt

@@ -0,0 +1,45 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * 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.models.domain.converters
+
+import com.bluelinelabs.logansquare.typeconverters.IntBasedTypeConverter
+import com.nextcloud.talk.models.domain.NotificationLevel
+
+class DomainEnumNotificationLevelConverter : IntBasedTypeConverter<NotificationLevel>() {
+    override fun getFromInt(i: Int): NotificationLevel {
+        return when (i) {
+            0 -> NotificationLevel.DEFAULT
+            1 -> NotificationLevel.ALWAYS
+            2 -> NotificationLevel.MENTION
+            3 -> NotificationLevel.NEVER
+            else -> NotificationLevel.DEFAULT
+        }
+    }
+
+    override fun convertToInt(`object`: NotificationLevel): Int {
+        return when (`object`) {
+            NotificationLevel.DEFAULT -> 0
+            NotificationLevel.ALWAYS -> 1
+            NotificationLevel.MENTION -> 2
+            NotificationLevel.NEVER -> 3
+            else -> 0
+        }
+    }
+}

+ 42 - 0
app/src/main/java/com/nextcloud/talk/models/json/capabilities/RoomCapabilitiesOCS.kt

@@ -0,0 +1,42 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * @author Tim Krüger
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
+ * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * 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.models.json.capabilities
+
+import android.os.Parcelable
+import com.bluelinelabs.logansquare.annotation.JsonField
+import com.bluelinelabs.logansquare.annotation.JsonObject
+import com.nextcloud.talk.models.json.generic.GenericMeta
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+@JsonObject
+data class RoomCapabilitiesOCS(
+    @JsonField(name = ["meta"])
+    var meta: GenericMeta?,
+    @JsonField(name = ["data"])
+    var data: SpreedCapability?
+) : Parcelable {
+    // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
+    constructor() : this(null, null)
+}

+ 37 - 0
app/src/main/java/com/nextcloud/talk/models/json/capabilities/RoomCapabilitiesOverall.kt

@@ -0,0 +1,37 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * @author Tim Krüger
+ * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * 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.models.json.capabilities
+
+import android.os.Parcelable
+import com.bluelinelabs.logansquare.annotation.JsonField
+import com.bluelinelabs.logansquare.annotation.JsonObject
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+@JsonObject
+data class RoomCapabilitiesOverall(
+    @JsonField(name = ["ocs"])
+    var ocs: RoomCapabilitiesOCS? = null
+) : Parcelable {
+    // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
+    constructor() : this(null)
+}

+ 7 - 7
app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt

@@ -37,7 +37,7 @@ import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage
 import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage
 import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter
 import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import com.stfalcon.chatkit.commons.models.IUser
 import com.stfalcon.chatkit.commons.models.IUser
 import com.stfalcon.chatkit.commons.models.MessageContentType
 import com.stfalcon.chatkit.commons.models.MessageContentType
 import kotlinx.parcelize.Parcelize
 import kotlinx.parcelize.Parcelize
@@ -213,7 +213,7 @@ data class ChatMessage(
 
 
     @Suppress("ReturnCount")
     @Suppress("ReturnCount")
     fun isLinkPreview(): Boolean {
     fun isLinkPreview(): Boolean {
-        if (CapabilitiesUtilNew.isLinkPreviewAvailable(activeUser!!)) {
+        if (CapabilitiesUtil.isLinkPreviewAvailable(activeUser!!)) {
             val regexStringFromServer = activeUser?.capabilities?.coreCapability?.referenceRegex
             val regexStringFromServer = activeUser?.capabilities?.coreCapability?.referenceRegex
 
 
             val regexFromServer = regexStringFromServer?.toRegex(setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE))
             val regexFromServer = regexStringFromServer?.toRegex(setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE))
@@ -249,8 +249,8 @@ data class ChatMessage(
                     if (!isVoiceMessage) {
                     if (!isVoiceMessage) {
                         if (activeUser != null && activeUser!!.baseUrl != null) {
                         if (activeUser != null && activeUser!!.baseUrl != null) {
                             return ApiUtils.getUrlForFilePreviewWithFileId(
                             return ApiUtils.getUrlForFilePreviewWithFileId(
-                                activeUser!!.baseUrl,
-                                individualHashMap["id"],
+                                activeUser!!.baseUrl!!,
+                                individualHashMap["id"]!!,
                                 sharedApplication!!.resources.getDimensionPixelSize(R.dimen.maximum_file_preview_size)
                                 sharedApplication!!.resources.getDimensionPixelSize(R.dimen.maximum_file_preview_size)
                             )
                             )
                         } else {
                         } else {
@@ -413,11 +413,11 @@ data class ChatMessage(
                         null
                         null
                     }
                     }
                     actorType == "users" -> {
                     actorType == "users" -> {
-                        ApiUtils.getUrlForAvatar(activeUser!!.baseUrl, actorId, true)
+                        ApiUtils.getUrlForAvatar(activeUser!!.baseUrl!!, actorId, true)
                     }
                     }
                     actorType == "bridged" -> {
                     actorType == "bridged" -> {
                         ApiUtils.getUrlForAvatar(
                         ApiUtils.getUrlForAvatar(
-                            activeUser!!.baseUrl,
+                            activeUser!!.baseUrl!!,
                             "bridge-bot",
                             "bridge-bot",
                             true
                             true
                         )
                         )
@@ -427,7 +427,7 @@ data class ChatMessage(
                         if (!TextUtils.isEmpty(actorDisplayName)) {
                         if (!TextUtils.isEmpty(actorDisplayName)) {
                             apiId = actorDisplayName
                             apiId = actorDisplayName
                         }
                         }
-                        ApiUtils.getUrlForGuestAvatar(activeUser!!.baseUrl, apiId, true)
+                        ApiUtils.getUrlForGuestAvatar(activeUser!!.baseUrl!!, apiId, true)
                     }
                     }
                 }
                 }
             }
             }

+ 18 - 4
app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt

@@ -38,8 +38,9 @@ import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter
 import com.nextcloud.talk.models.json.converters.EnumReadOnlyConversationConverter
 import com.nextcloud.talk.models.json.converters.EnumReadOnlyConversationConverter
 import com.nextcloud.talk.models.json.converters.EnumRoomTypeConverter
 import com.nextcloud.talk.models.json.converters.EnumRoomTypeConverter
 import com.nextcloud.talk.models.json.participants.Participant.ParticipantType
 import com.nextcloud.talk.models.json.participants.Participant.ParticipantType
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.ConversationUtils
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import kotlinx.parcelize.Parcelize
 import kotlinx.parcelize.Parcelize
 
 
 @Parcelize
 @Parcelize
@@ -160,7 +161,13 @@ data class Conversation(
     var callStartTime: Long? = null,
     var callStartTime: Long? = null,
 
 
     @JsonField(name = ["recordingConsent"])
     @JsonField(name = ["recordingConsent"])
-    var recordingConsentRequired: Int = 0
+    var recordingConsentRequired: Int = 0,
+
+    @JsonField(name = ["remoteServer"])
+    var remoteServer: String? = null,
+
+    @JsonField(name = ["remoteToken"])
+    var remoteToken: String? = null
 
 
 ) : Parcelable {
 ) : Parcelable {
     // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
     // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
@@ -185,13 +192,20 @@ data class Conversation(
     @Deprecated("Use ConversationUtil")
     @Deprecated("Use ConversationUtil")
     private fun isLockedOneToOne(conversationUser: User): Boolean {
     private fun isLockedOneToOne(conversationUser: User): Boolean {
         return type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
         return type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
-            CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms")
+            CapabilitiesUtil.hasSpreedFeatureCapability(
+                conversationUser.capabilities?.spreedCapability!!,
+                SpreedFeatures.LOCKED_ONE_TO_ONE_ROOMS
+            )
     }
     }
 
 
     @Deprecated("Use ConversationUtil")
     @Deprecated("Use ConversationUtil")
     fun canModerate(conversationUser: User): Boolean {
     fun canModerate(conversationUser: User): Boolean {
         return isParticipantOwnerOrModerator &&
         return isParticipantOwnerOrModerator &&
-            !isLockedOneToOne(conversationUser) &&
+            ConversationUtils.isLockedOneToOne(
+                ConversationModel.mapToConversationModel(this),
+                conversationUser
+                    .capabilities?.spreedCapability!!
+            ) &&
             type != ConversationType.FORMER_ONE_TO_ONE &&
             type != ConversationType.FORMER_ONE_TO_ONE &&
             !ConversationUtils.isNoteToSelfConversation(ConversationModel.mapToConversationModel(this))
             !ConversationUtils.isNoteToSelfConversation(ConversationModel.mapToConversationModel(this))
     }
     }

+ 3 - 3
app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversationsRepositoryImpl.kt

@@ -31,14 +31,14 @@ class OpenConversationsRepositoryImpl(private val ncApi: NcApi, currentUserProvi
     OpenConversationsRepository {
     OpenConversationsRepository {
 
 
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
-    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
 
 
-    val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
+    val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
 
 
     override fun fetchConversations(): Observable<OpenConversationsModel> {
     override fun fetchConversations(): Observable<OpenConversationsModel> {
         return ncApi.getOpenConversations(
         return ncApi.getOpenConversations(
             credentials,
             credentials,
-            ApiUtils.getUrlForOpenConversations(apiVersion, currentUser.baseUrl)
+            ApiUtils.getUrlForOpenConversations(apiVersion, currentUser.baseUrl!!)
         ).map { mapToOpenConversationsModel(it.ocs?.data!!) }
         ).map { mapToOpenConversationsModel(it.ocs?.data!!) }
     }
     }
 
 

+ 5 - 5
app/src/main/java/com/nextcloud/talk/polls/repositories/PollRepositoryImpl.kt

@@ -37,7 +37,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
     PollRepository {
     PollRepository {
 
 
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
-    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
 
 
     override fun createPoll(
     override fun createPoll(
         roomToken: String,
         roomToken: String,
@@ -49,7 +49,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
         return ncApi.createPoll(
         return ncApi.createPoll(
             credentials,
             credentials,
             ApiUtils.getUrlForPoll(
             ApiUtils.getUrlForPoll(
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken
                 roomToken
             ),
             ),
             question,
             question,
@@ -63,7 +63,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
         return ncApi.getPoll(
         return ncApi.getPoll(
             credentials,
             credentials,
             ApiUtils.getUrlForPoll(
             ApiUtils.getUrlForPoll(
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken,
                 roomToken,
                 pollId
                 pollId
             )
             )
@@ -74,7 +74,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
         return ncApi.votePoll(
         return ncApi.votePoll(
             credentials,
             credentials,
             ApiUtils.getUrlForPoll(
             ApiUtils.getUrlForPoll(
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken,
                 roomToken,
                 pollId
                 pollId
             ),
             ),
@@ -86,7 +86,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
         return ncApi.closePoll(
         return ncApi.closePoll(
             credentials,
             credentials,
             ApiUtils.getUrlForPoll(
             ApiUtils.getUrlForPoll(
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken,
                 roomToken,
                 pollId
                 pollId
             )
             )

+ 4 - 4
app/src/main/java/com/nextcloud/talk/presenters/MentionAutocompletePresenter.java

@@ -77,6 +77,7 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
     private Context context;
     private Context context;
 
 
     private String roomToken;
     private String roomToken;
+    private int chatApiVersion;
 
 
     private List<AbstractFlexibleItem> abstractFlexibleItemList = new ArrayList<>();
     private List<AbstractFlexibleItem> abstractFlexibleItemList = new ArrayList<>();
 
 
@@ -87,10 +88,11 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
         currentUser = userManager.getCurrentUser().blockingGet();
         currentUser = userManager.getCurrentUser().blockingGet();
     }
     }
 
 
-    public MentionAutocompletePresenter(Context context, String roomToken) {
+    public MentionAutocompletePresenter(Context context, String roomToken, int chatApiVersion) {
         super(context);
         super(context);
         this.roomToken = roomToken;
         this.roomToken = roomToken;
         this.context = context;
         this.context = context;
+        this.chatApiVersion = chatApiVersion;
         NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
         NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
         currentUser = userManager.getCurrentUser().blockingGet();
         currentUser = userManager.getCurrentUser().blockingGet();
     }
     }
@@ -120,8 +122,6 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
             queryString = "";
             queryString = "";
         }
         }
 
 
-        int apiVersion = ApiUtils.getChatApiVersion(currentUser, new int[] {1});
-
         adapter.setFilter(queryString);
         adapter.setFilter(queryString);
 
 
         Map<String, String> queryMap = new HashMap<>();
         Map<String, String> queryMap = new HashMap<>();
@@ -129,7 +129,7 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
 
 
         ncApi.getMentionAutocompleteSuggestions(
         ncApi.getMentionAutocompleteSuggestions(
                 ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
                 ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
-                ApiUtils.getUrlForMentionSuggestions(apiVersion, currentUser.getBaseUrl(), roomToken),
+                ApiUtils.getUrlForMentionSuggestions(chatApiVersion, currentUser.getBaseUrl(), roomToken),
                 queryString, 5, queryMap)
                 queryString, 5, queryMap)
                 .subscribeOn(Schedulers.io())
                 .subscribeOn(Schedulers.io())
                 .observeOn(AndroidSchedulers.mainThread())
                 .observeOn(AndroidSchedulers.mainThread())

+ 16 - 11
app/src/main/java/com/nextcloud/talk/profile/ProfileActivity.kt

@@ -66,12 +66,13 @@ import com.nextcloud.talk.ui.dialog.ScopeDialog
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.Mimetype.IMAGE_JPG
 import com.nextcloud.talk.utils.Mimetype.IMAGE_JPG
 import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC
 import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC
 import com.nextcloud.talk.utils.PickImage
 import com.nextcloud.talk.utils.PickImage
 import com.nextcloud.talk.utils.PickImage.Companion.REQUEST_PERMISSION_CAMERA
 import com.nextcloud.talk.utils.PickImage.Companion.REQUEST_PERMISSION_CAMERA
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import io.reactivex.Observer
 import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.Disposable
 import io.reactivex.disposables.Disposable
@@ -127,7 +128,7 @@ class ProfileActivity : BaseActivity() {
         binding.avatarDelete.setOnClickListener {
         binding.avatarDelete.setOnClickListener {
             ncApi.deleteAvatar(
             ncApi.deleteAvatar(
                 credentials,
                 credentials,
-                ApiUtils.getUrlForTempAvatar(currentUser!!.baseUrl)
+                ApiUtils.getUrlForTempAvatar(currentUser!!.baseUrl!!)
             )
             )
                 .subscribeOn(Schedulers.io())
                 .subscribeOn(Schedulers.io())
                 .observeOn(AndroidSchedulers.mainThread())
                 .observeOn(AndroidSchedulers.mainThread())
@@ -154,7 +155,7 @@ class ProfileActivity : BaseActivity() {
                 })
                 })
         }
         }
         binding.avatarImage.let { ViewCompat.setTransitionName(it, "userAvatar.transitionTag") }
         binding.avatarImage.let { ViewCompat.setTransitionName(it, "userAvatar.transitionTag") }
-        ncApi.getUserProfile(credentials, ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl))
+        ncApi.getUserProfile(credentials, ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!))
             .retry(DEFAULT_RETRIES)
             .retry(DEFAULT_RETRIES)
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread())
             .observeOn(AndroidSchedulers.mainThread())
@@ -226,13 +227,17 @@ class ProfileActivity : BaseActivity() {
                 item.icon = ContextCompat.getDrawable(this, R.drawable.ic_check)
                 item.icon = ContextCompat.getDrawable(this, R.drawable.ic_check)
                 binding.emptyList.root.visibility = View.GONE
                 binding.emptyList.root.visibility = View.GONE
                 binding.userinfoList.visibility = View.VISIBLE
                 binding.userinfoList.visibility = View.VISIBLE
-                if (CapabilitiesUtilNew.isAvatarEndpointAvailable(currentUser!!)) {
+                if (CapabilitiesUtil.hasSpreedFeatureCapability(
+                        currentUser!!.capabilities!!.spreedCapability!!,
+                        SpreedFeatures.TEMP_USER_AVATAR_API
+                    )
+                ) {
                     // TODO later avatar can also be checked via user fields, for now it is in Talk capability
                     // TODO later avatar can also be checked via user fields, for now it is in Talk capability
                     binding.avatarButtons.visibility = View.VISIBLE
                     binding.avatarButtons.visibility = View.VISIBLE
                 }
                 }
                 ncApi.getEditableUserProfileFields(
                 ncApi.getEditableUserProfileFields(
                     ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
                     ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-                    ApiUtils.getUrlForUserFields(currentUser!!.baseUrl)
+                    ApiUtils.getUrlForUserFields(currentUser!!.baseUrl!!)
                 )
                 )
                     .subscribeOn(Schedulers.io())
                     .subscribeOn(Schedulers.io())
                     .observeOn(AndroidSchedulers.mainThread())
                     .observeOn(AndroidSchedulers.mainThread())
@@ -292,7 +297,7 @@ class ProfileActivity : BaseActivity() {
 
 
     private fun showUserProfile() {
     private fun showUserProfile() {
         if (currentUser!!.baseUrl != null) {
         if (currentUser!!.baseUrl != null) {
-            binding.userinfoBaseurl.text = Uri.parse(currentUser!!.baseUrl).host
+            binding.userinfoBaseurl.text = Uri.parse(currentUser!!.baseUrl!!).host
         }
         }
         DisplayUtils.loadAvatarImage(currentUser, binding.avatarImage, false)
         DisplayUtils.loadAvatarImage(currentUser, binding.avatarImage, false)
         if (!TextUtils.isEmpty(userInfo?.displayName)) {
         if (!TextUtils.isEmpty(userInfo?.displayName)) {
@@ -327,10 +332,10 @@ class ProfileActivity : BaseActivity() {
         }
         }
 
 
         // show edit button
         // show edit button
-        if (CapabilitiesUtilNew.canEditScopes(currentUser!!)) {
+        if (CapabilitiesUtil.canEditScopes(currentUser!!)) {
             ncApi.getEditableUserProfileFields(
             ncApi.getEditableUserProfileFields(
                 ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
                 ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-                ApiUtils.getUrlForUserFields(currentUser!!.baseUrl)
+                ApiUtils.getUrlForUserFields(currentUser!!.baseUrl!!)
             )
             )
                 .subscribeOn(Schedulers.io())
                 .subscribeOn(Schedulers.io())
                 .observeOn(AndroidSchedulers.mainThread())
                 .observeOn(AndroidSchedulers.mainThread())
@@ -438,7 +443,7 @@ class ProfileActivity : BaseActivity() {
                 val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
                 val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
                 ncApi.setUserData(
                 ncApi.setUserData(
                     credentials,
                     credentials,
-                    ApiUtils.getUrlForUserData(currentUser!!.baseUrl, currentUser!!.userId),
+                    ApiUtils.getUrlForUserData(currentUser!!.baseUrl!!, currentUser!!.userId!!),
                     item.field.fieldName,
                     item.field.fieldName,
                     item.text
                     item.text
                 )
                 )
@@ -535,7 +540,7 @@ class ProfileActivity : BaseActivity() {
         // upload file
         // upload file
         ncApi.uploadAvatar(
         ncApi.uploadAvatar(
             ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
             ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-            ApiUtils.getUrlForTempAvatar(currentUser!!.baseUrl),
+            ApiUtils.getUrlForTempAvatar(currentUser!!.baseUrl!!),
             filePart
             filePart
         )
         )
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
@@ -569,7 +574,7 @@ class ProfileActivity : BaseActivity() {
         val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
         val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
         ncApi.setUserData(
         ncApi.setUserData(
             credentials,
             credentials,
-            ApiUtils.getUrlForUserData(currentUser!!.baseUrl, currentUser!!.userId),
+            ApiUtils.getUrlForUserData(currentUser!!.baseUrl!!, currentUser!!.userId!!),
             item.field.scopeName,
             item.field.scopeName,
             item.scope!!.name
             item.scope!!.name
         )
         )

+ 1 - 1
app/src/main/java/com/nextcloud/talk/raisehand/RequestAssistanceRepositoryImpl.kt

@@ -31,7 +31,7 @@ class RequestAssistanceRepositoryImpl(private val ncApi: NcApi, currentUserProvi
     RequestAssistanceRepository {
     RequestAssistanceRepository {
 
 
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
-    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
 
 
     var apiVersion = 1
     var apiVersion = 1
 
 

+ 3 - 3
app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt

@@ -91,8 +91,8 @@ class DirectReplyReceiver : BroadcastReceiver() {
 
 
     private fun sendDirectReply() {
     private fun sendDirectReply() {
         val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
         val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
-        val apiVersion = ApiUtils.getChatApiVersion(currentUser, intArrayOf(1))
-        val url = ApiUtils.getUrlForChat(apiVersion, currentUser.baseUrl, roomToken)
+        val apiVersion = ApiUtils.getChatApiVersion(currentUser.capabilities!!.spreedCapability!!, intArrayOf(1))
+        val url = ApiUtils.getUrlForChat(apiVersion, currentUser.baseUrl!!, roomToken!!)
 
 
         ncApi.sendChatMessage(credentials, url, replyMessage, currentUser.displayName, null, false)
         ncApi.sendChatMessage(credentials, url, replyMessage, currentUser.displayName, null, false)
             ?.subscribeOn(Schedulers.io())
             ?.subscribeOn(Schedulers.io())
@@ -153,7 +153,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
 
 
         // Add reply
         // Add reply
         Single.fromCallable {
         Single.fromCallable {
-            val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false)
+            val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl!!, currentUser.userId, false)
             val me = Person.Builder()
             val me = Person.Builder()
                 .setName(currentUser.displayName)
                 .setName(currentUser.displayName)
                 .setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context))
                 .setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context))

+ 3 - 3
app/src/main/java/com/nextcloud/talk/receivers/MarkAsReadReceiver.kt

@@ -80,11 +80,11 @@ class MarkAsReadReceiver : BroadcastReceiver() {
 
 
     private fun markAsRead() {
     private fun markAsRead() {
         val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
         val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
-        val apiVersion = ApiUtils.getChatApiVersion(currentUser, intArrayOf(1))
+        val apiVersion = ApiUtils.getChatApiVersion(currentUser.capabilities!!.spreedCapability!!, intArrayOf(1))
         val url = ApiUtils.getUrlForChatReadMarker(
         val url = ApiUtils.getUrlForChatReadMarker(
             apiVersion,
             apiVersion,
-            currentUser.baseUrl,
-            roomToken
+            currentUser.baseUrl!!,
+            roomToken!!
         )
         )
 
 
         ncApi.setChatReadMarker(credentials, url, messageId)
         ncApi.setChatReadMarker(credentials, url, messageId)

+ 1 - 1
app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt

@@ -94,7 +94,7 @@ class RemoteFileBrowserItemsListViewHolder(
 
 
         if (item.hasPreview) {
         if (item.hasPreview) {
             val path = ApiUtils.getUrlForFilePreviewWithRemotePath(
             val path = ApiUtils.getUrlForFilePreviewWithRemotePath(
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 item.path,
                 item.path,
                 fileIcon.context.resources.getDimensionPixelSize(R.dimen.small_item_height)
                 fileIcon.context.resources.getDimensionPixelSize(R.dimen.small_item_height)
             )
             )

+ 3 - 3
app/src/main/java/com/nextcloud/talk/repositories/callrecording/CallRecordingRepositoryImpl.kt

@@ -33,7 +33,7 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
     CallRecordingRepository {
     CallRecordingRepository {
 
 
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
-    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
 
 
     var apiVersion = 1
     var apiVersion = 1
 
 
@@ -42,7 +42,7 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
             credentials,
             credentials,
             ApiUtils.getUrlForRecording(
             ApiUtils.getUrlForRecording(
                 apiVersion,
                 apiVersion,
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken
                 roomToken
             ),
             ),
             1
             1
@@ -54,7 +54,7 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
             credentials,
             credentials,
             ApiUtils.getUrlForRecording(
             ApiUtils.getUrlForRecording(
                 apiVersion,
                 apiVersion,
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken
                 roomToken
             )
             )
         ).map { mapToStopCallRecordingModel(it.ocs?.meta!!) }
         ).map { mapToStopCallRecordingModel(it.ocs?.meta!!) }

+ 3 - 3
app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt

@@ -38,12 +38,12 @@ class ConversationsRepositoryImpl(private val api: NcApi, private val userProvid
         get() = userProvider.currentUser.blockingGet()
         get() = userProvider.currentUser.blockingGet()
 
 
     private val credentials: String
     private val credentials: String
-        get() = ApiUtils.getCredentials(user.username, user.token)
+        get() = ApiUtils.getCredentials(user.username, user.token)!!
 
 
     override fun allowGuests(token: String, allow: Boolean): Observable<AllowGuestsResult> {
     override fun allowGuests(token: String, allow: Boolean): Observable<AllowGuestsResult> {
         val url = ApiUtils.getUrlForRoomPublic(
         val url = ApiUtils.getUrlForRoomPublic(
             apiVersion(),
             apiVersion(),
-            user.baseUrl,
+            user.baseUrl!!,
             token
             token
         )
         )
 
 
@@ -100,7 +100,7 @@ class ConversationsRepositoryImpl(private val api: NcApi, private val userProvid
     }
     }
 
 
     private fun apiVersion(): Int {
     private fun apiVersion(): Int {
-        return ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4))
+        return ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4))
     }
     }
 
 
     companion object {
     companion object {

+ 3 - 3
app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt

@@ -34,13 +34,13 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
     ReactionsRepository {
     ReactionsRepository {
 
 
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
     val currentUser: User = currentUserProvider.currentUser.blockingGet()
-    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+    val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
 
 
     override fun addReaction(roomToken: String, message: ChatMessage, emoji: String): Observable<ReactionAddedModel> {
     override fun addReaction(roomToken: String, message: ChatMessage, emoji: String): Observable<ReactionAddedModel> {
         return ncApi.sendReaction(
         return ncApi.sendReaction(
             credentials,
             credentials,
             ApiUtils.getUrlForMessageReaction(
             ApiUtils.getUrlForMessageReaction(
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken,
                 roomToken,
                 message.id
                 message.id
             ),
             ),
@@ -56,7 +56,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
         return ncApi.deleteReaction(
         return ncApi.deleteReaction(
             credentials,
             credentials,
             ApiUtils.getUrlForMessageReaction(
             ApiUtils.getUrlForMessageReaction(
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 roomToken,
                 roomToken,
                 message.id
                 message.id
             ),
             ),

+ 1 - 1
app/src/main/java/com/nextcloud/talk/repositories/unifiedsearch/UnifiedSearchRepositoryImpl.kt

@@ -37,7 +37,7 @@ class UnifiedSearchRepositoryImpl(private val api: NcApi, private val userProvid
         get() = userProvider.currentUser.blockingGet()
         get() = userProvider.currentUser.blockingGet()
 
 
     private val credentials: String
     private val credentials: String
-        get() = ApiUtils.getCredentials(user.username, user.token)
+        get() = ApiUtils.getCredentials(user.username, user.token)!!
 
 
     override fun searchMessages(
     override fun searchMessages(
         searchTerm: String,
         searchTerm: String,

+ 20 - 15
app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt

@@ -90,6 +90,7 @@ import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
 import com.nextcloud.talk.profile.ProfileActivity
 import com.nextcloud.talk.profile.ProfileActivity
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.ClosedInterfaceImpl
 import com.nextcloud.talk.utils.ClosedInterfaceImpl
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.LoggingUtils.sendMailWithAttachment
 import com.nextcloud.talk.utils.LoggingUtils.sendMailWithAttachment
@@ -97,7 +98,7 @@ import com.nextcloud.talk.utils.NotificationUtils
 import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
 import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
 import com.nextcloud.talk.utils.NotificationUtils.getMessageRingtoneUri
 import com.nextcloud.talk.utils.NotificationUtils.getMessageRingtoneUri
 import com.nextcloud.talk.utils.SecurityUtils
 import com.nextcloud.talk.utils.SecurityUtils
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import com.nextcloud.talk.utils.power.PowerManagerUtils
 import com.nextcloud.talk.utils.power.PowerManagerUtils
@@ -266,7 +267,11 @@ class SettingsActivity : BaseActivity() {
     }
     }
 
 
     private fun setupPhoneBookIntegration() {
     private fun setupPhoneBookIntegration() {
-        if (CapabilitiesUtilNew.isPhoneBookIntegrationAvailable(currentUser!!)) {
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(
+                currentUser?.capabilities?.spreedCapability!!,
+                SpreedFeatures.PHONEBOOK_SEARCH
+            )
+        ) {
             binding.settingsPhoneBookIntegration.visibility = View.VISIBLE
             binding.settingsPhoneBookIntegration.visibility = View.VISIBLE
         } else {
         } else {
             binding.settingsPhoneBookIntegration.visibility = View.GONE
             binding.settingsPhoneBookIntegration.visibility = View.GONE
@@ -507,7 +512,7 @@ class SettingsActivity : BaseActivity() {
         var port = -1
         var port = -1
         val uri: URI
         val uri: URI
         try {
         try {
-            uri = URI(currentUser!!.baseUrl)
+            uri = URI(currentUser!!.baseUrl!!)
             host = uri.host
             host = uri.host
             port = uri.port
             port = uri.port
             Log.d(TAG, "uri is $uri")
             Log.d(TAG, "uri is $uri")
@@ -823,7 +828,7 @@ class SettingsActivity : BaseActivity() {
     private fun setupProfileQueryDisposable() {
     private fun setupProfileQueryDisposable() {
         profileQueryDisposable = ncApi.getUserProfile(
         profileQueryDisposable = ncApi.getUserProfile(
             credentials,
             credentials,
-            ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl)
+            ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!)
         )
         )
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread())
             .observeOn(AndroidSchedulers.mainThread())
@@ -854,7 +859,7 @@ class SettingsActivity : BaseActivity() {
 
 
     private fun setupServerAgeWarning() {
     private fun setupServerAgeWarning() {
         when {
         when {
-            CapabilitiesUtilNew.isServerEOL(currentUser!!.capabilities) -> {
+            CapabilitiesUtil.isServerEOL(currentUser!!.serverVersion!!.major) -> {
                 binding.serverAgeWarningText.setTextColor(ContextCompat.getColor((context), R.color.nc_darkRed))
                 binding.serverAgeWarningText.setTextColor(ContextCompat.getColor((context), R.color.nc_darkRed))
                 binding.serverAgeWarningText.setText(R.string.nc_settings_server_eol)
                 binding.serverAgeWarningText.setText(R.string.nc_settings_server_eol)
                 binding.serverAgeWarningIcon.setColorFilter(
                 binding.serverAgeWarningIcon.setColorFilter(
@@ -863,7 +868,7 @@ class SettingsActivity : BaseActivity() {
                 )
                 )
             }
             }
 
 
-            CapabilitiesUtilNew.isServerAlmostEOL(currentUser!!) -> {
+            CapabilitiesUtil.isServerAlmostEOL(currentUser!!.serverVersion!!.major) -> {
                 binding.serverAgeWarningText.setTextColor(
                 binding.serverAgeWarningText.setTextColor(
                     ContextCompat.getColor((context), R.color.nc_darkYellow)
                     ContextCompat.getColor((context), R.color.nc_darkYellow)
                 )
                 )
@@ -889,8 +894,8 @@ class SettingsActivity : BaseActivity() {
             binding.settingsIncognitoKeyboardSwitch.visibility = View.GONE
             binding.settingsIncognitoKeyboardSwitch.visibility = View.GONE
         }
         }
 
 
-        if (CapabilitiesUtilNew.isReadStatusAvailable(currentUser!!)) {
-            binding.settingsReadPrivacySwitch.isChecked = !CapabilitiesUtilNew.isReadStatusPrivate(currentUser!!)
+        if (CapabilitiesUtil.isReadStatusAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
+            binding.settingsReadPrivacySwitch.isChecked = !CapabilitiesUtil.isReadStatusPrivate(currentUser!!)
         } else {
         } else {
             binding.settingsReadPrivacy.visibility = View.GONE
             binding.settingsReadPrivacy.visibility = View.GONE
         }
         }
@@ -954,10 +959,10 @@ class SettingsActivity : BaseActivity() {
     private fun setupTypingStatusSetting() {
     private fun setupTypingStatusSetting() {
         if (currentUser!!.externalSignalingServer?.externalSignalingServer?.isNotEmpty() == true) {
         if (currentUser!!.externalSignalingServer?.externalSignalingServer?.isNotEmpty() == true) {
             binding.settingsTypingStatusOnlyWithHpb.visibility = View.GONE
             binding.settingsTypingStatusOnlyWithHpb.visibility = View.GONE
-            Log.i(TAG, "Typing Status Available: ${CapabilitiesUtilNew.isTypingStatusAvailable(currentUser!!)}")
+            Log.i(TAG, "Typing Status Available: ${CapabilitiesUtil.isTypingStatusAvailable(currentUser!!)}")
 
 
-            if (CapabilitiesUtilNew.isTypingStatusAvailable(currentUser!!)) {
-                binding.settingsTypingStatusSwitch.isChecked = !CapabilitiesUtilNew.isTypingStatusPrivate(currentUser!!)
+            if (CapabilitiesUtil.isTypingStatusAvailable(currentUser!!)) {
+                binding.settingsTypingStatusSwitch.isChecked = !CapabilitiesUtil.isTypingStatusPrivate(currentUser!!)
             } else {
             } else {
                 binding.settingsTypingStatus.visibility = View.GONE
                 binding.settingsTypingStatus.visibility = View.GONE
             }
             }
@@ -1209,7 +1214,7 @@ class SettingsActivity : BaseActivity() {
     private fun checkForPhoneNumber() {
     private fun checkForPhoneNumber() {
         ncApi.getUserData(
         ncApi.getUserData(
             ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
             ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-            ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl)
+            ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!)
         ).subscribeOn(Schedulers.io())
         ).subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread())
             .observeOn(AndroidSchedulers.mainThread())
             .subscribe(object : Observer<UserProfileOverall> {
             .subscribe(object : Observer<UserProfileOverall> {
@@ -1294,7 +1299,7 @@ class SettingsActivity : BaseActivity() {
         val phoneNumber = textInputLayout.editText!!.text.toString()
         val phoneNumber = textInputLayout.editText!!.text.toString()
         ncApi.setUserData(
         ncApi.setUserData(
             ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
             ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-            ApiUtils.getUrlForUserData(currentUser!!.baseUrl, currentUser!!.userId),
+            ApiUtils.getUrlForUserData(currentUser!!.baseUrl!!, currentUser!!.userId!!),
             "phone",
             "phone",
             phoneNumber
             phoneNumber
         ).subscribeOn(Schedulers.io())
         ).subscribeOn(Schedulers.io())
@@ -1349,7 +1354,7 @@ class SettingsActivity : BaseActivity() {
                     val json = "{\"key\": \"read_status_privacy\", \"value\" : $booleanValue}"
                     val json = "{\"key\": \"read_status_privacy\", \"value\" : $booleanValue}"
                     ncApi.setReadStatusPrivacy(
                     ncApi.setReadStatusPrivacy(
                         ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
                         ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-                        ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl),
+                        ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl!!),
                         json.toRequestBody("application/json".toMediaTypeOrNull())
                         json.toRequestBody("application/json".toMediaTypeOrNull())
                     )
                     )
                         .subscribeOn(Schedulers.io())
                         .subscribeOn(Schedulers.io())
@@ -1387,7 +1392,7 @@ class SettingsActivity : BaseActivity() {
                     val json = "{\"key\": \"typing_privacy\", \"value\" : $booleanValue}"
                     val json = "{\"key\": \"typing_privacy\", \"value\" : $booleanValue}"
                     ncApi.setTypingStatusPrivacy(
                     ncApi.setTypingStatusPrivacy(
                         ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
                         ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
-                        ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl),
+                        ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl!!),
                         json.toRequestBody("application/json".toMediaTypeOrNull())
                         json.toRequestBody("application/json".toMediaTypeOrNull())
                     )
                     )
                         .subscribeOn(Schedulers.io())
                         .subscribeOn(Schedulers.io())

+ 3 - 3
app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepositoryImpl.kt

@@ -105,7 +105,7 @@ class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, pr
                         fileParameters["link"]!!,
                         fileParameters["link"]!!,
                         fileParameters["mimetype"]!!,
                         fileParameters["mimetype"]!!,
                         previewAvailable,
                         previewAvailable,
-                        previewLink(fileParameters["id"], parameters.baseUrl)
+                        previewLink(fileParameters["id"], parameters.baseUrl!!)
                     )
                     )
                 } else if (it.value.messageParameters?.containsKey("object") == true) {
                 } else if (it.value.messageParameters?.containsKey("object") == true) {
                     val objectParameters = it.value.messageParameters!!["object"]!!
                     val objectParameters = it.value.messageParameters!!["object"]!!
@@ -184,7 +184,7 @@ class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, pr
 
 
         return ncApi.getSharedItemsOverview(
         return ncApi.getSharedItemsOverview(
             credentials,
             credentials,
-            ApiUtils.getUrlForChatSharedItemsOverview(1, parameters.baseUrl, parameters.roomToken),
+            ApiUtils.getUrlForChatSharedItemsOverview(1, parameters.baseUrl!!, parameters.roomToken),
             1
             1
         ).map {
         ).map {
             val types = mutableSetOf<SharedItemType>()
             val types = mutableSetOf<SharedItemType>()
@@ -206,7 +206,7 @@ class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, pr
     private fun previewLink(fileId: String?, baseUrl: String): String {
     private fun previewLink(fileId: String?, baseUrl: String): String {
         return ApiUtils.getUrlForFilePreviewWithFileId(
         return ApiUtils.getUrlForFilePreviewWithFileId(
             baseUrl,
             baseUrl,
-            fileId,
+            fileId!!,
             sharedApplication!!.resources.getDimensionPixelSize(R.dimen.maximum_file_preview_size)
             sharedApplication!!.resources.getDimensionPixelSize(R.dimen.maximum_file_preview_size)
         )
         )
     }
     }

+ 4 - 4
app/src/main/java/com/nextcloud/talk/translate/viewmodels/TranslateViewModel.kt

@@ -37,8 +37,8 @@ class TranslateViewModel @Inject constructor(
 
 
     fun translateMessage(toLanguage: String, fromLanguage: String?, text: String) {
     fun translateMessage(toLanguage: String, fromLanguage: String?, text: String) {
         val currentUser: User = userManager.currentUser.blockingGet()
         val currentUser: User = userManager.currentUser.blockingGet()
-        val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
-        val url: String = ApiUtils.getUrlForTranslation(currentUser.baseUrl)
+        val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
+        val url: String = ApiUtils.getUrlForTranslation(currentUser.baseUrl!!)
         val calculatedFromLanguage =
         val calculatedFromLanguage =
             if (fromLanguage == null || fromLanguage == "") {
             if (fromLanguage == null || fromLanguage == "") {
                 null
                 null
@@ -60,8 +60,8 @@ class TranslateViewModel @Inject constructor(
 
 
     fun getLanguages() {
     fun getLanguages() {
         val currentUser: User = userManager.currentUser.blockingGet()
         val currentUser: User = userManager.currentUser.blockingGet()
-        val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
-        val url: String = ApiUtils.getUrlForLanguages(currentUser.baseUrl)
+        val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
+        val url: String = ApiUtils.getUrlForLanguages(currentUser.baseUrl!!)
         Log.d(TAG, "URL is: $url")
         Log.d(TAG, "URL is: $url")
         repository.getLanguages(authorization, url)
         repository.getLanguages(authorization, url)
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())

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

@@ -57,7 +57,7 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User, val viewThemeUti
     fun showFor(user: String, context: Context) {
     fun showFor(user: String, context: Context) {
         ncApi.hoverCard(
         ncApi.hoverCard(
             ApiUtils.getCredentials(userModel.username, userModel.token),
             ApiUtils.getCredentials(userModel.username, userModel.token),
-            ApiUtils.getUrlForHoverCard(userModel.baseUrl, user)
+            ApiUtils.getUrlForHoverCard(userModel.baseUrl!!, user)
         ).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
         ).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
             .subscribe(object : Observer<HoverCardOverall> {
             .subscribe(object : Observer<HoverCardOverall> {
                 override fun onSubscribe(d: Disposable) {
                 override fun onSubscribe(d: Disposable) {
@@ -121,10 +121,10 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User, val viewThemeUti
 
 
     private fun talkTo(userId: String, context: Context) {
     private fun talkTo(userId: String, context: Context) {
         val apiVersion =
         val apiVersion =
-            ApiUtils.getConversationApiVersion(userModel, intArrayOf(ApiUtils.APIv4, 1))
+            ApiUtils.getConversationApiVersion(userModel, intArrayOf(ApiUtils.API_V4, 1))
         val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
         val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
             apiVersion,
             apiVersion,
-            userModel.baseUrl,
+            userModel.baseUrl!!,
             "1",
             "1",
             null,
             null,
             userId,
             userId,

+ 7 - 6
app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt

@@ -35,7 +35,8 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.chat.ChatActivity
 import com.nextcloud.talk.chat.ChatActivity
 import com.nextcloud.talk.databinding.DialogAttachmentBinding
 import com.nextcloud.talk.databinding.DialogAttachmentBinding
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.SpreedFeatures
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import javax.inject.Inject
 import javax.inject.Inject
 
 
 @AutoInjector(NextcloudTalkApplication::class)
 @AutoInjector(NextcloudTalkApplication::class)
@@ -61,7 +62,7 @@ class AttachmentDialog(val activity: Activity, var chatActivity: ChatActivity) :
     }
     }
 
 
     private fun initItemsStrings() {
     private fun initItemsStrings() {
-        var serverName = CapabilitiesUtilNew.getServerName(chatActivity.conversationUser)
+        var serverName = CapabilitiesUtil.getServerName(chatActivity.conversationUser)
         dialogAttachmentBinding.txtAttachFileFromCloud.text = chatActivity.resources?.let {
         dialogAttachmentBinding.txtAttachFileFromCloud.text = chatActivity.resources?.let {
             if (serverName.isNullOrEmpty()) {
             if (serverName.isNullOrEmpty()) {
                 serverName = it.getString(R.string.nc_server_product_name)
                 serverName = it.getString(R.string.nc_server_product_name)
@@ -71,15 +72,15 @@ class AttachmentDialog(val activity: Activity, var chatActivity: ChatActivity) :
     }
     }
 
 
     private fun initItemsVisibility() {
     private fun initItemsVisibility() {
-        if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(
-                chatActivity.conversationUser,
-                "geo-location-sharing"
+        if (!CapabilitiesUtil.hasSpreedFeatureCapability(
+                chatActivity.spreedCapabilities,
+                SpreedFeatures.GEO_LOCATION_SHARING
             )
             )
         ) {
         ) {
             dialogAttachmentBinding.menuShareLocation.visibility = View.GONE
             dialogAttachmentBinding.menuShareLocation.visibility = View.GONE
         }
         }
 
 
-        if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(chatActivity.conversationUser, "talk-polls") ||
+        if (!CapabilitiesUtil.hasSpreedFeatureCapability(chatActivity.spreedCapabilities, SpreedFeatures.TALK_POLLS) ||
             chatActivity.isOneToOneConversation()
             chatActivity.isOneToOneConversation()
         ) {
         ) {
             dialogAttachmentBinding.menuAttachPoll.visibility = View.GONE
             dialogAttachmentBinding.menuAttachPoll.visibility = View.GONE

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

@@ -54,7 +54,7 @@ import com.nextcloud.talk.ui.theme.ViewThemeUtils;
 import com.nextcloud.talk.users.UserManager;
 import com.nextcloud.talk.users.UserManager;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.DisplayUtils;
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
+import com.nextcloud.talk.utils.CapabilitiesUtil;
 
 
 import java.net.CookieManager;
 import java.net.CookieManager;
 import java.util.ArrayList;
 import java.util.ArrayList;
@@ -262,7 +262,7 @@ public class ChooseAccountDialogFragment extends DialogFragment {
     private void loadCurrentStatus(User user) {
     private void loadCurrentStatus(User user) {
         String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken());
         String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken());
 
 
-        if (CapabilitiesUtilNew.isUserStatusAvailable(userManager.getCurrentUser().blockingGet())) {
+        if (CapabilitiesUtil.isUserStatusAvailable(userManager.getCurrentUser().blockingGet())) {
             binding.statusView.setVisibility(View.VISIBLE);
             binding.statusView.setVisibility(View.VISIBLE);
 
 
             ncApi.status(credentials, ApiUtils.getUrlForStatus(user.getBaseUrl())).
             ncApi.status(credentials, ApiUtils.getUrlForStatus(user.getBaseUrl())).

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

@@ -89,7 +89,7 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
         if (user != null) {
         if (user != null) {
             binding!!.currentAccount.userName.text = user.displayName
             binding!!.currentAccount.userName.text = user.displayName
             binding!!.currentAccount.ticker.visibility = View.GONE
             binding!!.currentAccount.ticker.visibility = View.GONE
-            binding!!.currentAccount.account.text = Uri.parse(user.baseUrl).host
+            binding!!.currentAccount.account.text = Uri.parse(user.baseUrl!!).host
             viewThemeUtils!!.platform.colorImageView(binding!!.currentAccount.accountMenu, ColorRole.PRIMARY)
             viewThemeUtils!!.platform.colorImageView(binding!!.currentAccount.accountMenu, ColorRole.PRIMARY)
             if (user.baseUrl != null &&
             if (user.baseUrl != null &&
                 (user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))
                 (user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))

+ 25 - 14
app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt

@@ -46,7 +46,7 @@ import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
 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_ROOM_TOKEN
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import io.reactivex.Observer
 import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.Disposable
 import io.reactivex.disposables.Disposable
@@ -86,7 +86,7 @@ class ConversationsListBottomDialog(
         initItemsVisibility()
         initItemsVisibility()
         initClickListeners()
         initClickListeners()
 
 
-        credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+        credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
     }
     }
 
 
     override fun onStart() {
     override fun onStart() {
@@ -105,7 +105,10 @@ class ConversationsListBottomDialog(
     }
     }
 
 
     private fun initItemsVisibility() {
     private fun initItemsVisibility() {
-        val hasFavoritesCapability = CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "favorites")
+        val hasFavoritesCapability = CapabilitiesUtil.hasSpreedFeatureCapability(
+            currentUser.capabilities?.spreedCapability!!,
+            "favorites"
+        )
         val canModerate = conversation.canModerate(currentUser)
         val canModerate = conversation.canModerate(currentUser)
 
 
         binding.conversationRemoveFromFavorites.visibility = setVisibleIf(
         binding.conversationRemoveFromFavorites.visibility = setVisibleIf(
@@ -116,11 +119,19 @@ class ConversationsListBottomDialog(
         )
         )
 
 
         binding.conversationMarkAsRead.visibility = setVisibleIf(
         binding.conversationMarkAsRead.visibility = setVisibleIf(
-            conversation.unreadMessages > 0 && CapabilitiesUtilNew.canSetChatReadMarker(currentUser)
+            conversation.unreadMessages > 0 && CapabilitiesUtil.hasSpreedFeatureCapability(
+                currentUser
+                    .capabilities?.spreedCapability!!,
+                "chat-read-marker"
+            )
         )
         )
 
 
         binding.conversationMarkAsUnread.visibility = setVisibleIf(
         binding.conversationMarkAsUnread.visibility = setVisibleIf(
-            conversation.unreadMessages <= 0 && CapabilitiesUtilNew.canMarkRoomAsUnread(currentUser)
+            conversation.unreadMessages <= 0 && CapabilitiesUtil.hasSpreedFeatureCapability(
+                currentUser
+                    .capabilities?.spreedCapability!!,
+                "chat-unread"
+            )
         )
         )
 
 
         binding.conversationOperationRename.visibility = setVisibleIf(
         binding.conversationOperationRename.visibility = setVisibleIf(
@@ -178,12 +189,12 @@ class ConversationsListBottomDialog(
     }
     }
 
 
     private fun addConversationToFavorites() {
     private fun addConversationToFavorites() {
-        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
+        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
         ncApi.addConversationToFavorites(
         ncApi.addConversationToFavorites(
             credentials,
             credentials,
             ApiUtils.getUrlForRoomFavorite(
             ApiUtils.getUrlForRoomFavorite(
                 apiVersion,
                 apiVersion,
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 conversation.token
                 conversation.token
             )
             )
         )
         )
@@ -218,12 +229,12 @@ class ConversationsListBottomDialog(
     }
     }
 
 
     private fun removeConversationFromFavorites() {
     private fun removeConversationFromFavorites() {
-        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
+        val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
         ncApi.removeConversationFromFavorites(
         ncApi.removeConversationFromFavorites(
             credentials,
             credentials,
             ApiUtils.getUrlForRoomFavorite(
             ApiUtils.getUrlForRoomFavorite(
                 apiVersion,
                 apiVersion,
-                currentUser.baseUrl,
+                currentUser.baseUrl!!,
                 conversation.token
                 conversation.token
             )
             )
         )
         )
@@ -262,8 +273,8 @@ class ConversationsListBottomDialog(
             credentials,
             credentials,
             ApiUtils.getUrlForChatReadMarker(
             ApiUtils.getUrlForChatReadMarker(
                 chatApiVersion(),
                 chatApiVersion(),
-                currentUser.baseUrl,
-                conversation.token
+                currentUser.baseUrl!!,
+                conversation.token!!
             )
             )
         )
         )
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
@@ -301,8 +312,8 @@ class ConversationsListBottomDialog(
             credentials,
             credentials,
             ApiUtils.getUrlForChatReadMarker(
             ApiUtils.getUrlForChatReadMarker(
                 chatApiVersion(),
                 chatApiVersion(),
-                currentUser.baseUrl,
-                conversation.token
+                currentUser.baseUrl!!,
+                conversation.token!!
             ),
             ),
             conversation.lastMessage!!.jsonMessageId
             conversation.lastMessage!!.jsonMessageId
         )
         )
@@ -396,7 +407,7 @@ class ConversationsListBottomDialog(
     }
     }
 
 
     private fun chatApiVersion(): Int {
     private fun chatApiVersion(): Int {
-        return ApiUtils.getChatApiVersion(currentUser, intArrayOf(ApiUtils.APIv1))
+        return ApiUtils.getChatApiVersion(currentUser.capabilities!!.spreedCapability!!, intArrayOf(ApiUtils.API_V1))
     }
     }
 
 
     companion object {
     companion object {

+ 14 - 6
app/src/main/java/com/nextcloud/talk/ui/dialog/DateTimePickerFragment.kt

@@ -50,7 +50,8 @@ import javax.inject.Inject
 class DateTimePickerFragment(
 class DateTimePickerFragment(
     token: String,
     token: String,
     id: String,
     id: String,
-    chatViewModel: ChatViewModel
+    chatViewModel: ChatViewModel,
+    private val chatApiVersion: Int
 ) : DialogFragment() {
 ) : DialogFragment() {
     lateinit var binding: DialogDateTimePickerBinding
     lateinit var binding: DialogDateTimePickerBinding
     private var dialogView: View? = null
     private var dialogView: View? = null
@@ -144,7 +145,7 @@ class DateTimePickerFragment(
     }
     }
 
 
     private fun getReminder() {
     private fun getReminder() {
-        viewModel.getReminder(userManager.currentUser.blockingGet(), roomToken, messageId)
+        viewModel.getReminder(userManager.currentUser.blockingGet(), roomToken, messageId, chatApiVersion)
     }
     }
 
 
     private fun showDelete(value: Boolean) {
     private fun showDelete(value: Boolean) {
@@ -221,12 +222,18 @@ class DateTimePickerFragment(
         binding.buttonClose.setOnClickListener { dismiss() }
         binding.buttonClose.setOnClickListener { dismiss() }
         binding.buttonSet.setOnClickListener {
         binding.buttonSet.setOnClickListener {
             currentTimeStamp?.let { time ->
             currentTimeStamp?.let { time ->
-                viewModel.setReminder(userManager.currentUser.blockingGet(), roomToken, messageId, time.toInt())
+                viewModel.setReminder(
+                    userManager.currentUser.blockingGet(),
+                    roomToken,
+                    messageId,
+                    time.toInt(),
+                    chatApiVersion
+                )
             }
             }
             dismiss()
             dismiss()
         }
         }
         binding.buttonDelete.setOnClickListener {
         binding.buttonDelete.setOnClickListener {
-            viewModel.deleteReminder(userManager.currentUser.blockingGet(), roomToken, messageId)
+            viewModel.deleteReminder(userManager.currentUser.blockingGet(), roomToken, messageId, chatApiVersion)
         }
         }
     }
     }
 
 
@@ -299,11 +306,12 @@ class DateTimePickerFragment(
         private const val HOUR_SIX_PM = 18
         private const val HOUR_SIX_PM = 18
 
 
         @JvmStatic
         @JvmStatic
-        fun newInstance(token: String, id: String, chatViewModel: ChatViewModel) =
+        fun newInstance(token: String, id: String, chatViewModel: ChatViewModel, chatApiVersion: Int) =
             DateTimePickerFragment(
             DateTimePickerFragment(
                 token,
                 token,
                 id,
                 id,
-                chatViewModel
+                chatViewModel,
+                chatApiVersion
             )
             )
     }
     }
 }
 }

+ 16 - 9
app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt

@@ -46,14 +46,17 @@ import com.nextcloud.talk.models.domain.ConversationReadOnlyState
 import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.domain.ReactionAddedModel
 import com.nextcloud.talk.models.domain.ReactionAddedModel
 import com.nextcloud.talk.models.domain.ReactionDeletedModel
 import com.nextcloud.talk.models.domain.ReactionDeletedModel
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.repositories.reactions.ReactionsRepository
 import com.nextcloud.talk.repositories.reactions.ReactionsRepository
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.SpreedFeatures
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.ConversationUtils
 import com.nextcloud.talk.utils.DateConstants
 import com.nextcloud.talk.utils.DateConstants
 import com.nextcloud.talk.utils.DateUtils
 import com.nextcloud.talk.utils.DateUtils
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
+import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
 import com.vanniktech.emoji.EmojiPopup
 import com.vanniktech.emoji.EmojiPopup
 import com.vanniktech.emoji.EmojiTextView
 import com.vanniktech.emoji.EmojiTextView
 import com.vanniktech.emoji.installDisableKeyboardInput
 import com.vanniktech.emoji.installDisableKeyboardInput
@@ -72,7 +75,8 @@ class MessageActionsDialog(
     private val user: User?,
     private val user: User?,
     private val currentConversation: ConversationModel?,
     private val currentConversation: ConversationModel?,
     private val showMessageDeletionButton: Boolean,
     private val showMessageDeletionButton: Boolean,
-    private val hasChatPermission: Boolean
+    private val hasChatPermission: Boolean,
+    private val spreedCapabilities: SpreedCapability
 ) : BottomSheetDialog(chatActivity) {
 ) : BottomSheetDialog(chatActivity) {
 
 
     @Inject
     @Inject
@@ -100,8 +104,8 @@ class MessageActionsDialog(
 
 
     private val isUserAllowedToEdit = chatActivity.userAllowedByPrivilages(message)
     private val isUserAllowedToEdit = chatActivity.userAllowedByPrivilages(message)
 
 
-    private val isMessageEditable = CapabilitiesUtilNew.hasSpreedFeatureCapability(
-        user,
+    private val isMessageEditable = CapabilitiesUtil.hasSpreedFeatureCapability(
+        spreedCapabilities,
         "edit-messages"
         "edit-messages"
     ) && messageHasRegularText && !isOlderThanTwentyFourHours && isUserAllowedToEdit
     ) && messageHasRegularText && !isOlderThanTwentyFourHours && isUserAllowedToEdit
 
 
@@ -116,9 +120,9 @@ class MessageActionsDialog(
         viewThemeUtils.platform.themeDialog(dialogMessageActionsBinding.root)
         viewThemeUtils.platform.themeDialog(dialogMessageActionsBinding.root)
         initEmojiBar(hasChatPermission)
         initEmojiBar(hasChatPermission)
         initMenuItemCopy(!message.isDeleted)
         initMenuItemCopy(!message.isDeleted)
-        val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
+        val apiVersion = ApiUtils.getConversationApiVersion(user!!, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
         chatActivity.chatViewModel.checkForNoteToSelf(
         chatActivity.chatViewModel.checkForNoteToSelf(
-            ApiUtils.getCredentials(user!!.username, user.token),
+            ApiUtils.getCredentials(user!!.username, user.token)!!,
             ApiUtils.getUrlForRooms(
             ApiUtils.getUrlForRooms(
                 apiVersion,
                 apiVersion,
                 user.baseUrl
                 user.baseUrl
@@ -144,7 +148,7 @@ class MessageActionsDialog(
         initMenuItemTranslate(
         initMenuItemTranslate(
             !message.isDeleted &&
             !message.isDeleted &&
                 ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
                 ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
-                CapabilitiesUtilNew.isTranslationsSupported(user)
+                CapabilitiesUtil.isTranslationsSupported(spreedCapabilities)
         )
         )
         initMenuEditorDetails(message.lastEditTimestamp != 0L && !message.isDeleted)
         initMenuEditorDetails(message.lastEditTimestamp != 0L && !message.isDeleted)
         initMenuReplyToMessage(message.replyable && hasChatPermission)
         initMenuReplyToMessage(message.replyable && hasChatPermission)
@@ -160,7 +164,10 @@ class MessageActionsDialog(
             ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
             ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
                 !(message.isDeletedCommentMessage || message.isDeleted)
                 !(message.isDeletedCommentMessage || message.isDeleted)
         )
         )
-        initMenuRemindMessage(!message.isDeleted && CapabilitiesUtilNew.isRemindSupported(user))
+        initMenuRemindMessage(
+            !message.isDeleted && CapabilitiesUtil.hasSpreedFeatureCapability
+                (spreedCapabilities, "remind-me-later")
+        )
         initMenuMarkAsUnread(
         initMenuMarkAsUnread(
             message.previousMessageId > NO_PREVIOUS_MESSAGE_ID &&
             message.previousMessageId > NO_PREVIOUS_MESSAGE_ID &&
                 ChatMessage.MessageType.SYSTEM_MESSAGE != message.getCalculateMessageType()
                 ChatMessage.MessageType.SYSTEM_MESSAGE != message.getCalculateMessageType()
@@ -242,7 +249,7 @@ class MessageActionsDialog(
     }
     }
 
 
     private fun initEmojiBar(hasChatPermission: Boolean) {
     private fun initEmojiBar(hasChatPermission: Boolean) {
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "reactions") &&
+        if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.REACTIONS) &&
             isPermitted(hasChatPermission) &&
             isPermitted(hasChatPermission) &&
             isReactableMessageType(message)
             isReactableMessageType(message)
         ) {
         ) {

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

@@ -35,7 +35,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.databinding.DialogMoreCallActionsBinding
 import com.nextcloud.talk.databinding.DialogMoreCallActionsBinding
 import com.nextcloud.talk.raisehand.viewmodel.RaiseHandViewModel
 import com.nextcloud.talk.raisehand.viewmodel.RaiseHandViewModel
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.utils.CapabilitiesUtil
 import com.nextcloud.talk.viewmodels.CallRecordingViewModel
 import com.nextcloud.talk.viewmodels.CallRecordingViewModel
 import com.vanniktech.emoji.EmojiTextView
 import com.vanniktech.emoji.EmojiTextView
 import javax.inject.Inject
 import javax.inject.Inject
@@ -72,7 +72,7 @@ class MoreCallActionsDialog(private val callActivity: CallActivity) : BottomShee
     }
     }
 
 
     private fun initItemsVisibility() {
     private fun initItemsVisibility() {
-        if (CapabilitiesUtilNew.isCallReactionsSupported(callActivity.conversationUser)) {
+        if (CapabilitiesUtil.isCallReactionsSupported(callActivity.conversationUser)) {
             binding.callEmojiBar.visibility = View.VISIBLE
             binding.callEmojiBar.visibility = View.VISIBLE
         } else {
         } else {
             binding.callEmojiBar.visibility = View.GONE
             binding.callEmojiBar.visibility = View.GONE
@@ -102,7 +102,7 @@ class MoreCallActionsDialog(private val callActivity: CallActivity) : BottomShee
     }
     }
 
 
     private fun initEmojiBar() {
     private fun initEmojiBar() {
-        if (CapabilitiesUtilNew.isCallReactionsSupported(callActivity.conversationUser)) {
+        if (CapabilitiesUtil.isCallReactionsSupported(callActivity.conversationUser)) {
             binding.advancedCallOptionsTitle.visibility = View.GONE
             binding.advancedCallOptionsTitle.visibility = View.GONE
 
 
             val capabilities = callActivity.conversationUser?.capabilities
             val capabilities = callActivity.conversationUser?.capabilities

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

@@ -128,8 +128,8 @@ class SetStatusDialogFragment :
             currentUser = currentUserProvider?.currentUser?.blockingGet()
             currentUser = currentUserProvider?.currentUser?.blockingGet()
             currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM)
             currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM)
 
 
-            credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
-            ncApi.getPredefinedStatuses(credentials, ApiUtils.getUrlForPredefinedStatuses(currentUser?.baseUrl))
+            credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)!!
+            ncApi.getPredefinedStatuses(credentials, ApiUtils.getUrlForPredefinedStatuses(currentUser?.baseUrl!!))
                 .subscribeOn(Schedulers.io())
                 .subscribeOn(Schedulers.io())
                 .observeOn(AndroidSchedulers.mainThread())
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(object : Observer<ResponseBody> {
                 .subscribe(object : Observer<ResponseBody> {
@@ -369,7 +369,7 @@ class SetStatusDialogFragment :
 
 
     private fun clearStatus() {
     private fun clearStatus() {
         val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
         val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
-        ncApi.statusDeleteMessage(credentials, ApiUtils.getUrlForStatusMessage(currentUser?.baseUrl))
+        ncApi.statusDeleteMessage(credentials, ApiUtils.getUrlForStatusMessage(currentUser?.baseUrl!!))
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer<GenericOverall> {
             .observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer<GenericOverall> {
                 override fun onSubscribe(d: Disposable) {
                 override fun onSubscribe(d: Disposable) {
@@ -393,7 +393,7 @@ class SetStatusDialogFragment :
     private fun setStatus(statusType: StatusType) {
     private fun setStatus(statusType: StatusType) {
         visualizeStatus(statusType)
         visualizeStatus(statusType)
 
 
-        ncApi.setStatusType(credentials, ApiUtils.getUrlForSetStatusType(currentUser?.baseUrl), statusType.string)
+        ncApi.setStatusType(credentials, ApiUtils.getUrlForSetStatusType(currentUser?.baseUrl!!), statusType.string)
             .subscribeOn(
             .subscribeOn(
                 Schedulers
                 Schedulers
                     .io()
                     .io()
@@ -468,7 +468,7 @@ class SetStatusDialogFragment :
         ) {
         ) {
             ncApi.setCustomStatusMessage(
             ncApi.setCustomStatusMessage(
                 credentials,
                 credentials,
-                ApiUtils.getUrlForSetCustomStatus(currentUser?.baseUrl),
+                ApiUtils.getUrlForSetCustomStatus(currentUser?.baseUrl!!),
                 statusIcon,
                 statusIcon,
                 inputText,
                 inputText,
                 clearAt
                 clearAt
@@ -499,7 +499,7 @@ class SetStatusDialogFragment :
 
 
             ncApi.setPredefinedStatusMessage(
             ncApi.setPredefinedStatusMessage(
                 credentials,
                 credentials,
-                ApiUtils.getUrlForSetPredefinedStatus(currentUser?.baseUrl),
+                ApiUtils.getUrlForSetPredefinedStatus(currentUser?.baseUrl!!),
                 selectedPredefinedStatus!!.id,
                 selectedPredefinedStatus!!.id,
                 if (clearAt == -1L) null else clearAt
                 if (clearAt == -1L) null else clearAt
             )
             )

+ 2 - 2
app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt

@@ -154,7 +154,7 @@ class ShowReactionsDialog(
         ncApi.getReactions(
         ncApi.getReactions(
             credentials,
             credentials,
             ApiUtils.getUrlForMessageReaction(
             ApiUtils.getUrlForMessageReaction(
-                user?.baseUrl,
+                user?.baseUrl!!,
                 roomToken,
                 roomToken,
                 chatMessage.id
                 chatMessage.id
             ),
             ),
@@ -209,7 +209,7 @@ class ShowReactionsDialog(
         ncApi.deleteReaction(
         ncApi.deleteReaction(
             credentials,
             credentials,
             ApiUtils.getUrlForMessageReaction(
             ApiUtils.getUrlForMessageReaction(
-                user?.baseUrl,
+                user?.baseUrl!!,
                 roomToken,
                 roomToken,
                 message.id
                 message.id
             ),
             ),

+ 4 - 4
app/src/main/java/com/nextcloud/talk/upload/chunked/ChunkedFileUploader.kt

@@ -81,7 +81,7 @@ class ChunkedFileUploader(
 
 
     init {
     init {
         initHttpClient(okHttpClient, currentUser)
         initHttpClient(okHttpClient, currentUser)
-        remoteChunkUrl = ApiUtils.getUrlForChunkedUpload(currentUser.baseUrl, currentUser.userId)
+        remoteChunkUrl = ApiUtils.getUrlForChunkedUpload(currentUser.baseUrl!!, currentUser.userId!!)
     }
     }
 
 
     @Suppress("Detekt.TooGenericExceptionCaught")
     @Suppress("Detekt.TooGenericExceptionCaught")
@@ -295,7 +295,7 @@ class ChunkedFileUploader(
                 ApiUtils.getCredentials(
                 ApiUtils.getCredentials(
                     currentUser.username,
                     currentUser.username,
                     currentUser.token
                     currentUser.token
-                ),
+                )!!,
                 "Authorization"
                 "Authorization"
             )
             )
         )
         )
@@ -304,8 +304,8 @@ class ChunkedFileUploader(
 
 
     private fun assembleChunks(uploadFolderUri: String, targetPath: String) {
     private fun assembleChunks(uploadFolderUri: String, targetPath: String) {
         val destinationUri: String = ApiUtils.getUrlForFileUpload(
         val destinationUri: String = ApiUtils.getUrlForFileUpload(
-            currentUser.baseUrl,
-            currentUser.userId,
+            currentUser.baseUrl!!,
+            currentUser.userId!!,
             targetPath
             targetPath
         )
         )
         val originUri = "$uploadFolderUri/.file"
         val originUri = "$uploadFolderUri/.file"

+ 1 - 1
app/src/main/java/com/nextcloud/talk/upload/normal/FileUploader.kt

@@ -24,7 +24,7 @@ class FileUploader(
     fun upload(sourceFileUri: Uri, fileName: String, remotePath: String, metaData: String?): Observable<Boolean> {
     fun upload(sourceFileUri: Uri, fileName: String, remotePath: String, metaData: String?): Observable<Boolean> {
         return ncApi.uploadFile(
         return ncApi.uploadFile(
             ApiUtils.getCredentials(currentUser.username, currentUser.token),
             ApiUtils.getCredentials(currentUser.username, currentUser.token),
-            ApiUtils.getUrlForFileUpload(currentUser.baseUrl, currentUser.userId, remotePath),
+            ApiUtils.getUrlForFileUpload(currentUser.baseUrl!!, currentUser.userId!!, remotePath),
             createRequestBody(sourceFileUri)
             createRequestBody(sourceFileUri)
         )
         )
             .subscribeOn(Schedulers.io())
             .subscribeOn(Schedulers.io())

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

@@ -69,7 +69,7 @@ object AccountUtils {
     private fun matchAccounts(importAccount: ImportAccount, user: User): Boolean {
     private fun matchAccounts(importAccount: ImportAccount, user: User): Boolean {
         var accountFound = false
         var accountFound = false
         if (importAccount.token != null) {
         if (importAccount.token != null) {
-            if (UriUtils.hasHttpProtocolPrefixed(importAccount.baseUrl)) {
+            if (UriUtils.hasHttpProtocolPrefixed(importAccount.baseUrl!!)) {
                 if (
                 if (
                     user.username == importAccount.username &&
                     user.username == importAccount.username &&
                     user.baseUrl == importAccount.baseUrl
                     user.baseUrl == importAccount.baseUrl

+ 0 - 559
app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java

@@ -1,559 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Marcel Hibbe
- * @author Tim Krüger
- * Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
- * Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
- *
- * 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.utils;
-
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.nextcloud.talk.BuildConfig;
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.data.user.model.User;
-import com.nextcloud.talk.models.RetrofitBucket;
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
-
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import okhttp3.Credentials;
-
-public class ApiUtils {
-    public static final int APIv1 = 1;
-    public static final int APIv2 = 2;
-    public static final int APIv3 = 3;
-    public static final int APIv4 = 4;
-    public static final int AVATAR_SIZE_BIG = 512;
-    public static final int AVATAR_SIZE_SMALL = 64;
-    private static final String TAG = "ApiUtils";
-    private static final String ocsApiVersion = "/ocs/v2.php";
-    private static final String spreedApiVersion = "/apps/spreed/api/v1";
-    private static final String spreedApiBase = ocsApiVersion + "/apps/spreed/api/v";
-
-    private static final String userAgent = "Mozilla/5.0 (Android) Nextcloud-Talk v";
-
-    public static String getUserAgent() {
-        return userAgent + BuildConfig.VERSION_NAME;
-    }
-
-    /**
-     * @deprecated This is only supported on API v1-3, in API v4+ please use
-     * {@link ApiUtils#getUrlForAttendees(int, String, String)} instead.
-     */
-    @Deprecated
-    public static String getUrlForRemovingParticipantFromConversation(String baseUrl, String roomToken, boolean isGuest) {
-        String url = getUrlForParticipants(APIv1, baseUrl, roomToken);
-
-        if (isGuest) {
-            url += "/guests";
-        }
-
-        return url;
-    }
-
-    public static RetrofitBucket getRetrofitBucketForContactsSearch(String baseUrl, @Nullable String searchQuery) {
-        RetrofitBucket retrofitBucket = new RetrofitBucket();
-        retrofitBucket.setUrl(baseUrl + ocsApiVersion + "/apps/files_sharing/api/v1/sharees");
-
-        Map<String, String> queryMap = new HashMap<>();
-
-        if (searchQuery == null) {
-            searchQuery = "";
-        }
-        queryMap.put("format", "json");
-        queryMap.put("search", searchQuery);
-        queryMap.put("itemType", "call");
-
-        retrofitBucket.setQueryMap(queryMap);
-
-        return retrofitBucket;
-    }
-
-    public static String getUrlForFilePreviewWithRemotePath(String baseUrl, String remotePath, int px) {
-        return baseUrl + "/index.php/core/preview.png?file="
-            + Uri.encode(remotePath, "UTF-8")
-            + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1";
-    }
-
-    public static String getUrlForFilePreviewWithFileId(String baseUrl, String fileId, int px) {
-        return baseUrl + "/index.php/core/preview?fileId="
-            + fileId + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1";
-    }
-
-    public static String getSharingUrl(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/apps/files_sharing/api/v1/shares";
-    }
-
-    public static RetrofitBucket getRetrofitBucketForContactsSearchFor14(String baseUrl, @Nullable String searchQuery) {
-        RetrofitBucket retrofitBucket = getRetrofitBucketForContactsSearch(baseUrl, searchQuery);
-        retrofitBucket.setUrl(baseUrl + ocsApiVersion + "/core/autocomplete/get");
-
-        retrofitBucket.getQueryMap().put("itemId", "new");
-
-        return retrofitBucket;
-    }
-
-    public static String getUrlForCapabilities(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/cloud/capabilities";
-    }
-
-    public static int getCallApiVersion(User capabilities, int[] versions) throws NoSupportedApiException {
-        return getConversationApiVersion(capabilities, versions);
-    }
-
-    public static int getConversationApiVersion(User user, int[] versions) throws NoSupportedApiException {
-        boolean hasApiV4 = false;
-        for (int version : versions) {
-            hasApiV4 |= version == APIv4;
-        }
-
-        if (!hasApiV4) {
-            Exception e = new Exception("Api call did not try conversation-v4 api");
-            Log.d(TAG, e.getMessage(), e);
-        }
-
-        for (int version : versions) {
-            if (user.hasSpreedFeatureCapability("conversation-v" + version)) {
-                return version;
-            }
-
-            // Fallback for old API versions
-            if ((version == APIv1 || version == APIv2)) {
-                if (user.hasSpreedFeatureCapability("conversation-v2")) {
-                    return version;
-                }
-                if (version == APIv1 &&
-                    user.hasSpreedFeatureCapability("mention-flag") &&
-                    !user.hasSpreedFeatureCapability("conversation-v4")) {
-                    return version;
-                }
-            }
-        }
-        throw new NoSupportedApiException();
-    }
-
-    public static int getSignalingApiVersion(User user, int[] versions) throws NoSupportedApiException {
-        for (int version : versions) {
-            if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "signaling-v" + version)) {
-                return version;
-            }
-
-            if (version == APIv2 &&
-                CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "sip-support") &&
-                !CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "signaling-v3")) {
-                return version;
-            }
-
-            if (version == APIv1 &&
-                !CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "signaling-v3")) {
-                // Has no capability, we just assume it is always there when there is no v3 or later
-                return version;
-            }
-        }
-        throw new NoSupportedApiException();
-    }
-
-    public static int getChatApiVersion(User user, int[] versions) throws NoSupportedApiException {
-        for (int version : versions) {
-            if (version == APIv1 && CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "chat-v2")) {
-                // Do not question that chat-v2 capability shows the availability of api/v1/ endpoint *see no evil*
-                return version;
-            }
-        }
-        throw new NoSupportedApiException();
-    }
-
-    protected static String getUrlForApi(int version, String baseUrl) {
-        return baseUrl + spreedApiBase + version;
-    }
-
-    public static String getUrlForRooms(int version, String baseUrl) {
-        return getUrlForApi(version, baseUrl) + "/room";
-    }
-
-    public static String getUrlForRoom(int version, String baseUrl, String token) {
-        return getUrlForRooms(version, baseUrl) + "/" + token;
-    }
-
-    public static String getUrlForAttendees(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/attendees";
-    }
-
-    public static String getUrlForParticipants(int version, String baseUrl, String token) {
-        if (token == null || token.isEmpty()) {
-            Log.e(TAG, "token was null or empty");
-        }
-        return getUrlForRoom(version, baseUrl, token) + "/participants";
-    }
-
-    public static String getUrlForParticipantsActive(int version, String baseUrl, String token) {
-        return getUrlForParticipants(version, baseUrl, token) + "/active";
-    }
-
-    public static String getUrlForParticipantsSelf(int version, String baseUrl, String token) {
-        return getUrlForParticipants(version, baseUrl, token) + "/self";
-    }
-
-    public static String getUrlForParticipantsResendInvitations(int version, String baseUrl, String token) {
-        return getUrlForParticipants(version, baseUrl, token) + "/resend-invitations";
-    }
-
-    public static String getUrlForRoomFavorite(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/favorite";
-    }
-
-    public static String getUrlForRoomModerators(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/moderators";
-    }
-
-    public static String getUrlForRoomNotificationLevel(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/notify";
-    }
-
-    public static String getUrlForRoomPublic(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/public";
-    }
-
-    public static String getUrlForRoomPassword(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/password";
-    }
-
-    public static String getUrlForRoomReadOnlyState(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/read-only";
-    }
-
-    public static String getUrlForRoomWebinaryLobby(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/webinar/lobby";
-    }
-
-    public static String getUrlForRoomNotificationCalls(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/notify-calls";
-    }
-
-    public static String getUrlForCall(int version, String baseUrl, String token) {
-        return getUrlForApi(version, baseUrl) + "/call/" + token;
-    }
-
-    public static String getUrlForChat(int version, String baseUrl, String token) {
-        return getUrlForApi(version, baseUrl) + "/chat/" + token;
-    }
-
-    public static String getUrlForMentionSuggestions(int version, String baseUrl, String token) {
-        return getUrlForChat(version, baseUrl, token) + "/mentions";
-    }
-
-    public static String getUrlForChatMessage(int version, String baseUrl, String token, String messageId) {
-        return getUrlForChat(version, baseUrl, token) + "/" + messageId;
-    }
-
-    public static String getUrlForChatSharedItems(int version, String baseUrl, String token) {
-        return getUrlForChat(version, baseUrl, token) + "/share";
-    }
-
-    public static String getUrlForChatSharedItemsOverview(int version, String baseUrl, String token) {
-        return getUrlForChatSharedItems(version, baseUrl, token) + "/overview";
-    }
-
-    public static String getUrlForSignaling(int version, String baseUrl) {
-        return getUrlForApi(version, baseUrl) + "/signaling";
-    }
-
-    public static String getUrlForSignalingBackend(int version, String baseUrl) {
-        return getUrlForSignaling(version, baseUrl) + "/backend";
-    }
-
-    public static String getUrlForSignalingSettings(int version, String baseUrl) {
-        return getUrlForSignaling(version, baseUrl) + "/settings";
-    }
-
-    public static String getUrlForSignaling(int version, String baseUrl, String token) {
-        return getUrlForSignaling(version, baseUrl) + "/" + token;
-    }
-
-    public static String getUrlForOpenConversations(int version, String baseUrl) {
-        return getUrlForApi(version, baseUrl) + "/listed-room";
-    }
-
-    public static RetrofitBucket getRetrofitBucketForCreateRoom(int version, String baseUrl, String roomType,
-                                                                @Nullable String source,
-                                                                @Nullable String invite,
-                                                                @Nullable String conversationName) {
-        RetrofitBucket retrofitBucket = new RetrofitBucket();
-        retrofitBucket.setUrl(getUrlForRooms(version, baseUrl));
-        Map<String, String> queryMap = new HashMap<>();
-
-        queryMap.put("roomType", roomType);
-        if (invite != null) {
-            queryMap.put("invite", invite);
-        }
-        if (source != null) {
-            queryMap.put("source", source);
-        }
-
-        if (conversationName != null) {
-            queryMap.put("roomName", conversationName);
-        }
-
-        retrofitBucket.setQueryMap(queryMap);
-
-        return retrofitBucket;
-    }
-
-    public static RetrofitBucket getRetrofitBucketForAddParticipant(int version, String baseUrl, String token, String user) {
-        RetrofitBucket retrofitBucket = new RetrofitBucket();
-        retrofitBucket.setUrl(getUrlForParticipants(version, baseUrl, token));
-
-        Map<String, String> queryMap = new HashMap<>();
-
-        queryMap.put("newParticipant", user);
-
-        retrofitBucket.setQueryMap(queryMap);
-
-        return retrofitBucket;
-
-    }
-
-    public static RetrofitBucket getRetrofitBucketForAddParticipantWithSource(
-        int version,
-        String baseUrl,
-        String token,
-        String source,
-        String id
-                                                                             ) {
-        RetrofitBucket retrofitBucket = getRetrofitBucketForAddParticipant(version, baseUrl, token, id);
-        retrofitBucket.getQueryMap().put("source", source);
-        return retrofitBucket;
-    }
-
-    public static String getUrlForUserProfile(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/cloud/user";
-    }
-
-    public static String getUrlForUserData(String baseUrl, String userId) {
-        return baseUrl + ocsApiVersion + "/cloud/users/" + userId;
-    }
-
-    public static String getUrlForUserSettings(String baseUrl) {
-        // FIXME Introduce API version
-        return baseUrl + ocsApiVersion + spreedApiVersion + "/settings/user";
-    }
-
-    public static String getUrlPostfixForStatus() {
-        return "/status.php";
-    }
-
-    public static String getUrlForAvatar(String baseUrl, String name, boolean requestBigSize) {
-        int avatarSize = requestBigSize ? AVATAR_SIZE_BIG : AVATAR_SIZE_SMALL;
-        return baseUrl + "/index.php/avatar/" + Uri.encode(name) + "/" + avatarSize;
-    }
-
-    public static String getUrlForGuestAvatar(String baseUrl, String name, boolean requestBigSize) {
-        int avatarSize = requestBigSize ? AVATAR_SIZE_BIG : AVATAR_SIZE_SMALL;
-        return baseUrl + "/index.php/avatar/guest/" + Uri.encode(name) + "/" + avatarSize;
-    }
-
-    public static String getUrlForConversationAvatar(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/avatar";
-    }
-
-    public static String getUrlForConversationAvatarWithVersion(int version, String baseUrl, String token,
-                                                                boolean isDark,
-                                                                String avatarVersion) {
-        String isDarkString = "";
-        if (isDark) {
-            isDarkString = "/dark";
-        }
-
-        String avatarVersionString = "";
-        if (avatarVersion != null) {
-            avatarVersionString = "?avatarVersion=" + avatarVersion;
-        }
-
-        return getUrlForRoom(version, baseUrl, token) + "/avatar" + isDarkString + avatarVersionString;
-    }
-
-    public static String getCredentials(String username, String token) {
-        if (TextUtils.isEmpty(username) && TextUtils.isEmpty(token)) {
-            return null;
-        }
-        return Credentials.basic(username, token, StandardCharsets.UTF_8);
-    }
-
-    public static String getUrlNextcloudPush(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/apps/notifications/api/v2/push";
-    }
-
-    public static String getUrlPushProxy() {
-        return NextcloudTalkApplication.Companion.getSharedApplication().
-            getApplicationContext().getResources().getString(R.string.nc_push_server_url) + "/devices";
-    }
-
-    // see https://github.com/nextcloud/notifications/blob/master/docs/ocs-endpoint-v2.md
-    public static String getUrlForNcNotificationWithId(String baseUrl, String notificationId) {
-        return baseUrl + ocsApiVersion + "/apps/notifications/api/v2/notifications/" + notificationId;
-    }
-
-    public static String getUrlForSearchByNumber(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/cloud/users/search/by-phone";
-    }
-
-    public static String getUrlForFileUpload(String baseUrl, String user, String remotePath) {
-        return baseUrl + "/remote.php/dav/files/" + user + remotePath;
-    }
-
-    public static String getUrlForChunkedUpload(String baseUrl, String user) {
-        return baseUrl + "/remote.php/dav/uploads/" + user;
-    }
-
-    public static String getUrlForFileDownload(String baseUrl, String user, String remotePath) {
-        return baseUrl + "/remote.php/dav/files/" + user + "/" + remotePath;
-    }
-
-    public static String getUrlForTempAvatar(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/apps/spreed/temp-user-avatar";
-    }
-
-    public static String getUrlForUserFields(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/cloud/user/fields";
-    }
-
-    public static String getUrlToSendLocation(int version, String baseUrl, String roomToken) {
-        return getUrlForChat(version, baseUrl, roomToken) + "/share";
-    }
-
-    public static String getUrlForHoverCard(String baseUrl, String userId) {
-        return baseUrl + ocsApiVersion +
-            "/hovercard/v1/" + userId;
-    }
-
-    public static String getUrlForChatReadMarker(int version, String baseUrl, String roomToken) {
-        return getUrlForChat(version, baseUrl, roomToken) + "/read";
-    }
-
-    /*
-     * OCS Status API
-     */
-
-    public static String getUrlForStatus(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/user_status";
-    }
-
-    public static String getUrlForSetStatusType(String baseUrl) {
-        return getUrlForStatus(baseUrl) + "/status";
-    }
-
-    public static String getUrlForPredefinedStatuses(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/predefined_statuses";
-    }
-
-    public static String getUrlForStatusMessage(String baseUrl) {
-        return getUrlForStatus(baseUrl) + "/message";
-    }
-
-    public static String getUrlForSetCustomStatus(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/user_status/message/custom";
-    }
-
-    public static String getUrlForSetPredefinedStatus(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/user_status/message/predefined";
-    }
-
-    public static String getUrlForUserStatuses(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/statuses";
-    }
-
-    public static String getUrlForMessageReaction(String baseUrl,
-                                                  String roomToken,
-                                                  String messageId) {
-        return baseUrl + ocsApiVersion + spreedApiVersion + "/reaction/" + roomToken + "/" + messageId;
-    }
-
-    @NonNull
-    public static String getUrlForUnifiedSearch(@NonNull String baseUrl, @NonNull String providerId) {
-        return baseUrl + ocsApiVersion + "/search/providers/" + providerId + "/search";
-    }
-
-    public static String getUrlForPoll(String baseUrl,
-                                       String roomToken,
-                                       String pollId) {
-        return getUrlForPoll(baseUrl, roomToken) + "/" + pollId;
-    }
-
-    public static String getUrlForPoll(String baseUrl,
-                                       String roomToken) {
-        return baseUrl + ocsApiVersion + spreedApiVersion + "/poll/" + roomToken;
-    }
-
-    public static String getUrlForMessageExpiration(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/message-expiration";
-    }
-
-    public static String getUrlForOpenGraph(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/references/resolve";
-    }
-
-    public static String getUrlForRecording(int version, String baseUrl, String token) {
-        return getUrlForApi(version, baseUrl) + "/recording/" + token;
-    }
-
-    public static String getUrlForRequestAssistance(int version, String baseUrl, String token) {
-        return getUrlForApi(version, baseUrl) + "/breakout-rooms/" + token + "/request-assistance";
-    }
-
-    public static String getUrlForConversationDescription(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/description";
-    }
-
-    public static String getUrlForTranslation(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/translation/translate";
-    }
-
-    public static String getUrlForLanguages(String baseUrl) {
-        return baseUrl + ocsApiVersion + "/translation/languages";
-    }
-
-    public static String getUrlForReminder(User user, String roomToken, String messageId, int version) {
-        String url = ApiUtils.getUrlForChatMessage(version, user.getBaseUrl(), roomToken, messageId);
-        return url + "/reminder";
-    }
-
-    public static String getUrlForRecordingConsent(int version, String baseUrl, String token) {
-        return getUrlForRoom(version, baseUrl, token) + "/recording-consent";
-    }
-
-    public static String getUrlForInvitation(String baseUrl) {
-        return baseUrl + ocsApiVersion + spreedApiVersion + "/federation/invitation";
-    }
-
-    public static String getUrlForInvitationAccept(String baseUrl, int id) {
-        return getUrlForInvitation(baseUrl) + "/" + id;
-    }
-
-    public static String getUrlForInvitationReject(String baseUrl, int id) {
-        return getUrlForInvitation(baseUrl) + "/" + id;
-    }
-}

+ 577 - 0
app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt

@@ -0,0 +1,577 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * @author Marcel Hibbe
+ * @author Tim Krüger
+ * Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
+ * Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * 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.utils
+
+import android.net.Uri
+import android.text.TextUtils
+import android.util.Log
+import com.nextcloud.talk.BuildConfig
+import com.nextcloud.talk.R
+import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.RetrofitBucket
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
+import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
+import okhttp3.Credentials.basic
+import java.nio.charset.StandardCharsets
+
+@Suppress("TooManyFunctions")
+object ApiUtils {
+    private val TAG = ApiUtils::class.java.simpleName
+    const val API_V1 = 1
+    private const val API_V2 = 2
+    const val API_V3 = 3
+    const val API_V4 = 4
+    private const val AVATAR_SIZE_BIG = 512
+    private const val AVATAR_SIZE_SMALL = 64
+    private const val OCS_API_VERSION = "/ocs/v2.php"
+    private const val SPREED_API_VERSION = "/apps/spreed/api/v1"
+    private const val SPREED_API_BASE = "$OCS_API_VERSION/apps/spreed/api/v"
+
+    @JvmStatic
+    val userAgent = "Mozilla/5.0 (Android) Nextcloud-Talk v"
+        get() = field + BuildConfig.VERSION_NAME
+
+    @Deprecated(
+        "This is only supported on API v1-3, in API v4+ please use " +
+            "{@link ApiUtils#getUrlForAttendees(int, String, String)} instead."
+    )
+    fun getUrlForRemovingParticipantFromConversation(baseUrl: String?, roomToken: String?, isGuest: Boolean): String {
+        var url = getUrlForParticipants(API_V1, baseUrl, roomToken)
+        if (isGuest) {
+            url += "/guests"
+        }
+        return url
+    }
+
+    private fun getRetrofitBucketForContactsSearch(baseUrl: String, searchQuery: String?): RetrofitBucket {
+        var query = searchQuery
+        val retrofitBucket = RetrofitBucket()
+        retrofitBucket.url = "$baseUrl$OCS_API_VERSION/apps/files_sharing/api/v1/sharees"
+        val queryMap: MutableMap<String, String> = HashMap()
+        if (query == null) {
+            query = ""
+        }
+        queryMap["format"] = "json"
+        queryMap["search"] = query
+        queryMap["itemType"] = "call"
+        retrofitBucket.queryMap = queryMap
+        return retrofitBucket
+    }
+
+    fun getUrlForFilePreviewWithRemotePath(baseUrl: String, remotePath: String?, px: Int): String {
+        return (
+            baseUrl + "/index.php/core/preview.png?file=" +
+                Uri.encode(remotePath, "UTF-8") +
+                "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1"
+            )
+    }
+
+    fun getUrlForFilePreviewWithFileId(baseUrl: String, fileId: String, px: Int): String {
+        return (
+            baseUrl + "/index.php/core/preview?fileId=" +
+                fileId + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1"
+            )
+    }
+
+    fun getSharingUrl(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/files_sharing/api/v1/shares"
+    }
+
+    fun getRetrofitBucketForContactsSearchFor14(baseUrl: String, searchQuery: String?): RetrofitBucket {
+        val retrofitBucket = getRetrofitBucketForContactsSearch(baseUrl, searchQuery)
+        retrofitBucket.url = "$baseUrl$OCS_API_VERSION/core/autocomplete/get"
+        retrofitBucket.queryMap?.put("itemId", "new")
+        return retrofitBucket
+    }
+
+    @JvmStatic
+    fun getUrlForCapabilities(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/cloud/capabilities"
+    }
+
+    @Throws(NoSupportedApiException::class)
+    fun getCallApiVersion(capabilities: User, versions: IntArray): Int {
+        return getConversationApiVersion(capabilities, versions)
+    }
+
+    @JvmStatic
+    @Throws(NoSupportedApiException::class)
+    @Suppress("ReturnCount")
+    fun getConversationApiVersion(user: User, versions: IntArray): Int {
+        var hasApiV4 = false
+        for (version in versions) {
+            hasApiV4 = hasApiV4 or (version == API_V4)
+        }
+        if (!hasApiV4) {
+            val e = Exception("Api call did not try conversation-v4 api")
+            Log.d(TAG, e.message, e)
+        }
+        for (version in versions) {
+            if (user.hasSpreedFeatureCapability("conversation-v$version")) {
+                return version
+            }
+
+            // Fallback for old API versions
+            if (version == API_V1 || version == API_V2) {
+                if (user.hasSpreedFeatureCapability("conversation-v2")) {
+                    return version
+                }
+                if (version == API_V1 &&
+                    user.hasSpreedFeatureCapability("mention-flag") &&
+                    !user.hasSpreedFeatureCapability("conversation-v4")
+                ) {
+                    return version
+                }
+            }
+        }
+        throw NoSupportedApiException()
+    }
+
+    @JvmStatic
+    @Throws(NoSupportedApiException::class)
+    @Suppress("ReturnCount")
+    fun getSignalingApiVersion(user: User, versions: IntArray): Int {
+        val spreedCapabilities = user.capabilities!!.spreedCapability
+        for (version in versions) {
+            if (spreedCapabilities != null) {
+                if (hasSpreedFeatureCapability(spreedCapabilities, "signaling-v$version")) {
+                    return version
+                }
+                if (version == API_V2 &&
+                    hasSpreedFeatureCapability(spreedCapabilities, "sip-support") &&
+                    !hasSpreedFeatureCapability(spreedCapabilities, "signaling-v3")
+                ) {
+                    return version
+                }
+                if (version == API_V1 &&
+                    !hasSpreedFeatureCapability(spreedCapabilities, "signaling-v3")
+                ) {
+                    // Has no capability, we just assume it is always there when there is no v3 or later
+                    return version
+                }
+            }
+        }
+        throw NoSupportedApiException()
+    }
+
+    @JvmStatic
+    @Throws(NoSupportedApiException::class)
+    fun getChatApiVersion(spreedCapabilities: SpreedCapability, versions: IntArray): Int {
+        for (version in versions) {
+            if (version == API_V1 && hasSpreedFeatureCapability(spreedCapabilities, "chat-v2")) {
+                // Do not question that chat-v2 capability shows the availability of api/v1/ endpoint *see no evil*
+                return version
+            }
+        }
+        throw NoSupportedApiException()
+    }
+
+    private fun getUrlForApi(version: Int, baseUrl: String?): String {
+        return baseUrl + SPREED_API_BASE + version
+    }
+
+    fun getUrlForRooms(version: Int, baseUrl: String?): String {
+        return getUrlForApi(version, baseUrl) + "/room"
+    }
+
+    @JvmStatic
+    fun getUrlForRoom(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRooms(version, baseUrl) + "/" + token
+    }
+
+    fun getUrlForAttendees(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/attendees"
+    }
+
+    fun getUrlForParticipants(version: Int, baseUrl: String?, token: String?): String {
+        if (token.isNullOrEmpty()) {
+            Log.e(TAG, "token was null or empty")
+        }
+        return getUrlForRoom(version, baseUrl, token) + "/participants"
+    }
+
+    fun getUrlForParticipantsActive(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForParticipants(version, baseUrl, token) + "/active"
+    }
+
+    @JvmStatic
+    fun getUrlForParticipantsSelf(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForParticipants(version, baseUrl, token) + "/self"
+    }
+
+    fun getUrlForParticipantsResendInvitations(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForParticipants(version, baseUrl, token) + "/resend-invitations"
+    }
+
+    fun getUrlForRoomFavorite(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/favorite"
+    }
+
+    fun getUrlForRoomModerators(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/moderators"
+    }
+
+    @JvmStatic
+    fun getUrlForRoomNotificationLevel(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/notify"
+    }
+
+    fun getUrlForRoomPublic(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/public"
+    }
+
+    fun getUrlForRoomPassword(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/password"
+    }
+
+    fun getUrlForRoomReadOnlyState(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/read-only"
+    }
+
+    fun getUrlForRoomWebinaryLobby(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/webinar/lobby"
+    }
+
+    @JvmStatic
+    fun getUrlForRoomNotificationCalls(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/notify-calls"
+    }
+
+    fun getUrlForCall(version: Int, baseUrl: String?, token: String): String {
+        return getUrlForApi(version, baseUrl) + "/call/" + token
+    }
+
+    fun getUrlForChat(version: Int, baseUrl: String?, token: String): String {
+        return getUrlForApi(version, baseUrl) + "/chat/" + token
+    }
+
+    @JvmStatic
+    fun getUrlForMentionSuggestions(version: Int, baseUrl: String?, token: String): String {
+        return getUrlForChat(version, baseUrl, token) + "/mentions"
+    }
+
+    fun getUrlForChatMessage(version: Int, baseUrl: String?, token: String, messageId: String): String {
+        return getUrlForChat(version, baseUrl, token) + "/" + messageId
+    }
+
+    fun getUrlForChatSharedItems(version: Int, baseUrl: String?, token: String): String {
+        return getUrlForChat(version, baseUrl, token) + "/share"
+    }
+
+    fun getUrlForChatSharedItemsOverview(version: Int, baseUrl: String?, token: String): String {
+        return getUrlForChatSharedItems(version, baseUrl, token) + "/overview"
+    }
+
+    fun getUrlForSignaling(version: Int, baseUrl: String?): String {
+        return getUrlForApi(version, baseUrl) + "/signaling"
+    }
+
+    @JvmStatic
+    fun getUrlForSignalingBackend(version: Int, baseUrl: String?): String {
+        return getUrlForSignaling(version, baseUrl) + "/backend"
+    }
+
+    @JvmStatic
+    fun getUrlForSignalingSettings(version: Int, baseUrl: String?): String {
+        return getUrlForSignaling(version, baseUrl) + "/settings"
+    }
+
+    fun getUrlForSignaling(version: Int, baseUrl: String?, token: String): String {
+        return getUrlForSignaling(version, baseUrl) + "/" + token
+    }
+
+    fun getUrlForOpenConversations(version: Int, baseUrl: String?): String {
+        return getUrlForApi(version, baseUrl) + "/listed-room"
+    }
+
+    @Suppress("LongParameterList")
+    fun getRetrofitBucketForCreateRoom(
+        version: Int,
+        baseUrl: String?,
+        roomType: String,
+        source: String?,
+        invite: String?,
+        conversationName: String?
+    ): RetrofitBucket {
+        val retrofitBucket = RetrofitBucket()
+        retrofitBucket.url = getUrlForRooms(version, baseUrl)
+        val queryMap: MutableMap<String, String> = HashMap()
+        queryMap["roomType"] = roomType
+        if (invite != null) {
+            queryMap["invite"] = invite
+        }
+        if (source != null) {
+            queryMap["source"] = source
+        }
+        if (conversationName != null) {
+            queryMap["roomName"] = conversationName
+        }
+        retrofitBucket.queryMap = queryMap
+        return retrofitBucket
+    }
+
+    @JvmStatic
+    fun getRetrofitBucketForAddParticipant(
+        version: Int,
+        baseUrl: String?,
+        token: String?,
+        user: String
+    ): RetrofitBucket {
+        val retrofitBucket = RetrofitBucket()
+        retrofitBucket.url = getUrlForParticipants(version, baseUrl, token)
+        val queryMap: MutableMap<String, String> = HashMap()
+        queryMap["newParticipant"] = user
+        retrofitBucket.queryMap = queryMap
+        return retrofitBucket
+    }
+
+    @JvmStatic
+    fun getRetrofitBucketForAddParticipantWithSource(
+        version: Int,
+        baseUrl: String?,
+        token: String?,
+        source: String,
+        id: String
+    ): RetrofitBucket {
+        val retrofitBucket = getRetrofitBucketForAddParticipant(version, baseUrl, token, id)
+        retrofitBucket.queryMap?.put("source", source)
+        return retrofitBucket
+    }
+
+    fun getUrlForUserProfile(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/cloud/user"
+    }
+
+    fun getUrlForUserData(baseUrl: String, userId: String): String {
+        return "$baseUrl$OCS_API_VERSION/cloud/users/$userId"
+    }
+
+    fun getUrlForUserSettings(baseUrl: String): String {
+        // FIXME Introduce API version
+        return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/settings/user"
+    }
+
+    fun getUrlPostfixForStatus(): String {
+        return "/status.php"
+    }
+
+    @JvmStatic
+    fun getUrlForAvatar(baseUrl: String?, name: String?, requestBigSize: Boolean): String {
+        val avatarSize = if (requestBigSize) AVATAR_SIZE_BIG else AVATAR_SIZE_SMALL
+        return baseUrl + "/index.php/avatar/" + Uri.encode(name) + "/" + avatarSize
+    }
+
+    @JvmStatic
+    fun getUrlForGuestAvatar(baseUrl: String?, name: String?, requestBigSize: Boolean): String {
+        val avatarSize = if (requestBigSize) AVATAR_SIZE_BIG else AVATAR_SIZE_SMALL
+        return baseUrl + "/index.php/avatar/guest/" + Uri.encode(name) + "/" + avatarSize
+    }
+
+    fun getUrlForConversationAvatar(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/avatar"
+    }
+
+    fun getUrlForConversationAvatarWithVersion(
+        version: Int,
+        baseUrl: String?,
+        token: String?,
+        isDark: Boolean,
+        avatarVersion: String?
+    ): String {
+        var isDarkString = ""
+        if (isDark) {
+            isDarkString = "/dark"
+        }
+        var avatarVersionString = ""
+        if (avatarVersion != null) {
+            avatarVersionString = "?avatarVersion=$avatarVersion"
+        }
+        return getUrlForRoom(version, baseUrl, token) + "/avatar" + isDarkString + avatarVersionString
+    }
+
+    @JvmStatic
+    fun getCredentials(username: String?, token: String?): String? {
+        return if (TextUtils.isEmpty(username) && TextUtils.isEmpty(token)) {
+            null
+        } else {
+            basic(username!!, token!!, StandardCharsets.UTF_8)
+        }
+    }
+
+    @JvmStatic
+    fun getUrlNextcloudPush(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/notifications/api/v2/push"
+    }
+
+    @JvmStatic
+    fun getUrlPushProxy(): String {
+        return sharedApplication!!.applicationContext.resources.getString(R.string.nc_push_server_url) + "/devices"
+    }
+
+    // see https://github.com/nextcloud/notifications/blob/master/docs/ocs-endpoint-v2.md
+    fun getUrlForNcNotificationWithId(baseUrl: String, notificationId: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/notifications/api/v2/notifications/$notificationId"
+    }
+
+    fun getUrlForSearchByNumber(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/cloud/users/search/by-phone"
+    }
+
+    fun getUrlForFileUpload(baseUrl: String, user: String, remotePath: String): String {
+        return "$baseUrl/remote.php/dav/files/$user$remotePath"
+    }
+
+    fun getUrlForChunkedUpload(baseUrl: String, user: String): String {
+        return "$baseUrl/remote.php/dav/uploads/$user"
+    }
+
+    fun getUrlForFileDownload(baseUrl: String, user: String, remotePath: String): String {
+        return "$baseUrl/remote.php/dav/files/$user/$remotePath"
+    }
+
+    fun getUrlForTempAvatar(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/spreed/temp-user-avatar"
+    }
+
+    fun getUrlForUserFields(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/cloud/user/fields"
+    }
+
+    fun getUrlToSendLocation(version: Int, baseUrl: String?, roomToken: String): String {
+        return getUrlForChat(version, baseUrl, roomToken) + "/share"
+    }
+
+    fun getUrlForHoverCard(baseUrl: String, userId: String): String {
+        return baseUrl + OCS_API_VERSION +
+            "/hovercard/v1/" + userId
+    }
+
+    fun getUrlForChatReadMarker(version: Int, baseUrl: String?, roomToken: String): String {
+        return getUrlForChat(version, baseUrl, roomToken) + "/read"
+    }
+
+    /*
+     * OCS Status API
+     */
+    @JvmStatic
+    fun getUrlForStatus(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/user_status"
+    }
+
+    fun getUrlForSetStatusType(baseUrl: String): String {
+        return getUrlForStatus(baseUrl) + "/status"
+    }
+
+    fun getUrlForPredefinedStatuses(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/predefined_statuses"
+    }
+
+    fun getUrlForStatusMessage(baseUrl: String): String {
+        return getUrlForStatus(baseUrl) + "/message"
+    }
+
+    fun getUrlForSetCustomStatus(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/user_status/message/custom"
+    }
+
+    fun getUrlForSetPredefinedStatus(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/user_status/message/predefined"
+    }
+
+    fun getUrlForUserStatuses(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/statuses"
+    }
+
+    fun getUrlForMessageReaction(baseUrl: String, roomToken: String, messageId: String): String {
+        return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/reaction/$roomToken/$messageId"
+    }
+
+    fun getUrlForUnifiedSearch(baseUrl: String, providerId: String): String {
+        return "$baseUrl$OCS_API_VERSION/search/providers/$providerId/search"
+    }
+
+    fun getUrlForPoll(baseUrl: String, roomToken: String, pollId: String): String {
+        return getUrlForPoll(baseUrl, roomToken) + "/" + pollId
+    }
+
+    fun getUrlForPoll(baseUrl: String, roomToken: String): String {
+        return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/poll/$roomToken"
+    }
+
+    @JvmStatic
+    fun getUrlForMessageExpiration(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/message-expiration"
+    }
+
+    fun getUrlForOpenGraph(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/references/resolve"
+    }
+
+    fun getUrlForRecording(version: Int, baseUrl: String?, token: String): String {
+        return getUrlForApi(version, baseUrl) + "/recording/" + token
+    }
+
+    fun getUrlForRequestAssistance(version: Int, baseUrl: String?, token: String): String {
+        return getUrlForApi(version, baseUrl) + "/breakout-rooms/" + token + "/request-assistance"
+    }
+
+    fun getUrlForConversationDescription(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/description"
+    }
+
+    fun getUrlForTranslation(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/translation/translate"
+    }
+
+    fun getUrlForLanguages(baseUrl: String): String {
+        return "$baseUrl$OCS_API_VERSION/translation/languages"
+    }
+
+    fun getUrlForReminder(user: User, roomToken: String, messageId: String, version: Int): String {
+        val url = getUrlForChatMessage(version, user.baseUrl!!, roomToken, messageId)
+        return "$url/reminder"
+    }
+
+    fun getUrlForRecordingConsent(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRoom(version, baseUrl, token) + "/recording-consent"
+    }
+
+    fun getUrlForInvitation(baseUrl: String): String {
+        return baseUrl + OCS_API_VERSION + SPREED_API_VERSION + "/federation/invitation"
+    }
+
+    fun getUrlForInvitationAccept(baseUrl: String, id: Int): String {
+        return getUrlForInvitation(baseUrl) + "/" + id
+    }
+
+    fun getUrlForInvitationReject(baseUrl: String, id: Int): String {
+        return getUrlForInvitation(baseUrl) + "/" + id
+    }
+
+    @JvmStatic
+    fun getUrlForRoomCapabilities(version: Int, baseUrl: String?, token: String?): String {
+        return getUrlForRooms(version, baseUrl) + "/" + token + "/capabilities"
+    }
+}

+ 275 - 0
app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt

@@ -0,0 +1,275 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Mario Danic
+ * @author Marcel Hibbe
+ * Copyright (C) 2023-2024 Marcel Hibbe <dev@mhibbe.de>
+ * Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * 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.utils
+
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
+
+enum class SpreedFeatures(val value: String) {
+    RECORDING_V1("recording-v1"),
+    REACTIONS("reactions"),
+    RAISE_HAND("raise-hand"),
+    DIRECT_MENTION_FLAG("direct-mention-flag"),
+    CONVERSATION_CALL_FLAGS("conversation-call-flags"),
+    SILENT_SEND("silent-send"),
+    MENTION_FLAG("mention-flag"),
+    DELETE_MESSAGES("delete-messages"),
+    READ_ONLY_ROOMS("read-only-rooms"),
+    RICH_OBJECT_LIST_MEDIA("rich-object-list-media"),
+    SILENT_CALL("silent-call"),
+    MESSAGE_EXPIRATION("message-expiration"),
+    WEBINARY_LOBBY("webinary-lobby"),
+    VOICE_MESSAGE_SHARING("voice-message-sharing"),
+    INVITE_GROUPS_AND_MAILS("invite-groups-and-mails"),
+    CIRCLES_SUPPORT("circles-support"),
+    LAST_ROOM_ACTIVITY("last-room-activity"),
+    NOTIFICATION_LEVELS("notification-levels"),
+    CLEAR_HISTORY("clear-history"),
+    AVATAR("avatar"),
+    LISTABLE_ROOMS("listable-rooms"),
+    LOCKED_ONE_TO_ONE_ROOMS("locked-one-to-one-rooms"),
+    TEMP_USER_AVATAR_API("temp-user-avatar-api"),
+    PHONEBOOK_SEARCH("phonebook-search"),
+    GEO_LOCATION_SHARING("geo-location-sharing"),
+    TALK_POLLS("talk-polls")
+}
+
+@Suppress("TooManyFunctions")
+object CapabilitiesUtil {
+
+    //region Version checks
+    fun isServerEOL(serverVersion: Int): Boolean {
+        return (serverVersion < SERVER_VERSION_MIN_SUPPORTED)
+    }
+
+    fun isServerAlmostEOL(serverVersion: Int): Boolean {
+        return (serverVersion < SERVER_VERSION_SUPPORT_WARNING)
+    }
+
+    // endregion
+
+    //region CoreCapabilities
+
+    @JvmStatic
+    fun isLinkPreviewAvailable(user: User): Boolean {
+        return user.capabilities?.coreCapability?.referenceApi != null &&
+            user.capabilities?.coreCapability?.referenceApi == "true"
+    }
+
+    // endregion
+
+    //region SpreedCapabilities
+
+    @JvmStatic
+    fun hasSpreedFeatureCapability(spreedCapabilities: SpreedCapability, spreedFeatures: SpreedFeatures): Boolean {
+        if (spreedCapabilities.features != null) {
+            return spreedCapabilities.features!!.contains(spreedFeatures.value)
+        }
+        return false
+    }
+
+    @JvmStatic
+    @Deprecated("Add your capability to Capability enums and use hasSpreedFeatureCapability with enum.")
+    fun hasSpreedFeatureCapability(spreedCapabilities: SpreedCapability, capabilityName: String): Boolean {
+        if (spreedCapabilities.features != null) {
+            return spreedCapabilities.features!!.contains(capabilityName)
+        }
+        return false
+    }
+
+    fun getMessageMaxLength(spreedCapabilities: SpreedCapability): Int {
+        if (spreedCapabilities.config?.containsKey("chat") == true) {
+            val chatConfigHashMap = spreedCapabilities.config!!["chat"]
+            if (chatConfigHashMap?.containsKey("max-length") == true) {
+                val chatSize = (chatConfigHashMap["max-length"]!!.toString()).toInt()
+                return if (chatSize > 0) {
+                    chatSize
+                } else {
+                    DEFAULT_CHAT_SIZE
+                }
+            }
+        }
+
+        return DEFAULT_CHAT_SIZE
+    }
+
+    fun isReadStatusAvailable(spreedCapabilities: SpreedCapability): Boolean {
+        if (spreedCapabilities.config?.containsKey("chat") == true) {
+            val map: Map<String, Any>? = spreedCapabilities.config!!["chat"]
+            return map != null && map.containsKey("read-privacy")
+        }
+        return false
+    }
+
+    @JvmStatic
+    fun isCallRecordingAvailable(spreedCapabilities: SpreedCapability): Boolean {
+        if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.RECORDING_V1) &&
+            spreedCapabilities.config?.containsKey("call") == true
+        ) {
+            val map: Map<String, Any>? = spreedCapabilities.config!!["call"]
+            if (map != null && map.containsKey("recording")) {
+                return (map["recording"].toString()).toBoolean()
+            }
+        }
+        return false
+    }
+
+    @JvmStatic
+    fun getAttachmentFolder(spreedCapabilities: SpreedCapability): String {
+        if (spreedCapabilities.config?.containsKey("attachments") == true) {
+            val map = spreedCapabilities.config!!["attachments"]
+            if (map?.containsKey("folder") == true) {
+                return map["folder"].toString()
+            }
+        }
+        return "/Talk"
+    }
+
+    fun isConversationDescriptionEndpointAvailable(spreedCapabilities: SpreedCapability): Boolean {
+        return hasSpreedFeatureCapability(spreedCapabilities, "room-description")
+    }
+
+    fun isUnifiedSearchAvailable(spreedCapabilities: SpreedCapability): Boolean {
+        return hasSpreedFeatureCapability(spreedCapabilities, "unified-search")
+    }
+
+    fun isAbleToCall(spreedCapabilities: SpreedCapability): Boolean {
+        return if (
+            spreedCapabilities.config?.containsKey("call") == true &&
+            spreedCapabilities.config!!["call"] != null &&
+            spreedCapabilities.config!!["call"]!!.containsKey("enabled")
+        ) {
+            java.lang.Boolean.parseBoolean(spreedCapabilities.config!!["call"]!!["enabled"].toString())
+        } else {
+            // older nextcloud versions without the capability can't disable the calls
+            true
+        }
+    }
+
+    fun isCallReactionsSupported(user: User?): Boolean {
+        if (user?.capabilities != null) {
+            val capabilities = user.capabilities
+            return capabilities?.spreedCapability?.config?.containsKey("call") == true &&
+                capabilities.spreedCapability!!.config!!["call"] != null &&
+                capabilities.spreedCapability!!.config!!["call"]!!.containsKey("supported-reactions")
+        }
+        return false
+    }
+
+    fun isTranslationsSupported(spreedCapabilities: SpreedCapability): Boolean {
+        return spreedCapabilities.config?.containsKey("chat") == true &&
+            spreedCapabilities.config!!["chat"] != null &&
+            spreedCapabilities.config!!["chat"]!!.containsKey("has-translation-providers") &&
+            spreedCapabilities.config!!["chat"]!!["has-translation-providers"] == true
+    }
+
+    fun getRecordingConsentType(spreedCapabilities: SpreedCapability): Int {
+        if (
+            spreedCapabilities.config?.containsKey("call") == true &&
+            spreedCapabilities.config!!["call"] != null &&
+            spreedCapabilities.config!!["call"]!!.containsKey("recording-consent")
+        ) {
+            return when (
+                spreedCapabilities.config!!["call"]!!["recording-consent"].toString()
+                    .toInt()
+            ) {
+                1 -> RECORDING_CONSENT_REQUIRED
+                2 -> RECORDING_CONSENT_DEPEND_ON_CONVERSATION
+                else -> RECORDING_CONSENT_NOT_REQUIRED
+            }
+        }
+        return RECORDING_CONSENT_NOT_REQUIRED
+    }
+
+    // endregion
+
+    //region SpreedCapabilities that can't be used with federation as the settings for them are global
+
+    fun isReadStatusPrivate(user: User): Boolean {
+        if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
+            val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
+            if (map?.containsKey("read-privacy") == true) {
+                return (map["read-privacy"]!!.toString()).toInt() == 1
+            }
+        }
+        return false
+    }
+
+    fun isTypingStatusAvailable(user: User): Boolean {
+        if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
+            val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
+            return map != null && map.containsKey("typing-privacy")
+        }
+        return false
+    }
+
+    fun isTypingStatusPrivate(user: User): Boolean {
+        if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
+            val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
+            if (map?.containsKey("typing-privacy") == true) {
+                return (map["typing-privacy"]!!.toString()).toInt() == 1
+            }
+        }
+        return false
+    }
+
+    // endregion
+
+    //region ThemingCapabilities
+
+    fun getServerName(user: User?): String? {
+        if (user?.capabilities?.themingCapability != null) {
+            return user.capabilities!!.themingCapability!!.name
+        }
+        return ""
+    }
+
+    // endregion
+
+    //region ProvisioningCapabilities
+
+    fun canEditScopes(user: User): Boolean {
+        return user.capabilities?.provisioningCapability?.accountPropertyScopesVersion != null &&
+            user.capabilities!!.provisioningCapability!!.accountPropertyScopesVersion!! > 1
+    }
+
+    // endregion
+
+    //region UserStatusCapabilities
+
+    @JvmStatic
+    fun isUserStatusAvailable(user: User): Boolean {
+        return user.capabilities?.userStatusCapability?.enabled == true &&
+            user.capabilities?.userStatusCapability?.supportsEmoji == true
+    }
+
+    // endregion
+
+    const val DEFAULT_CHAT_SIZE = 1000
+    const val RECORDING_CONSENT_NOT_REQUIRED = 0
+    const val RECORDING_CONSENT_REQUIRED = 1
+    const val RECORDING_CONSENT_DEPEND_ON_CONVERSATION = 2
+    private const val SERVER_VERSION_MIN_SUPPORTED = 14
+    private const val SERVER_VERSION_SUPPORT_WARNING = 18
+}

+ 11 - 12
app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt

@@ -1,10 +1,9 @@
 package com.nextcloud.talk.utils
 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.ConversationModel
 import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.domain.ConversationType
 import com.nextcloud.talk.models.domain.ParticipantType
 import com.nextcloud.talk.models.domain.ParticipantType
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 
 
 /*
 /*
  * Nextcloud Talk application
  * Nextcloud Talk application
@@ -45,28 +44,28 @@ object ConversationUtils {
             ParticipantType.MODERATOR == conversation.participantType
             ParticipantType.MODERATOR == conversation.participantType
     }
     }
 
 
-    private fun isLockedOneToOne(conversation: ConversationModel, conversationUser: User): Boolean {
+    fun isLockedOneToOne(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
         return conversation.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
         return conversation.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
-            CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms")
+            CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, "locked-one-to-one-rooms")
     }
     }
 
 
-    fun canModerate(conversation: ConversationModel, conversationUser: User): Boolean {
+    fun canModerate(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
         return isParticipantOwnerOrModerator(conversation) &&
         return isParticipantOwnerOrModerator(conversation) &&
-            !isLockedOneToOne(conversation, conversationUser) &&
+            !isLockedOneToOne(conversation, spreedCapabilities) &&
             conversation.type != ConversationType.FORMER_ONE_TO_ONE &&
             conversation.type != ConversationType.FORMER_ONE_TO_ONE &&
             !isNoteToSelfConversation(conversation)
             !isNoteToSelfConversation(conversation)
     }
     }
 
 
-    fun isLobbyViewApplicable(conversation: ConversationModel, conversationUser: User): Boolean {
-        return !canModerate(conversation, conversationUser) &&
+    fun isLobbyViewApplicable(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
+        return !canModerate(conversation, spreedCapabilities) &&
             (
             (
                 conversation.type == ConversationType.ROOM_GROUP_CALL ||
                 conversation.type == ConversationType.ROOM_GROUP_CALL ||
                     conversation.type == ConversationType.ROOM_PUBLIC_CALL
                     conversation.type == ConversationType.ROOM_PUBLIC_CALL
                 )
                 )
     }
     }
 
 
-    fun isNameEditable(conversation: ConversationModel, conversationUser: User): Boolean {
-        return canModerate(conversation, conversationUser) &&
+    fun isNameEditable(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
+        return canModerate(conversation, spreedCapabilities) &&
             ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation.type
             ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation.type
     }
     }
 
 
@@ -79,12 +78,12 @@ object ConversationUtils {
         }
         }
     }
     }
 
 
-    fun canDelete(conversation: ConversationModel, conversationUser: User): Boolean {
+    fun canDelete(conversation: ConversationModel, spreedCapability: SpreedCapability): Boolean {
         return if (conversation.canDeleteConversation != null) {
         return if (conversation.canDeleteConversation != null) {
             // Available since APIv2
             // Available since APIv2
             conversation.canDeleteConversation!!
             conversation.canDeleteConversation!!
         } else {
         } else {
-            canModerate(conversation, conversationUser)
+            canModerate(conversation, spreedCapability)
             // Fallback for APIv1
             // Fallback for APIv1
         }
         }
     }
     }

+ 1 - 2
app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt

@@ -61,7 +61,6 @@ import com.nextcloud.talk.utils.MimetypeUtils.isGif
 import com.nextcloud.talk.utils.MimetypeUtils.isMarkdown
 import com.nextcloud.talk.utils.MimetypeUtils.isMarkdown
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACCOUNT
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACCOUNT
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
 import java.io.File
 import java.io.File
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.ExecutionException
 
 
@@ -308,7 +307,7 @@ class FileViewerUtils(private val context: Context, private val user: User) {
             .putString(DownloadFileToCacheWorker.KEY_USER_ID, user.userId)
             .putString(DownloadFileToCacheWorker.KEY_USER_ID, user.userId)
             .putString(
             .putString(
                 DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER,
                 DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER,
-                CapabilitiesUtilNew.getAttachmentFolder(user)
+                CapabilitiesUtil.getAttachmentFolder(user.capabilities!!.spreedCapability!!)
             )
             )
             .putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileInfo.fileName)
             .putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileInfo.fileName)
             .putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)
             .putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)

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

@@ -22,22 +22,21 @@
 
 
 package com.nextcloud.talk.utils
 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.ConversationModel
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.Conversation
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
 
 
 /**
 /**
  * see https://nextcloud-talk.readthedocs.io/en/latest/constants/#attendee-permissions
  * see https://nextcloud-talk.readthedocs.io/en/latest/constants/#attendee-permissions
  */
  */
 class ParticipantPermissions(
 class ParticipantPermissions(
-    private val user: User,
+    private val spreedCapabilities: SpreedCapability,
     private val conversation: ConversationModel
     private val conversation: ConversationModel
 ) {
 ) {
 
 
     @Deprecated("Use ChatRepository.ConversationModel")
     @Deprecated("Use ChatRepository.ConversationModel")
-    constructor(user: User, conversation: Conversation) : this(
-        user,
+    constructor(spreedCapabilities: SpreedCapability, conversation: Conversation) : this(
+        spreedCapabilities,
         ConversationModel.mapToConversationModel(conversation)
         ConversationModel.mapToConversationModel(conversation)
     )
     )
 
 
@@ -52,8 +51,8 @@ class ParticipantPermissions(
     private val hasChatPermission = (conversation.permissions and CHAT) == CHAT
     private val hasChatPermission = (conversation.permissions and CHAT) == CHAT
 
 
     private fun hasConversationPermissions(): Boolean {
     private fun hasConversationPermissions(): Boolean {
-        return CapabilitiesUtilNew.hasSpreedFeatureCapability(
-            user,
+        return CapabilitiesUtil.hasSpreedFeatureCapability(
+            spreedCapabilities,
             "conversation-permissions"
             "conversation-permissions"
         )
         )
     }
     }
@@ -91,7 +90,7 @@ class ParticipantPermissions(
     }
     }
 
 
     fun hasChatPermission(): Boolean {
     fun hasChatPermission(): Boolean {
-        if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "chat-permission")) {
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, "chat-permission")) {
             return hasChatPermission
             return hasChatPermission
         }
         }
         // if capability is not available then the spreed version doesn't support to restrict this
         // if capability is not available then the spreed version doesn't support to restrict this

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

@@ -238,7 +238,7 @@ class PushUtils {
         val credentials = ApiUtils.getCredentials(user.username, user.token)
         val credentials = ApiUtils.getCredentials(user.username, user.token)
         ncApi.registerDeviceForNotificationsWithNextcloud(
         ncApi.registerDeviceForNotificationsWithNextcloud(
             credentials,
             credentials,
-            ApiUtils.getUrlNextcloudPush(user.baseUrl),
+            ApiUtils.getUrlNextcloudPush(user.baseUrl!!),
             nextcloudRegisterPushMap
             nextcloudRegisterPushMap
         )
         )
             .subscribe(object : Observer<PushRegistrationOverall> {
             .subscribe(object : Observer<PushRegistrationOverall> {

+ 2 - 2
app/src/main/java/com/nextcloud/talk/utils/RemoteFileUtils.kt

@@ -53,8 +53,8 @@ object RemoteFileUtils {
         return ncApi.checkIfFileExists(
         return ncApi.checkIfFileExists(
             ApiUtils.getCredentials(currentUser.username, currentUser.token),
             ApiUtils.getCredentials(currentUser.username, currentUser.token),
             ApiUtils.getUrlForFileUpload(
             ApiUtils.getUrlForFileUpload(
-                currentUser.baseUrl,
-                currentUser.userId,
+                currentUser.baseUrl!!,
+                currentUser.userId!!,
                 remotePath
                 remotePath
             )
             )
         )
         )

+ 2 - 2
app/src/main/java/com/nextcloud/talk/utils/ShareUtils.kt

@@ -22,10 +22,10 @@ package com.nextcloud.talk.utils
 import android.content.Context
 import android.content.Context
 import com.nextcloud.talk.R
 import com.nextcloud.talk.R
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.data.user.model.User
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.domain.ConversationModel
 
 
 object ShareUtils {
 object ShareUtils {
-    fun getStringForIntent(context: Context, user: User, conversation: Conversation?): String {
+    fun getStringForIntent(context: Context, user: User, conversation: ConversationModel?): String {
         return String.format(
         return String.format(
             context.resources.getString(R.string.nc_share_text),
             context.resources.getString(R.string.nc_share_text),
             user.baseUrl,
             user.baseUrl,

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

@@ -89,4 +89,5 @@ object BundleKeys {
     const val KEY_REAUTHORIZE_ACCOUNT = "KEY_REAUTHORIZE_ACCOUNT"
     const val KEY_REAUTHORIZE_ACCOUNT = "KEY_REAUTHORIZE_ACCOUNT"
     const val KEY_PASSWORD = "KEY_PASSWORD"
     const val KEY_PASSWORD = "KEY_PASSWORD"
     const val KEY_REMOTE_TALK_SHARE = "KEY_REMOTE_TALK_SHARE"
     const val KEY_REMOTE_TALK_SHARE = "KEY_REMOTE_TALK_SHARE"
+    const val KEY_CHAT_API_VERSION = "KEY_CHAT_API_VERSION"
 }
 }

+ 0 - 267
app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt

@@ -1,267 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Andy Scherzinger
- * @author Mario Danic
- * Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
- *
- * 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.utils.database.user
-
-import com.nextcloud.talk.data.user.model.User
-import com.nextcloud.talk.models.json.capabilities.Capabilities
-
-@Suppress("TooManyFunctions")
-object CapabilitiesUtilNew {
-    fun hasNotificationsCapability(user: User, capabilityName: String): Boolean {
-        return user.capabilities?.spreedCapability?.features?.contains(capabilityName) == true
-    }
-
-    fun hasExternalCapability(user: User, capabilityName: String?): Boolean {
-        if (user.capabilities?.externalCapability?.containsKey("v1") == true) {
-            return user.capabilities!!.externalCapability!!["v1"]?.contains(capabilityName!!) == true
-        }
-        return false
-    }
-
-    @JvmStatic
-    fun isServerEOL(capabilities: Capabilities?): Boolean {
-        // Capability is available since Talk 4 => Nextcloud 14 => Autmn 2018
-        return !hasSpreedFeatureCapability(capabilities, "no-ping")
-    }
-
-    fun isServerAlmostEOL(user: User): Boolean {
-        // Capability is available since Talk 8 => Nextcloud 18 => January 2020
-        return !hasSpreedFeatureCapability(user, "chat-replies")
-    }
-
-    fun canSetChatReadMarker(user: User): Boolean {
-        return hasSpreedFeatureCapability(user, "chat-read-marker")
-    }
-
-    fun canMarkRoomAsUnread(user: User): Boolean {
-        return hasSpreedFeatureCapability(user, "chat-unread")
-    }
-
-    @JvmStatic
-    fun hasSpreedFeatureCapability(user: User?, capabilityName: String): Boolean {
-        return hasSpreedFeatureCapability(user?.capabilities, capabilityName)
-    }
-
-    @JvmStatic
-    fun hasSpreedFeatureCapability(capabilities: Capabilities?, capabilityName: String): Boolean {
-        if (capabilities?.spreedCapability?.features != null) {
-            return capabilities.spreedCapability!!.features!!.contains(capabilityName)
-        }
-        return false
-    }
-
-    fun getMessageMaxLength(user: User?): Int {
-        if (user?.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
-            val chatConfigHashMap = user.capabilities!!.spreedCapability!!.config!!["chat"]
-            if (chatConfigHashMap?.containsKey("max-length") == true) {
-                val chatSize = (chatConfigHashMap["max-length"]!!.toString()).toInt()
-                return if (chatSize > 0) {
-                    chatSize
-                } else {
-                    DEFAULT_CHAT_SIZE
-                }
-            }
-        }
-
-        return DEFAULT_CHAT_SIZE
-    }
-
-    fun isPhoneBookIntegrationAvailable(user: User): Boolean {
-        return user.capabilities?.spreedCapability?.features?.contains("phonebook-search") == true
-    }
-
-    fun isReadStatusAvailable(user: User): Boolean {
-        if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
-            val map: Map<String, Any>? = user.capabilities!!.spreedCapability!!.config!!["chat"]
-            return map != null && map.containsKey("read-privacy")
-        }
-        return false
-    }
-
-    fun isReadStatusPrivate(user: User): Boolean {
-        if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
-            val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
-            if (map?.containsKey("read-privacy") == true) {
-                return (map["read-privacy"]!!.toString()).toInt() == 1
-            }
-        }
-        return false
-    }
-
-    fun isTypingStatusAvailable(user: User): Boolean {
-        if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
-            val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
-            return map != null && map.containsKey("typing-privacy")
-        }
-        return false
-    }
-
-    fun isTypingStatusPrivate(user: User): Boolean {
-        if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
-            val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
-            if (map?.containsKey("typing-privacy") == true) {
-                return (map["typing-privacy"]!!.toString()).toInt() == 1
-            }
-        }
-        return false
-    }
-
-    @JvmStatic
-    fun isCallRecordingAvailable(user: User): Boolean {
-        if (hasSpreedFeatureCapability(user, "recording-v1") &&
-            user.capabilities?.spreedCapability?.config?.containsKey("call") == true
-        ) {
-            val map: Map<String, Any>? = user.capabilities!!.spreedCapability!!.config!!["call"]
-            if (map != null && map.containsKey("recording")) {
-                return (map["recording"].toString()).toBoolean()
-            }
-        }
-        return false
-    }
-
-    @JvmStatic
-    fun isUserStatusAvailable(user: User): Boolean {
-        return user.capabilities?.userStatusCapability?.enabled == true &&
-            user.capabilities?.userStatusCapability?.supportsEmoji == true
-    }
-
-    @JvmStatic
-    fun getAttachmentFolder(user: User): String {
-        if (user.capabilities?.spreedCapability?.config?.containsKey("attachments") == true) {
-            val map = user.capabilities!!.spreedCapability!!.config!!["attachments"]
-            if (map?.containsKey("folder") == true) {
-                return map["folder"].toString()
-            }
-        }
-        return "/Talk"
-    }
-
-    fun getServerName(user: User?): String? {
-        if (user?.capabilities?.themingCapability != null) {
-            return user.capabilities!!.themingCapability!!.name
-        }
-        return ""
-    }
-
-    // TODO later avatar can also be checked via user fields, for now it is in Talk capability
-    fun isAvatarEndpointAvailable(user: User): Boolean {
-        return user.capabilities?.spreedCapability?.features?.contains("temp-user-avatar-api") == true
-    }
-
-    fun isConversationAvatarEndpointAvailable(user: User): Boolean {
-        return user.capabilities?.spreedCapability?.features?.contains("avatar") == true
-    }
-
-    fun isConversationDescriptionEndpointAvailable(user: User): Boolean {
-        return user.capabilities?.spreedCapability?.features?.contains("room-description") == true
-    }
-
-    fun canEditScopes(user: User): Boolean {
-        return user.capabilities?.provisioningCapability?.accountPropertyScopesVersion != null &&
-            user.capabilities!!.provisioningCapability!!.accountPropertyScopesVersion!! > 1
-    }
-
-    fun isAbleToCall(user: User?): Boolean {
-        if (user?.capabilities != null) {
-            val capabilities = user.capabilities
-            return if (
-                capabilities?.spreedCapability?.config?.containsKey("call") == true &&
-                capabilities.spreedCapability!!.config!!["call"] != null &&
-                capabilities.spreedCapability!!.config!!["call"]!!.containsKey("enabled")
-            ) {
-                java.lang.Boolean.parseBoolean(capabilities.spreedCapability!!.config!!["call"]!!["enabled"].toString())
-            } else {
-                // older nextcloud versions without the capability can't disable the calls
-                true
-            }
-        }
-        return false
-    }
-
-    fun isCallReactionsSupported(user: User?): Boolean {
-        if (user?.capabilities != null) {
-            val capabilities = user.capabilities
-            return capabilities?.spreedCapability?.config?.containsKey("call") == true &&
-                capabilities.spreedCapability!!.config!!["call"] != null &&
-                capabilities.spreedCapability!!.config!!["call"]!!.containsKey("supported-reactions")
-        }
-        return false
-    }
-
-    @JvmStatic
-    fun isUnifiedSearchAvailable(user: User): Boolean {
-        return hasSpreedFeatureCapability(user, "unified-search")
-    }
-
-    @JvmStatic
-    fun isLinkPreviewAvailable(user: User): Boolean {
-        return user.capabilities?.coreCapability?.referenceApi != null &&
-            user.capabilities?.coreCapability?.referenceApi == "true"
-    }
-
-    fun isTranslationsSupported(user: User?): Boolean {
-        if (user?.capabilities != null) {
-            val capabilities = user.capabilities
-            return capabilities?.spreedCapability?.config?.containsKey("chat") == true &&
-                capabilities.spreedCapability!!.config!!["chat"] != null &&
-                capabilities.spreedCapability!!.config!!["chat"]!!.containsKey("has-translation-providers") &&
-                capabilities.spreedCapability!!.config!!["chat"]!!["has-translation-providers"] == true
-        }
-
-        return false
-    }
-
-    fun isRemindSupported(user: User?): Boolean {
-        if (user?.capabilities != null) {
-            val capabilities = user.capabilities
-            return capabilities?.spreedCapability?.features?.contains("remind-me-later") == true
-        }
-
-        return false
-    }
-
-    fun getRecordingConsentType(user: User?): Int {
-        if (user?.capabilities != null) {
-            val capabilities = user.capabilities
-            if (
-                capabilities?.spreedCapability?.config?.containsKey("call") == true &&
-                capabilities.spreedCapability!!.config!!["call"] != null &&
-                capabilities.spreedCapability!!.config!!["call"]!!.containsKey("recording-consent")
-            ) {
-                return when (
-                    capabilities.spreedCapability!!.config!!["call"]!!["recording-consent"].toString()
-                        .toInt()
-                ) {
-                    1 -> RECORDING_CONSENT_REQUIRED
-                    2 -> RECORDING_CONSENT_DEPEND_ON_CONVERSATION
-                    else -> RECORDING_CONSENT_NOT_REQUIRED
-                }
-            }
-        }
-        return RECORDING_CONSENT_NOT_REQUIRED
-    }
-
-    const val DEFAULT_CHAT_SIZE = 1000
-    const val RECORDING_CONSENT_NOT_REQUIRED = 0
-    const val RECORDING_CONSENT_REQUIRED = 1
-    const val RECORDING_CONSENT_DEPEND_ON_CONVERSATION = 2
-}

+ 4 - 3
app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.java

@@ -35,7 +35,7 @@ import com.nextcloud.talk.data.user.model.User;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.UserIdUtils;
 import com.nextcloud.talk.utils.UserIdUtils;
-import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
+import com.nextcloud.talk.utils.CapabilitiesUtil;
 
 
 import javax.inject.Inject;
 import javax.inject.Inject;
 
 
@@ -158,7 +158,8 @@ public class DatabaseStorageModule {
                 });
                 });
 
 
         } else if ("conversation_info_message_notifications_dropdown".equals(key)) {
         } else if ("conversation_info_message_notifications_dropdown".equals(key)) {
-            if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "notification-levels")) {
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser.getCapabilities().getSpreedCapability(), "notification" +
+                "-levels")) {
                 if (TextUtils.isEmpty(messageNotificationLevel) || !messageNotificationLevel.equals(value)) {
                 if (TextUtils.isEmpty(messageNotificationLevel) || !messageNotificationLevel.equals(value)) {
                     int intValue;
                     int intValue;
                     switch (value) {
                     switch (value) {
@@ -175,7 +176,7 @@ public class DatabaseStorageModule {
                             intValue = 0;
                             intValue = 0;
                     }
                     }
 
 
-                    int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
+                    int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.API_V4, 1});
 
 
                     ncApi.setNotificationLevel(ApiUtils.getCredentials(conversationUser.getUsername(),
                     ncApi.setNotificationLevel(ApiUtils.getCredentials(conversationUser.getUsername(),
                                                                        conversationUser.getToken()),
                                                                        conversationUser.getToken()),

+ 3 - 4
app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt

@@ -25,7 +25,6 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModel
 import com.nextcloud.talk.activities.CallActivity.Companion.TAG
 import com.nextcloud.talk.activities.CallActivity.Companion.TAG
-import com.nextcloud.talk.location.GeocodingActivity
 import fr.dudie.nominatim.client.TalkJsonNominatimClient
 import fr.dudie.nominatim.client.TalkJsonNominatimClient
 import fr.dudie.nominatim.model.Address
 import fr.dudie.nominatim.model.Address
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineScope
@@ -70,9 +69,9 @@ class GeoCodingViewModel : ViewModel() {
                 try {
                 try {
                     val results = nominatimClient.search(query) as ArrayList<Address>
                     val results = nominatimClient.search(query) as ArrayList<Address>
                     for (address in results) {
                     for (address in results) {
-                        Log.d(GeocodingActivity.TAG, address.displayName)
-                        Log.d(GeocodingActivity.TAG, address.latitude.toString())
-                        Log.d(GeocodingActivity.TAG, address.longitude.toString())
+                        Log.d(TAG, address.displayName)
+                        Log.d(TAG, address.latitude.toString())
+                        Log.d(TAG, address.longitude.toString())
                     }
                     }
                     geocodingResults = results
                     geocodingResults = results
                     geocodingResultsLiveData.postValue(results)
                     geocodingResultsLiveData.postValue(results)

+ 1 - 1
app/src/main/java/com/nextcloud/talk/webrtc/WebSocketConnectionHelper.java

@@ -113,7 +113,7 @@ public class WebSocketConnectionHelper {
     }
     }
 
 
     HelloOverallWebSocketMessage getAssembledHelloModel(User user, String ticket) {
     HelloOverallWebSocketMessage getAssembledHelloModel(User user, String ticket) {
-        int apiVersion = ApiUtils.getSignalingApiVersion(user, new int[]{ApiUtils.APIv3, 2, 1});
+        int apiVersion = ApiUtils.getSignalingApiVersion(user, new int[]{ApiUtils.API_V3, 2, 1});
 
 
         HelloOverallWebSocketMessage helloOverallWebSocketMessage = new HelloOverallWebSocketMessage();
         HelloOverallWebSocketMessage helloOverallWebSocketMessage = new HelloOverallWebSocketMessage();
         helloOverallWebSocketMessage.setType("hello");
         helloOverallWebSocketMessage.setType("hello");

+ 3 - 3
app/src/test/java/com/nextcloud/talk/utils/ParticipantPermissionsTest.kt

@@ -22,7 +22,7 @@
 
 
 package com.nextcloud.talk.utils
 package com.nextcloud.talk.utils
 
 
-import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.json.capabilities.SpreedCapability
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.Conversation
 import junit.framework.TestCase
 import junit.framework.TestCase
 import org.junit.Test
 import org.junit.Test
@@ -31,7 +31,7 @@ class ParticipantPermissionsTest : TestCase() {
 
 
     @Test
     @Test
     fun test_areFlagsSet() {
     fun test_areFlagsSet() {
-        val user = User()
+        val spreedCapability = SpreedCapability()
         val conversation = Conversation()
         val conversation = Conversation()
         conversation.permissions = ParticipantPermissions.PUBLISH_SCREEN or
         conversation.permissions = ParticipantPermissions.PUBLISH_SCREEN or
             ParticipantPermissions.JOIN_CALL or
             ParticipantPermissions.JOIN_CALL or
@@ -39,7 +39,7 @@ class ParticipantPermissionsTest : TestCase() {
 
 
         val attendeePermissions =
         val attendeePermissions =
             ParticipantPermissions(
             ParticipantPermissions(
-                user,
+                spreedCapability,
                 conversation
                 conversation
             )
             )
 
 

+ 4 - 4
app/src/test/java/com/nextcloud/talk/utils/ShareUtilsTest.kt

@@ -23,7 +23,7 @@ import android.content.Context
 import android.content.res.Resources
 import android.content.res.Resources
 import com.nextcloud.talk.R
 import com.nextcloud.talk.R
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.data.user.model.User
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.domain.ConversationModel
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.users.UserManager
 import io.reactivex.Maybe
 import io.reactivex.Maybe
 import org.junit.Assert
 import org.junit.Assert
@@ -49,19 +49,19 @@ class ShareUtilsTest {
     private val baseUrl = "https://my.nextcloud.com"
     private val baseUrl = "https://my.nextcloud.com"
     private val token = "2aotbrjr"
     private val token = "2aotbrjr"
 
 
-    private lateinit var conversation: Conversation
+    private lateinit var conversation: ConversationModel
 
 
     @Before
     @Before
     fun setUp() {
     fun setUp() {
         MockitoAnnotations.openMocks(this)
         MockitoAnnotations.openMocks(this)
         Mockito.`when`(userManager!!.currentUser).thenReturn(Maybe.just(user))
         Mockito.`when`(userManager!!.currentUser).thenReturn(Maybe.just(user))
-        Mockito.`when`(user!!.baseUrl).thenReturn(baseUrl)
+        Mockito.`when`(user!!.baseUrl!!).thenReturn(baseUrl)
         Mockito.`when`(context!!.resources).thenReturn(resources)
         Mockito.`when`(context!!.resources).thenReturn(resources)
         Mockito.`when`(resources!!.getString(R.string.nc_share_text))
         Mockito.`when`(resources!!.getString(R.string.nc_share_text))
             .thenReturn("Join the conversation at %1\$s/index.php/call/%2\$s")
             .thenReturn("Join the conversation at %1\$s/index.php/call/%2\$s")
         Mockito.`when`(resources.getString(R.string.nc_share_text_pass)).thenReturn("\nPassword: %1\$s")
         Mockito.`when`(resources.getString(R.string.nc_share_text_pass)).thenReturn("\nPassword: %1\$s")
 
 
-        conversation = Conversation(token = token)
+        conversation = ConversationModel(token = token)
     }
     }
 
 
     @Test
     @Test

+ 1 - 1
detekt.yml

@@ -1,5 +1,5 @@
 build:
 build:
-  maxIssues: 116
+  maxIssues: 122
   weights:
   weights:
     # complexity: 2
     # complexity: 2
     # LongParameterList: 1
     # LongParameterList: 1

+ 14 - 54
gradle/verification-keyring.keys

@@ -1313,8 +1313,6 @@ lQyC8nl8P5PgkEZ5CHcGymZlpzihR3ECrPJTk39Sb7D3SxCW4WrChV3kVfmLgvc=
 -----END PGP PUBLIC KEY BLOCK-----
 -----END PGP PUBLIC KEY BLOCK-----
 
 
 pub    C21CE653B639E41A
 pub    C21CE653B639E41A
-uid    Eric Kuck <eric@bluelinelabs.com>
-
 sub    4F80368F9034B8D0
 sub    4F80368F9034B8D0
 -----BEGIN PGP PUBLIC KEY BLOCK-----
 -----BEGIN PGP PUBLIC KEY BLOCK-----
 Version: BCPG v1.68
 Version: BCPG v1.68
@@ -1324,21 +1322,20 @@ aBF7dud1bzw7voZo5ieGK923wUB+R9vQYd5DYfNLBHj9/TrTVCfKfUIeeEQRZYBz
 ufYcDwi4uVx9VPj2wRhkK+lzxphvosJCNFK8Vn82oY7eHQ1RA4AEhCeE/hz8maq6
 ufYcDwi4uVx9VPj2wRhkK+lzxphvosJCNFK8Vn82oY7eHQ1RA4AEhCeE/hz8maq6
 NPoOPjpEN0DVnPIYdjPsdqd4UKQzkX/wMOxghz8SdcVROzUoL+9pZzx968OFuGrV
 NPoOPjpEN0DVnPIYdjPsdqd4UKQzkX/wMOxghz8SdcVROzUoL+9pZzx968OFuGrV
 lUD0su37S6To1IUn6WNEuy1uJTzT3Zqi0hfm31AqPxlLWDOwnuKvUJl3RObyli2k
 lUD0su37S6To1IUn6WNEuy1uJTzT3Zqi0hfm31AqPxlLWDOwnuKvUJl3RObyli2k
-CBtDa5xPE6GU6ZUEFUZ7qbk7iV5p8uTchKVbABEBAAG0IUVyaWMgS3VjayA8ZXJp
-Y0BibHVlbGluZWxhYnMuY29tPrkBDQRU5ChoAQgAxC44rEZjgnJevvrzdL5vCVqC
-1WI9cZ7L8DwF/pvm7NbRKC5GgXigul18UET80q4E3WIi0tTMG+pVWO+1v0dEu/Re
-B/l+hc76iwJjOlwSiQ1jvq6/q0Nhne0/0khSYNWyd0AwJ2VZktcD93dJV4EqTm1O
-Ck1gigAd/GN5wslQkMST/nUYUGm4cA/RZVSA8PSFZDZ2CxHyRyHgaOQNBUmWG2gf
-ExUrrJA26iKowkNqZXWegnzYwlf8ZRE6MZM0JPLOUw/+r4ybI8ny+/U55s2sm0XZ
-CcJvNda5N3SoaC/OgGWZFx1s9UksN7MmvhznaSUMeaeVFbGC3nu9dsQhV9RxMwAR
-AQABiQEfBBgBAgAJBQJU5ChoAhsMAAoJEMIc5lO2OeQadSwH/07x1foZKkFRGMlj
-wCmofKGSqZ9fu6ueOIV6fwHjrhlfkSyKN+96xbjhwIvWhKdSmWP/AsUqRDD/mTHw
-ZMdlgmdXkGEvvCuJDL5FlQzl9OWeeplfVhLysx6dzj/G8AUXlfEIGBvb8Q56d5dK
-MpId4H4vt+YIzS8x/ry+QTTDJAOu1cVJfwoX34yMcZ+IHTzly2XKi4zQ41DyfrgY
-lCodWna10RtBdPZY41Jf4xSezX2q7KZBXXRgyVNYu3dDuNzhJAJ6jy7eMcb6urK6
-n73cz5uZPmWIbp4cAecZB0BfMj/PW37dK0oYdWKLxaDwpxvIV7T45Y64Md2FCC+d
-nC2Xh7c=
-=kzY9
+CBtDa5xPE6GU6ZUEFUZ7qbk7iV5p8uTchKVbABEBAAG5AQ0EVOQoaAEIAMQuOKxG
+Y4JyXr7683S+bwlagtViPXGey/A8Bf6b5uzW0SguRoF4oLpdfFBE/NKuBN1iItLU
+zBvqVVjvtb9HRLv0Xgf5foXO+osCYzpcEokNY76uv6tDYZ3tP9JIUmDVsndAMCdl
+WZLXA/d3SVeBKk5tTgpNYIoAHfxjecLJUJDEk/51GFBpuHAP0WVUgPD0hWQ2dgsR
+8kch4GjkDQVJlhtoHxMVK6yQNuoiqMJDamV1noJ82MJX/GUROjGTNCTyzlMP/q+M
+myPJ8vv1OebNrJtF2QnCbzXWuTd0qGgvzoBlmRcdbPVJLDezJr4c52klDHmnlRWx
+gt57vXbEIVfUcTMAEQEAAYkBHwQYAQIACQUCVOQoaAIbDAAKCRDCHOZTtjnkGnUs
+B/9O8dX6GSpBURjJY8ApqHyhkqmfX7urnjiFen8B464ZX5EsijfvesW44cCL1oSn
+Uplj/wLFKkQw/5kx8GTHZYJnV5BhL7wriQy+RZUM5fTlnnqZX1YS8rMenc4/xvAF
+F5XxCBgb2/EOeneXSjKSHeB+L7fmCM0vMf68vkE0wyQDrtXFSX8KF9+MjHGfiB08
+5ctlyouM0ONQ8n64GJQqHVp2tdEbQXT2WONSX+MUns19quymQV10YMlTWLt3Q7jc
+4SQCeo8u3jHG+rqyup+93M+bmT5liG6eHAHnGQdAXzI/z1t+3StKGHVii8Wg8Kcb
+yFe0+OWOuDHdhQgvnZwtl4e3
+=DwNF
 -----END PGP PUBLIC KEY BLOCK-----
 -----END PGP PUBLIC KEY BLOCK-----
 
 
 pub    C488A74FCAE540C6
 pub    C488A74FCAE540C6
@@ -1674,43 +1671,6 @@ fW1AkBVEk6siyL8PXfxmj9ev3H9xiQVLyJ6HpdHTLVjHjFkgNOLd
 =R7zg
 =R7zg
 -----END PGP PUBLIC KEY BLOCK-----
 -----END PGP PUBLIC KEY BLOCK-----
 
 
-pub    D041CAD2E452550F
-uid    Deanna <deannagarcia@google.com>
-
-sub    5199F3DAE89C332D
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: BCPG v1.68
-
-mQGNBGCtdhoBDADdopjDt4eUNEqLJSw1ZICSR0oq09SOVtJSaSYdF8UiXjBfL1Ds
-fhTDqSv5pT2a2gLj0OU3tFhWHvINLaKKCjQnHVcFXi2LTxt+XBOjRYkFjHVisbaZ
-PZ6HnTMStPrvs+hQ168vU3VfYOsOLN22j53I/Ba+FA7E0G0bqkratuT5L7BTR1mC
-fqDaeisWSCllfe6EEysaFF+/1RcRy+Yt+8ZWV0FZEF7UwQvqKHcYmlkqPIn3v/8y
-J/yvmzIEtCQ1F+bvJbzaROmeJf254G2Uh7IfMYEm9WlqnGwNdbIhil7bdxq8Y/0H
-XbQPaESxkki7yL5JTfH/+UzdklMe+Dga273L/cgzfjV3zJJ9vR94W5ABAbGYh4ZW
-aKvNnT1m4vTbEMfo4r3NF2zc+K9Ly/JNaHqkR5M4SVElvN2lsC5KNUiRvExhg+h0
-mKyx61mu3gUIrC1UOmqhtx7RzQQf7ESMdzmNHY0P93lR0Ic10fyli0wfl7A6q7+q
-zV2a1V2k9Yg6B9sAEQEAAbQgRGVhbm5hIDxkZWFubmFnYXJjaWFAZ29vZ2xlLmNv
-bT65AY0EYK12GgEMAMgP3//QeBsTS3IrfSp3m44el96X6BWona2yo4DvVyuwqfUL
-ZE+Nhj7I+kEZLrA29AOySOD/6quJ4MIJZfq/Do920Di8/10WQ00OdCM1wH7bMz2U
-vcSqsr0iOgQtycuUf7JOHSTME9vqk+C3Lhn0r59AVaRdXEe6zBgNZyzZJeCr5F8w
-RhglPlwvhOGs2aLEqlCxFnY4pLayQFoQyw1lDjHIXHg5JtfOHvqiNXVDcGpyKLG8
-SzImp62iL4sfuA0weVIQeS9kZiQabSYKvSf3TvNXYTgmFz/vjPbYhv9LTkBroTlV
-g3l+UmAxLrHVuXMx0zX3jfNNHAqUjVhPYZhnifMkmGJgLeMIVqr5Q/tx8pzyYiiO
-cqQ1zDg8ubJDGRue1JjlUGdw19OvhFDs+lydukt8Mmhb0gPkBLi2syZHgYHtEooX
-PLwEsJ+SynZCFhZiWj8BsWNFJpaDd8ynNeWhMAcwi3B5ZeQiZaAlV0sItxsrzvbu
-4ZYZtkjAkQdsaaTWSwARAQABiQG8BBgBCgAmFiEEaWthmaKp2MKc54zA0EHK0uRS
-VQ8FAmCtdhoCGwwFCQPCZwAACgkQ0EHK0uRSVQ+G7wwAvaVPDgnM+i2pGQPwq6Mk
-SzhKEG4H1pvBWyYR8H9D3p/dE33IjVu3EEy1h37Nzdyp46KtASGNe3KBodSsh6gv
-PlV5pNGxMNbX6fo8ZGtS83C+6uTF1cYmuO1nmi8P4+7qtcNZg4xv/ujAZIC20kem
-YKDth3FvPxEXsoxY+Ns7sxgd3SqoyLhjcyoczI8uyhim5nfvvbnEd6WrdiBPBtb/
-F1h/nfqdFj2TcZkAlnzGnlVlgU8J60u6zE+9VvBm0lJR73Ar55mQEwarGFPL1a3/
-A7ZEeNa0Dc3Oa5sKMYtxMlGKZ0WGUoGcDWiaDEsv5YyRnaSOaXKM1NkJCR013QAr
-RcHrRBPo+0/RIZVE+b8oEcmGzdL8HNwnm7e06ruZryF9LQA5YBmCKE0urigmgEvC
-zZsj/fMJ+OIZcAhE7UVae48GpW2kLATxmK01oSzvizIlmN3rVz2EnjOun2iuuEpF
-/lmDbjK5n1r3f8npB1l1fT5cozzQJkPVYzhBWH1KXP5X
-=nh9O
------END PGP PUBLIC KEY BLOCK-----
-
 pub    D364ABAA39A47320
 pub    D364ABAA39A47320
 sub    3F606403DCA455C8
 sub    3F606403DCA455C8
 -----BEGIN PGP PUBLIC KEY BLOCK-----
 -----BEGIN PGP PUBLIC KEY BLOCK-----

Неке датотеке нису приказане због велике количине промена