浏览代码

Move controller to new view binding utilizing logic

Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Andy Scherzinger 3 年之前
父节点
当前提交
ca8d6cb782
共有 20 个文件被更改,包括 640 次插入335 次删除
  1. 1 1
      app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
  2. 2 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java
  3. 4 1
      app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java
  4. 9 8
      app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
  5. 5 4
      app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java
  6. 144 194
      app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt
  7. 6 4
      app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
  8. 3 2
      app/src/main/java/com/nextcloud/talk/controllers/ProfileController.java
  9. 6 5
      app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java
  10. 259 0
      app/src/main/java/com/nextcloud/talk/controllers/base/NewBaseController.kt
  11. 2 1
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java
  12. 6 4
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java
  13. 49 0
      app/src/main/java/com/nextcloud/talk/controllers/util/ControllerViewBindingDelegate.kt
  14. 50 84
      app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java
  15. 53 0
      app/src/main/java/com/nextcloud/talk/models/database/User.kt
  16. 3 1
      app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java
  17. 2 1
      app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
  18. 13 11
      app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
  19. 2 1
      app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.java
  20. 21 12
      app/src/main/res/layout/controller_conversation_info.xml

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

@@ -318,7 +318,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
             } else {
             } else {
                 ConductorRemapping.remapChatController(
                 ConductorRemapping.remapChatController(
                     router!!, intent.getLongExtra(BundleKeys.KEY_INTERNAL_USER_ID, -1),
                     router!!, intent.getLongExtra(BundleKeys.KEY_INTERNAL_USER_ID, -1),
-                    intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN), intent.extras!!, false
+                    intent.getStringExtra(KEY_ROOM_TOKEN)!!, intent.extras!!, false
                 )
                 )
             }
             }
         }
         }

+ 2 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java

@@ -46,6 +46,7 @@ import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
 import com.nextcloud.talk.components.filebrowser.models.DavResponse;
 import com.nextcloud.talk.components.filebrowser.models.DavResponse;
 import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
 import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
 import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
 import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.chat.ChatMessage;
 import com.nextcloud.talk.models.json.chat.ChatMessage;
 import com.nextcloud.talk.utils.AccountUtils;
 import com.nextcloud.talk.utils.AccountUtils;
@@ -335,7 +336,7 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
 
 
         String baseUrl = message.activeUser.getBaseUrl();
         String baseUrl = message.activeUser.getBaseUrl();
         String userId = message.activeUser.getUserId();
         String userId = message.activeUser.getUserId();
-        String attachmentFolder = message.activeUser.getAttachmentFolder();
+        String attachmentFolder = CapabilitiesUtil.getAttachmentFolder(message.activeUser);
 
 
         String fileName = message.getSelectedIndividualHashMap().get("name");
         String fileName = message.getSelectedIndividualHashMap().get("name");
         String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
         String mimetype = message.getSelectedIndividualHashMap().get("mimetype");

+ 4 - 1
app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java

@@ -64,6 +64,7 @@ import com.nextcloud.talk.controllers.base.BaseController;
 import com.nextcloud.talk.events.CallNotificationClick;
 import com.nextcloud.talk.events.CallNotificationClick;
 import com.nextcloud.talk.events.ConfigurationChangeEvent;
 import com.nextcloud.talk.events.ConfigurationChangeEvent;
 import com.nextcloud.talk.models.RingtoneSettings;
 import com.nextcloud.talk.models.RingtoneSettings;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.models.json.conversations.RoomOverall;
 import com.nextcloud.talk.models.json.conversations.RoomOverall;
@@ -278,7 +279,9 @@ public class CallNotificationController extends BaseController {
                         runAllThings();
                         runAllThings();
 
 
                         if (apiVersion >= 3) {
                         if (apiVersion >= 3) {
-                            boolean hasCallFlags = userBeingCalled.hasSpreedFeatureCapability("conversation-call-flags");
+                            boolean hasCallFlags =
+                                    CapabilitiesUtil.hasSpreedFeatureCapability(userBeingCalled,
+                                                                                "conversation-call-flags");
                             if (hasCallFlags) {
                             if (hasCallFlags) {
                                 if (isInCallWithVideo(currentConversation.callFlag)) {
                                 if (isInCallWithVideo(currentConversation.callFlag)) {
                                     incomingCallVoiceOrVideoTextView.setText(String.format(getResources().getString(R.string.nc_call_video),
                                     incomingCallVoiceOrVideoTextView.setText(String.format(getResources().getString(R.string.nc_call_video),

+ 9 - 8
app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt

@@ -99,6 +99,7 @@ import com.nextcloud.talk.controllers.base.BaseController
 import com.nextcloud.talk.events.UserMentionClickEvent
 import com.nextcloud.talk.events.UserMentionClickEvent
 import com.nextcloud.talk.events.WebSocketCommunicationEvent
 import com.nextcloud.talk.events.WebSocketCommunicationEvent
 import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
 import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
+import com.nextcloud.talk.models.database.CapabilitiesUtil
 import com.nextcloud.talk.models.database.UserEntity
 import com.nextcloud.talk.models.database.UserEntity
 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
@@ -297,7 +298,8 @@ class ChatController(args: Bundle) :
     }
     }
 
 
     private fun getRoomInfo() {
     private fun getRoomInfo() {
-        val shouldRepeat = conversationUser?.hasSpreedFeatureCapability("webinary-lobby") ?: false
+        val shouldRepeat = CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") ?:
+        false
         if (shouldRepeat) {
         if (shouldRepeat) {
             checkingLobbyStatus = true
             checkingLobbyStatus = true
         }
         }
@@ -540,7 +542,7 @@ class ChatController(args: Bundle) :
         })
         })
 
 
         val filters = arrayOfNulls<InputFilter>(1)
         val filters = arrayOfNulls<InputFilter>(1)
-        val lengthFilter = conversationUser?.messageMaxLength ?: 1000
+        val lengthFilter = CapabilitiesUtil.getMessageMaxLength(conversationUser) ?: 1000
 
 
         filters[0] = InputFilter.LengthFilter(lengthFilter)
         filters[0] = InputFilter.LengthFilter(lengthFilter)
         messageInput?.filters = filters
         messageInput?.filters = filters
@@ -765,7 +767,7 @@ class ChatController(args: Bundle) :
             require(files.isNotEmpty())
             require(files.isNotEmpty())
             val data: Data = Data.Builder()
             val data: Data = Data.Builder()
                 .putStringArray(UploadAndShareFilesWorker.DEVICE_SOURCEFILES, files.toTypedArray())
                 .putStringArray(UploadAndShareFilesWorker.DEVICE_SOURCEFILES, files.toTypedArray())
-                .putString(UploadAndShareFilesWorker.NC_TARGETPATH, conversationUser?.getAttachmentFolder())
+                .putString(UploadAndShareFilesWorker.NC_TARGETPATH, CapabilitiesUtil.getAttachmentFolder(conversationUser))
                 .putString(UploadAndShareFilesWorker.ROOM_TOKEN, roomToken)
                 .putString(UploadAndShareFilesWorker.ROOM_TOKEN, roomToken)
                 .build()
                 .build()
             val uploadWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(UploadAndShareFilesWorker::class.java)
             val uploadWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(UploadAndShareFilesWorker::class.java)
@@ -851,9 +853,8 @@ class ChatController(args: Bundle) :
         eventBus?.register(this)
         eventBus?.register(this)
 
 
         if (conversationUser?.userId != "?" &&
         if (conversationUser?.userId != "?" &&
-            conversationUser?.hasSpreedFeatureCapability("mention-flag") ?: false &&
-            activity != null
-        ) {
+            CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "mention-flag") ?: false &&
+            activity != null) {
             activity?.findViewById<View>(R.id.toolbar)?.setOnClickListener { v -> showConversationInfoScreen() }
             activity?.findViewById<View>(R.id.toolbar)?.setOnClickListener { v -> showConversationInfoScreen() }
         }
         }
 
 
@@ -1489,7 +1490,7 @@ class ChatController(args: Bundle) :
     override fun onPrepareOptionsMenu(menu: Menu) {
     override fun onPrepareOptionsMenu(menu: Menu) {
         super.onPrepareOptionsMenu(menu)
         super.onPrepareOptionsMenu(menu)
         conversationUser?.let {
         conversationUser?.let {
-            if (it.hasSpreedFeatureCapability("read-only-rooms")) {
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(it, "read-only-rooms")) {
                 checkReadOnlyState()
                 checkReadOnlyState()
             }
             }
         }
         }
@@ -1813,7 +1814,7 @@ class ChatController(args: Bundle) :
         }
         }
         if (!isUserAllowedByPrivileges) return false
         if (!isUserAllowedByPrivileges) return false
 
 
-        if (!conversationUser.hasSpreedFeatureCapability("delete-messages")) return false
+        if (!CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "delete-messages")) return false
 
 
         return true
         return true
     }
     }

+ 5 - 4
app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java

@@ -56,6 +56,7 @@ import com.nextcloud.talk.controllers.bottomsheet.OperationsMenuController;
 import com.nextcloud.talk.events.BottomSheetLockEvent;
 import com.nextcloud.talk.events.BottomSheetLockEvent;
 import com.nextcloud.talk.jobs.AddParticipantsToConversation;
 import com.nextcloud.talk.jobs.AddParticipantsToConversation;
 import com.nextcloud.talk.models.RetrofitBucket;
 import com.nextcloud.talk.models.RetrofitBucket;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall;
 import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall;
 import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser;
 import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser;
@@ -493,13 +494,13 @@ public class ContactsController extends BaseController implements SearchView.OnQ
         if (!isAddingParticipantsView) {
         if (!isAddingParticipantsView) {
             // groups
             // groups
             shareTypesList.add("1");
             shareTypesList.add("1");
-        } else if (currentUser.hasSpreedFeatureCapability("invite-groups-and-mails")) {
+        } else if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails")) {
             // groups
             // groups
             shareTypesList.add("1");
             shareTypesList.add("1");
             // emails
             // emails
             shareTypesList.add("4");
             shareTypesList.add("4");
         }
         }
-        if (currentUser.hasSpreedFeatureCapability("circles-support")) {
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "circles-support")) {
             // circles
             // circles
             shareTypesList.add("7");
             shareTypesList.add("7");
         }
         }
@@ -974,8 +975,8 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                     }
                     }
                 }
                 }
 
 
-                if (currentUser.hasSpreedFeatureCapability("last-room-activity")
-                        && !currentUser.hasSpreedFeatureCapability("invite-groups-and-mails") &&
+                if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "last-room-activity")
+                        && !CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails") &&
                         "groups".equals(((UserItem) adapter.getItem(position)).getModel().getSource()) &&
                         "groups".equals(((UserItem) adapter.getItem(position)).getModel().getSource()) &&
                         participant.isSelected() &&
                         participant.isSelected() &&
                         adapter.getSelectedItemCount() > 1) {
                         adapter.getSelectedItemCount() > 1) {

+ 144 - 194
app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt

@@ -21,26 +21,19 @@
 package com.nextcloud.talk.controllers
 package com.nextcloud.talk.controllers
 
 
 import android.annotation.SuppressLint
 import android.annotation.SuppressLint
-import android.content.Context
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.graphics.drawable.LayerDrawable
 import android.os.Bundle
 import android.os.Bundle
 import android.text.TextUtils
 import android.text.TextUtils
 import android.util.Log
 import android.util.Log
-import android.view.LayoutInflater
 import android.view.MenuItem
 import android.view.MenuItem
 import android.view.View
 import android.view.View
-import android.view.ViewGroup
-import android.widget.ProgressBar
 import androidx.appcompat.widget.SwitchCompat
 import androidx.appcompat.widget.SwitchCompat
-import androidx.emoji.widget.EmojiTextView
-import androidx.recyclerview.widget.RecyclerView
+import androidx.core.content.ContextCompat
 import androidx.work.Data
 import androidx.work.Data
 import androidx.work.OneTimeWorkRequest
 import androidx.work.OneTimeWorkRequest
 import androidx.work.WorkManager
 import androidx.work.WorkManager
 import autodagger.AutoInjector
 import autodagger.AutoInjector
-import butterknife.BindView
-import butterknife.OnClick
 import com.afollestad.materialdialogs.LayoutMode.WRAP_CONTENT
 import com.afollestad.materialdialogs.LayoutMode.WRAP_CONTENT
 import com.afollestad.materialdialogs.MaterialDialog
 import com.afollestad.materialdialogs.MaterialDialog
 import com.afollestad.materialdialogs.bottomsheets.BottomSheet
 import com.afollestad.materialdialogs.bottomsheets.BottomSheet
@@ -48,17 +41,19 @@ import com.afollestad.materialdialogs.datetime.dateTimePicker
 import com.bluelinelabs.conductor.RouterTransaction
 import com.bluelinelabs.conductor.RouterTransaction
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
 import com.facebook.drawee.backends.pipeline.Fresco
 import com.facebook.drawee.backends.pipeline.Fresco
-import com.facebook.drawee.view.SimpleDraweeView
 import com.nextcloud.talk.R
 import com.nextcloud.talk.R
 import com.nextcloud.talk.adapters.items.UserItem
 import com.nextcloud.talk.adapters.items.UserItem
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.application.NextcloudTalkApplication
-import com.nextcloud.talk.controllers.base.BaseController
+import com.nextcloud.talk.controllers.base.NewBaseController
 import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
 import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
 import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
 import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
+import com.nextcloud.talk.controllers.util.viewBinding
+import com.nextcloud.talk.databinding.ControllerConversationInfoBinding
 import com.nextcloud.talk.events.EventStatus
 import com.nextcloud.talk.events.EventStatus
 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.database.CapabilitiesUtil
 import com.nextcloud.talk.models.database.UserEntity
 import com.nextcloud.talk.models.database.UserEntity
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.RoomOverall
 import com.nextcloud.talk.models.json.conversations.RoomOverall
@@ -75,11 +70,6 @@ import com.nextcloud.talk.utils.bundle.BundleKeys
 import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
 import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
 import com.yarolegovich.lovelydialog.LovelySaveStateHandler
 import com.yarolegovich.lovelydialog.LovelySaveStateHandler
 import com.yarolegovich.lovelydialog.LovelyStandardDialog
 import com.yarolegovich.lovelydialog.LovelyStandardDialog
-import com.yarolegovich.mp.MaterialChoicePreference
-import com.yarolegovich.mp.MaterialPreferenceCategory
-import com.yarolegovich.mp.MaterialPreferenceScreen
-import com.yarolegovich.mp.MaterialStandardPreference
-import com.yarolegovich.mp.MaterialSwitchPreference
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
 import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
 import io.reactivex.Observer
 import io.reactivex.Observer
@@ -96,70 +86,18 @@ import java.util.Locale
 import javax.inject.Inject
 import javax.inject.Inject
 
 
 @AutoInjector(NextcloudTalkApplication::class)
 @AutoInjector(NextcloudTalkApplication::class)
-class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleAdapter.OnItemClickListener {
+class ConversationInfoController(args: Bundle) : NewBaseController(R.layout.controller_conversation_info, args),
+    FlexibleAdapter
+    .OnItemClickListener {
+    private val binding: ControllerConversationInfoBinding by viewBinding(ControllerConversationInfoBinding::bind)
 
 
-    @BindView(R.id.notification_settings)
-    lateinit var notificationsPreferenceScreen: MaterialPreferenceScreen
+    @Inject
+    @JvmField
+    var ncApi: NcApi? = null
 
 
-    @BindView(R.id.progressBar)
-    lateinit var progressBar: ProgressBar
-
-    @BindView(R.id.conversation_info_message_notifications)
-    lateinit var messageNotificationLevel: MaterialChoicePreference
-
-    @BindView(R.id.webinar_settings)
-    lateinit var conversationInfoWebinar: MaterialPreferenceScreen
-
-    @BindView(R.id.conversation_info_lobby)
-    lateinit var conversationInfoLobby: MaterialSwitchPreference
-
-    @BindView(R.id.conversation_info_name)
-    lateinit var nameCategoryView: MaterialPreferenceCategory
-
-    @BindView(R.id.start_time_preferences)
-    lateinit var startTimeView: MaterialStandardPreference
-
-    @BindView(R.id.avatar_image)
-    lateinit var conversationAvatarImageView: SimpleDraweeView
-
-    @BindView(R.id.display_name_text)
-    lateinit var conversationDisplayName: EmojiTextView
-
-    @BindView(R.id.conversation_description)
-    lateinit var descriptionCategoryView: MaterialPreferenceCategory
-
-    @BindView(R.id.description_text)
-    lateinit var conversationDescription: EmojiTextView
-
-    @BindView(R.id.participants_list_category)
-    lateinit var participantsListCategory: MaterialPreferenceCategory
-
-    @BindView(R.id.addParticipantsAction)
-    lateinit var addParticipantsAction: MaterialStandardPreference
-
-    @BindView(R.id.recycler_view)
-    lateinit var recyclerView: RecyclerView
-
-    @BindView(R.id.deleteConversationAction)
-    lateinit var deleteConversationAction: MaterialStandardPreference
-
-    @BindView(R.id.leaveConversationAction)
-    lateinit var leaveConversationAction: MaterialStandardPreference
-
-    @BindView(R.id.ownOptions)
-    lateinit var ownOptionsCategory: MaterialPreferenceCategory
-
-    @BindView(R.id.muteCalls)
-    lateinit var muteCalls: MaterialSwitchPreference
-
-    @set:Inject
-    lateinit var ncApi: NcApi
-
-    @set:Inject
-    lateinit var context: Context
-
-    @set:Inject
-    lateinit var eventBus: EventBus
+    @Inject
+    @JvmField
+    var eventBus: EventBus? = null
 
 
     private val conversationToken: String?
     private val conversationToken: String?
     private val conversationUser: UserEntity?
     private val conversationUser: UserEntity?
@@ -207,20 +145,20 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
         }
         }
     }
     }
 
 
-    override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
-        return inflater.inflate(R.layout.controller_conversation_info, container, false)
-    }
-
     override fun onAttach(view: View) {
     override fun onAttach(view: View) {
         super.onAttach(view)
         super.onAttach(view)
-        eventBus.register(this)
+        eventBus?.register(this)
 
 
         if (databaseStorageModule == null) {
         if (databaseStorageModule == null) {
             databaseStorageModule = DatabaseStorageModule(conversationUser!!, conversationToken)
             databaseStorageModule = DatabaseStorageModule(conversationUser!!, conversationToken)
         }
         }
 
 
-        notificationsPreferenceScreen.setStorageModule(databaseStorageModule)
-        conversationInfoWebinar.setStorageModule(databaseStorageModule)
+        binding.notificationSettingsView.notificationSettings.setStorageModule(databaseStorageModule)
+        binding.webinarInfoView.webinarSettings.setStorageModule(databaseStorageModule)
+
+        binding.deleteConversationAction.setOnClickListener { showDeleteConversationDialog(null) }
+        binding.leaveConversationAction.setOnClickListener { leaveConversation() }
+        binding.addParticipantsAction.setOnClickListener { addParticipants() }
 
 
         fetchRoomInfo()
         fetchRoomInfo()
     }
     }
@@ -232,27 +170,27 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             saveStateHandler = LovelySaveStateHandler()
             saveStateHandler = LovelySaveStateHandler()
         }
         }
 
 
-        addParticipantsAction.visibility = View.GONE
+        binding.addParticipantsAction.visibility = View.GONE
     }
     }
 
 
     private fun setupWebinaryView() {
     private fun setupWebinaryView() {
-        if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") &&
+        if (CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") &&
             (
             (
                 conversation!!.type == Conversation.ConversationType.ROOM_GROUP_CALL ||
                 conversation!!.type == Conversation.ConversationType.ROOM_GROUP_CALL ||
                     conversation!!.type == Conversation.ConversationType.ROOM_PUBLIC_CALL
                     conversation!!.type == Conversation.ConversationType.ROOM_PUBLIC_CALL
                 ) &&
                 ) &&
             conversation!!.canModerate(conversationUser)
             conversation!!.canModerate(conversationUser)
         ) {
         ) {
-            conversationInfoWebinar.visibility = View.VISIBLE
+            binding.webinarInfoView.webinarSettings.visibility = View.VISIBLE
 
 
             val isLobbyOpenToModeratorsOnly =
             val isLobbyOpenToModeratorsOnly =
                 conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
                 conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
-            (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat)
+            (binding.webinarInfoView.conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat)
                 .isChecked = isLobbyOpenToModeratorsOnly
                 .isChecked = isLobbyOpenToModeratorsOnly
 
 
             reconfigureLobbyTimerView()
             reconfigureLobbyTimerView()
 
 
-            startTimeView.setOnClickListener {
+            binding.webinarInfoView.startTimePreferences.setOnClickListener {
                 MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
                 MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
                     val currentTimeCalendar = Calendar.getInstance()
                     val currentTimeCalendar = Calendar.getInstance()
                     if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) {
                     if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) {
@@ -273,17 +211,18 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                 }
                 }
             }
             }
 
 
-            (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).setOnCheckedChangeListener { _, _ ->
+            (binding.webinarInfoView.conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).setOnCheckedChangeListener { _, _ ->
                 reconfigureLobbyTimerView()
                 reconfigureLobbyTimerView()
                 submitLobbyChanges()
                 submitLobbyChanges()
             }
             }
         } else {
         } else {
-            conversationInfoWebinar.visibility = View.GONE
+            binding.webinarInfoView.webinarSettings.visibility = View.GONE
         }
         }
     }
     }
 
 
     fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
     fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
-        val isChecked = (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).isChecked
+        val isChecked =
+            (binding.webinarInfoView.conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).isChecked
 
 
         if (dateTime != null && isChecked) {
         if (dateTime != null && isChecked) {
             conversation!!.lobbyTimer = (dateTime.timeInMillis - (dateTime.time.seconds * 1000)) / 1000
             conversation!!.lobbyTimer = (dateTime.timeInMillis - (dateTime.time.seconds * 1000)) / 1000
@@ -295,34 +234,38 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             .LOBBY_STATE_MODERATORS_ONLY else Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
             .LOBBY_STATE_MODERATORS_ONLY else Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
 
 
         if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != java.lang.Long.MIN_VALUE && conversation!!.lobbyTimer != 0L) {
         if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != java.lang.Long.MIN_VALUE && conversation!!.lobbyTimer != 0L) {
-            startTimeView.setSummary(DateUtils.getLocalDateStringFromTimestampForLobby(conversation!!.lobbyTimer))
+            binding.webinarInfoView.startTimePreferences.setSummary(
+                DateUtils.getLocalDateStringFromTimestampForLobby(
+                    conversation!!.lobbyTimer
+                )
+            )
         } else {
         } else {
-            startTimeView.setSummary(R.string.nc_manual)
+            binding.webinarInfoView.startTimePreferences.setSummary(R.string.nc_manual)
         }
         }
 
 
         if (isChecked) {
         if (isChecked) {
-            startTimeView.visibility = View.VISIBLE
+            binding.webinarInfoView.startTimePreferences.visibility = View.VISIBLE
         } else {
         } else {
-            startTimeView.visibility = View.GONE
+            binding.webinarInfoView.startTimePreferences.visibility = View.GONE
         }
         }
     }
     }
 
 
     fun submitLobbyChanges() {
     fun submitLobbyChanges() {
         val state = if (
         val state = if (
-            (conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).isChecked
+            (binding.webinarInfoView.conversationInfoLobby.findViewById<View>(R.id.mp_checkable) as SwitchCompat).isChecked
         ) 1 else 0
         ) 1 else 0
 
 
         val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
         val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 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
         )
         )
-            .subscribeOn(Schedulers.io())
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribe(object : Observer<GenericOverall> {
+            ?.subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(object : Observer<GenericOverall> {
                 override fun onComplete() {
                 override fun onComplete() {
                 }
                 }
 
 
@@ -352,7 +295,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
 
 
     override fun onDetach(view: View) {
     override fun onDetach(view: View) {
         super.onDetach(view)
         super.onDetach(view)
-        eventBus.unregister(this)
+        eventBus?.unregister(this)
     }
     }
 
 
     private fun showDeleteConversationDialog(savedInstanceState: Bundle?) {
     private fun showDeleteConversationDialog(savedInstanceState: Bundle?) {
@@ -397,9 +340,9 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             }
             }
 
 
             val layoutManager = SmoothScrollLinearLayoutManager(activity)
             val layoutManager = SmoothScrollLinearLayoutManager(activity)
-            recyclerView.layoutManager = layoutManager
-            recyclerView.setHasFixedSize(true)
-            recyclerView.adapter = adapter
+            binding.recyclerView.layoutManager = layoutManager
+            binding.recyclerView.setHasFixedSize(true)
+            binding.recyclerView.adapter = adapter
 
 
             adapter!!.addListener(this)
             adapter!!.addListener(this)
         }
         }
@@ -438,17 +381,27 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
 
 
         setupAdapter()
         setupAdapter()
 
 
-        participantsListCategory.visibility = View.VISIBLE
+        binding.participantsListCategory.visibility = View.VISIBLE
         adapter!!.updateDataSet(recyclerViewItems)
         adapter!!.updateDataSet(recyclerViewItems)
     }
     }
 
 
+    /**
     override fun getTitle(): String? {
     override fun getTitle(): String? {
-        return if (hasAvatarSpacing) {
-            " " + resources!!.getString(R.string.nc_conversation_menu_conversation_info)
-        } else {
-            resources!!.getString(R.string.nc_conversation_menu_conversation_info)
-        }
+    return if (hasAvatarSpacing) {
+    " " + resources!!.getString(R.string.nc_conversation_menu_conversation_info)
+    } else {
+    resources!!.getString(R.string.nc_conversation_menu_conversation_info)
     }
     }
+    }
+     */
+
+    override val title: String?
+        get() =
+            if (hasAvatarSpacing) {
+                " " + resources!!.getString(R.string.nc_conversation_menu_conversation_info)
+            } else {
+                resources!!.getString(R.string.nc_conversation_menu_conversation_info)
+            }
 
 
     private fun getListOfParticipants() {
     private fun getListOfParticipants() {
         var apiVersion = 1
         var apiVersion = 1
@@ -457,13 +410,13 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
             apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
         }
         }
 
 
-        ncApi.getPeersForCall(
+        ncApi?.getPeersForCall(
             credentials,
             credentials,
             ApiUtils.getUrlForParticipants(apiVersion, conversationUser!!.baseUrl, conversationToken)
             ApiUtils.getUrlForParticipants(apiVersion, conversationUser!!.baseUrl, conversationToken)
         )
         )
-            .subscribeOn(Schedulers.io())
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribe(object : Observer<ParticipantsOverall> {
+            ?.subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(object : Observer<ParticipantsOverall> {
                 override fun onSubscribe(d: Disposable) {
                 override fun onSubscribe(d: Disposable) {
                     participantsDisposable = d
                     participantsDisposable = d
                 }
                 }
@@ -481,7 +434,6 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             })
             })
     }
     }
 
 
-    @OnClick(R.id.addParticipantsAction)
     internal fun addParticipants() {
     internal fun addParticipants() {
         val bundle = Bundle()
         val bundle = Bundle()
         val existingParticipantsId = arrayListOf<String>()
         val existingParticipantsId = arrayListOf<String>()
@@ -511,8 +463,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
         )
         )
     }
     }
 
 
-    @OnClick(R.id.leaveConversationAction)
-    internal fun leaveConversation() {
+    private fun leaveConversation() {
         workerData?.let {
         workerData?.let {
             WorkManager.getInstance().enqueue(
             WorkManager.getInstance().enqueue(
                 OneTimeWorkRequest.Builder(
                 OneTimeWorkRequest.Builder(
@@ -535,11 +486,6 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
         }
         }
     }
     }
 
 
-    @OnClick(R.id.deleteConversationAction)
-    internal fun deleteConversationClick() {
-        showDeleteConversationDialog(null)
-    }
-
     private fun popTwoLastControllers() {
     private fun popTwoLastControllers() {
         var backstack = router.backstack
         var backstack = router.backstack
         backstack = backstack.subList(0, backstack.size - 2)
         backstack = backstack.subList(0, backstack.size - 2)
@@ -553,10 +499,10 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
             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> {
+        ncApi?.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, conversationUser!!.baseUrl, conversationToken))
+            ?.subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(object : Observer<RoomOverall> {
                 override fun onSubscribe(d: Disposable) {
                 override fun onSubscribe(d: Disposable) {
                     roomDisposable = d
                     roomDisposable = d
                 }
                 }
@@ -567,49 +513,49 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                     val conversationCopy = conversation
                     val conversationCopy = conversation
 
 
                     if (conversationCopy!!.canModerate(conversationUser)) {
                     if (conversationCopy!!.canModerate(conversationUser)) {
-                        addParticipantsAction.visibility = View.VISIBLE
+                        binding.addParticipantsAction.visibility = View.VISIBLE
                     } else {
                     } else {
-                        addParticipantsAction.visibility = View.GONE
+                        binding.addParticipantsAction.visibility = View.GONE
                     }
                     }
 
 
                     if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
                     if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
-                        ownOptionsCategory.visibility = View.VISIBLE
+                        binding.ownOptions.visibility = View.VISIBLE
 
 
                         setupWebinaryView()
                         setupWebinaryView()
 
 
                         if (!conversation!!.canLeave(conversationUser)) {
                         if (!conversation!!.canLeave(conversationUser)) {
-                            leaveConversationAction.visibility = View.GONE
+                            binding.leaveConversationAction.visibility = View.GONE
                         } else {
                         } else {
-                            leaveConversationAction.visibility = View.VISIBLE
+                            binding.leaveConversationAction.visibility = View.VISIBLE
                         }
                         }
 
 
                         if (!conversation!!.canDelete(conversationUser)) {
                         if (!conversation!!.canDelete(conversationUser)) {
-                            deleteConversationAction.visibility = View.GONE
+                            binding.deleteConversationAction.visibility = View.GONE
                         } else {
                         } else {
-                            deleteConversationAction.visibility = View.VISIBLE
+                            binding.deleteConversationAction.visibility = View.VISIBLE
                         }
                         }
 
 
                         if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
                         if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
-                            muteCalls.visibility = View.GONE
+                            binding.notificationSettingsView.muteCalls.visibility = View.GONE
                         }
                         }
 
 
                         getListOfParticipants()
                         getListOfParticipants()
 
 
-                        progressBar.visibility = View.GONE
+                        binding.progressBar.visibility = View.GONE
 
 
-                        nameCategoryView.visibility = View.VISIBLE
+                        binding.conversationInfoName.visibility = View.VISIBLE
 
 
-                        conversationDisplayName.text = conversation!!.displayName
+                        binding.displayNameText.text = conversation!!.displayName
 
 
                         if (conversation!!.description != null && !conversation!!.description.isEmpty()) {
                         if (conversation!!.description != null && !conversation!!.description.isEmpty()) {
-                            conversationDescription.text = conversation!!.description
-                            descriptionCategoryView.visibility = View.VISIBLE
+                            binding.descriptionText.text = conversation!!.description
+                            binding.conversationDescription.visibility = View.VISIBLE
                         }
                         }
 
 
                         loadConversationAvatar()
                         loadConversationAvatar()
                         adjustNotificationLevelUI()
                         adjustNotificationLevelUI()
 
 
-                        notificationsPreferenceScreen.visibility = View.VISIBLE
+                        binding.notificationSettingsView.notificationSettings.visibility = View.VISIBLE
                     }
                     }
                 }
                 }
 
 
@@ -624,9 +570,9 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
 
 
     private fun adjustNotificationLevelUI() {
     private fun adjustNotificationLevelUI() {
         if (conversation != null) {
         if (conversation != null) {
-            if (conversationUser != null && conversationUser.hasSpreedFeatureCapability("notification-levels")) {
-                messageNotificationLevel.isEnabled = true
-                messageNotificationLevel.alpha = 1.0f
+            if (conversationUser != null && CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "notification-levels")) {
+                binding.notificationSettingsView.conversationInfoMessageNotifications.isEnabled = true
+                binding.notificationSettingsView.conversationInfoMessageNotifications.alpha = 1.0f
 
 
                 if (conversation!!.notificationLevel != Conversation.NotificationLevel.DEFAULT) {
                 if (conversation!!.notificationLevel != Conversation.NotificationLevel.DEFAULT) {
                     val stringValue: String =
                     val stringValue: String =
@@ -637,13 +583,13 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                             else -> "mention"
                             else -> "mention"
                         }
                         }
 
 
-                    messageNotificationLevel.value = stringValue
+                    binding.notificationSettingsView.conversationInfoMessageNotifications.value = stringValue
                 } else {
                 } else {
                     setProperNotificationValue(conversation)
                     setProperNotificationValue(conversation)
                 }
                 }
             } else {
             } else {
-                messageNotificationLevel.isEnabled = false
-                messageNotificationLevel.alpha = 0.38f
+                binding.notificationSettingsView.conversationInfoMessageNotifications.isEnabled = false
+                binding.notificationSettingsView.conversationInfoMessageNotifications.alpha = 0.38f
                 setProperNotificationValue(conversation)
                 setProperNotificationValue(conversation)
             }
             }
         }
         }
@@ -652,13 +598,13 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
     private fun setProperNotificationValue(conversation: Conversation?) {
     private fun setProperNotificationValue(conversation: Conversation?) {
         if (conversation!!.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
         if (conversation!!.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
             // hack to see if we get mentioned always or just on mention
             // hack to see if we get mentioned always or just on mention
-            if (conversationUser!!.hasSpreedFeatureCapability("mention-flag")) {
-                messageNotificationLevel.value = "always"
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "mention-flag")) {
+                binding.notificationSettingsView.conversationInfoMessageNotifications.value = "always"
             } else {
             } else {
-                messageNotificationLevel.value = "mention"
+                binding.notificationSettingsView.conversationInfoMessageNotifications.value = "mention"
             }
             }
         } else {
         } else {
-            messageNotificationLevel.value = "mention"
+            binding.notificationSettingsView.conversationInfoMessageNotifications.value = "mention"
         }
         }
     }
     }
 
 
@@ -666,7 +612,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
         when (conversation!!.type) {
         when (conversation!!.type) {
             Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
             Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
                 val draweeController = Fresco.newDraweeControllerBuilder()
                 val draweeController = Fresco.newDraweeControllerBuilder()
-                    .setOldController(conversationAvatarImageView.controller)
+                    .setOldController(binding.avatarImage.controller)
                     .setAutoPlayAnimations(true)
                     .setAutoPlayAnimations(true)
                     .setImageRequest(
                     .setImageRequest(
                         DisplayUtils.getImageRequestForUrl(
                         DisplayUtils.getImageRequestForUrl(
@@ -678,20 +624,20 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                         )
                         )
                     )
                     )
                     .build()
                     .build()
-                conversationAvatarImageView.controller = draweeController
+                binding.avatarImage.controller = draweeController
             }
             }
-            Conversation.ConversationType.ROOM_GROUP_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(
+            Conversation.ConversationType.ROOM_GROUP_CALL -> binding.avatarImage.hierarchy.setPlaceholderImage(
                 R.drawable.ic_circular_group
                 R.drawable.ic_circular_group
             )
             )
-            Conversation.ConversationType.ROOM_PUBLIC_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage(
+            Conversation.ConversationType.ROOM_PUBLIC_CALL -> binding.avatarImage.hierarchy.setPlaceholderImage(
                 R.drawable.ic_circular_link
                 R.drawable.ic_circular_link
             )
             )
             Conversation.ConversationType.ROOM_SYSTEM -> {
             Conversation.ConversationType.ROOM_SYSTEM -> {
                 val layers = arrayOfNulls<Drawable>(2)
                 val layers = arrayOfNulls<Drawable>(2)
-                layers[0] = context.getDrawable(R.drawable.ic_launcher_background)
-                layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground)
+                layers[0] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_background)
+                layers[1] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_foreground)
                 val layerDrawable = LayerDrawable(layers)
                 val layerDrawable = LayerDrawable(layers)
-                conversationAvatarImageView.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable))
+                binding.avatarImage.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable))
             }
             }
 
 
             else -> {
             else -> {
@@ -720,7 +666,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
         if (participant.type == Participant.ParticipantType.MODERATOR ||
         if (participant.type == Participant.ParticipantType.MODERATOR ||
             participant.type == Participant.ParticipantType.GUEST_MODERATOR
             participant.type == Participant.ParticipantType.GUEST_MODERATOR
         ) {
         ) {
-            ncApi.demoteAttendeeFromModerator(
+            ncApi?.demoteAttendeeFromModerator(
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoomModerators(
                 ApiUtils.getUrlForRoomModerators(
                     apiVersion,
                     apiVersion,
@@ -729,13 +675,13 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                 ),
                 ),
                 participant.attendeeId
                 participant.attendeeId
             )
             )
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(subscriber)
+                ?.subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.subscribe(subscriber)
         } else if (participant.type == Participant.ParticipantType.USER ||
         } else if (participant.type == Participant.ParticipantType.USER ||
             participant.type == Participant.ParticipantType.GUEST
             participant.type == Participant.ParticipantType.GUEST
         ) {
         ) {
-            ncApi.promoteAttendeeToModerator(
+            ncApi?.promoteAttendeeToModerator(
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoomModerators(
                 ApiUtils.getUrlForRoomModerators(
                     apiVersion,
                     apiVersion,
@@ -744,9 +690,9 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                 ),
                 ),
                 participant.attendeeId
                 participant.attendeeId
             )
             )
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(subscriber)
+                ?.subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.subscribe(subscriber)
         }
         }
     }
     }
 
 
@@ -769,7 +715,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
         }
         }
 
 
         if (participant.type == Participant.ParticipantType.MODERATOR) {
         if (participant.type == Participant.ParticipantType.MODERATOR) {
-            ncApi.demoteModeratorToUser(
+            ncApi?.demoteModeratorToUser(
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoomModerators(
                 ApiUtils.getUrlForRoomModerators(
                     apiVersion,
                     apiVersion,
@@ -778,11 +724,11 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                 ),
                 ),
                 participant.userId
                 participant.userId
             )
             )
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(subscriber)
+                ?.subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.subscribe(subscriber)
         } else if (participant.type == Participant.ParticipantType.USER) {
         } else if (participant.type == Participant.ParticipantType.USER) {
-            ncApi.promoteUserToModerator(
+            ncApi?.promoteUserToModerator(
                 credentials,
                 credentials,
                 ApiUtils.getUrlForRoomModerators(
                 ApiUtils.getUrlForRoomModerators(
                     apiVersion,
                     apiVersion,
@@ -791,15 +737,15 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                 ),
                 ),
                 participant.userId
                 participant.userId
             )
             )
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(subscriber)
+                ?.subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.subscribe(subscriber)
         }
         }
     }
     }
 
 
     fun removeAttendeeFromConversation(apiVersion: Int, participant: Participant) {
     fun removeAttendeeFromConversation(apiVersion: Int, participant: Participant) {
         if (apiVersion >= ApiUtils.APIv4) {
         if (apiVersion >= ApiUtils.APIv4) {
-            ncApi.removeAttendeeFromConversation(
+            ncApi?.removeAttendeeFromConversation(
                 credentials,
                 credentials,
                 ApiUtils.getUrlForAttendees(
                 ApiUtils.getUrlForAttendees(
                     apiVersion,
                     apiVersion,
@@ -808,9 +754,9 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                 ),
                 ),
                 participant.attendeeId
                 participant.attendeeId
             )
             )
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(object : Observer<GenericOverall> {
+                ?.subscribeOn(Schedulers.io())
+                ?.observeOn(AndroidSchedulers.mainThread())
+                ?.subscribe(object : Observer<GenericOverall> {
                     override fun onSubscribe(d: Disposable) {
                     override fun onSubscribe(d: Disposable) {
                     }
                     }
 
 
@@ -830,7 +776,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             if (participant.type == Participant.ParticipantType.GUEST ||
             if (participant.type == Participant.ParticipantType.GUEST ||
                 participant.type == Participant.ParticipantType.USER_FOLLOWING_LINK
                 participant.type == Participant.ParticipantType.USER_FOLLOWING_LINK
             ) {
             ) {
-                ncApi.removeParticipantFromConversation(
+                ncApi?.removeParticipantFromConversation(
                     credentials,
                     credentials,
                     ApiUtils.getUrlForRemovingParticipantFromConversation(
                     ApiUtils.getUrlForRemovingParticipantFromConversation(
                         conversationUser!!.baseUrl,
                         conversationUser!!.baseUrl,
@@ -839,9 +785,9 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                     ),
                     ),
                     participant.sessionId
                     participant.sessionId
                 )
                 )
-                    .subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(object : Observer<GenericOverall> {
+                    ?.subscribeOn(Schedulers.io())
+                    ?.observeOn(AndroidSchedulers.mainThread())
+                    ?.subscribe(object : Observer<GenericOverall> {
                         override fun onSubscribe(d: Disposable) {
                         override fun onSubscribe(d: Disposable) {
                         }
                         }
 
 
@@ -858,7 +804,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                         }
                         }
                     })
                     })
             } else {
             } else {
-                ncApi.removeParticipantFromConversation(
+                ncApi?.removeParticipantFromConversation(
                     credentials,
                     credentials,
                     ApiUtils.getUrlForRemovingParticipantFromConversation(
                     ApiUtils.getUrlForRemovingParticipantFromConversation(
                         conversationUser!!.baseUrl,
                         conversationUser!!.baseUrl,
@@ -867,9 +813,9 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                     ),
                     ),
                     participant.userId
                     participant.userId
                 )
                 )
-                    .subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(object : Observer<GenericOverall> {
+                    ?.subscribeOn(Schedulers.io())
+                    ?.observeOn(AndroidSchedulers.mainThread())
+                    ?.subscribe(object : Observer<GenericOverall> {
                         override fun onSubscribe(d: Disposable) {
                         override fun onSubscribe(d: Disposable) {
                         }
                         }
 
 
@@ -904,7 +850,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
                 val items = mutableListOf(
                 val items = mutableListOf(
                     BasicListItemWithImage(
                     BasicListItemWithImage(
                         R.drawable.ic_lock_grey600_24px,
                         R.drawable.ic_lock_grey600_24px,
-                        context.getString(R.string.nc_attendee_pin, participant.attendeePin)
+                        context!!.getString(R.string.nc_attendee_pin, participant.attendeePin)
                     )
                     )
                 )
                 )
                 MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
                 MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
@@ -930,7 +876,7 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             val items = mutableListOf(
             val items = mutableListOf(
                 BasicListItemWithImage(
                 BasicListItemWithImage(
                     R.drawable.ic_delete_grey600_24dp,
                     R.drawable.ic_delete_grey600_24dp,
-                    context.getString(R.string.nc_remove_group_and_members)
+                    context!!.getString(R.string.nc_remove_group_and_members)
                 )
                 )
             )
             )
             MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
             MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
@@ -946,16 +892,16 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             return true
             return true
         }
         }
 
 
-        var items = mutableListOf(
+        val items = mutableListOf(
             BasicListItemWithImage(
             BasicListItemWithImage(
                 R.drawable.ic_lock_grey600_24px,
                 R.drawable.ic_lock_grey600_24px,
-                context.getString(R.string.nc_attendee_pin, participant.attendeePin)
+                context!!.getString(R.string.nc_attendee_pin, participant.attendeePin)
             ),
             ),
-            BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context.getString(R.string.nc_promote)),
-            BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context.getString(R.string.nc_demote)),
+            BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context!!.getString(R.string.nc_promote)),
+            BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context!!.getString(R.string.nc_demote)),
             BasicListItemWithImage(
             BasicListItemWithImage(
                 R.drawable.ic_delete_grey600_24dp,
                 R.drawable.ic_delete_grey600_24dp,
-                context.getString(R.string.nc_remove_participant)
+                context!!.getString(R.string.nc_remove_participant)
             )
             )
         )
         )
 
 
@@ -1025,7 +971,11 @@ class ConversationInfoController(args: Bundle) : BaseController(args), FlexibleA
             val rightIsGroup = right.model.actorType == GROUPS
             val rightIsGroup = right.model.actorType == GROUPS
             if (leftIsGroup != rightIsGroup) {
             if (leftIsGroup != rightIsGroup) {
                 // Groups below participants
                 // Groups below participants
-                return if (rightIsGroup) { -1 } else { 1 }
+                return if (rightIsGroup) {
+                    -1
+                } else {
+                    1
+                }
             }
             }
 
 
             if (left.isOnline && !right.isOnline) {
             if (left.isOnline && !right.isOnline) {

+ 6 - 4
app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java

@@ -79,6 +79,7 @@ import com.nextcloud.talk.jobs.AccountRemovalWorker;
 import com.nextcloud.talk.jobs.ContactAddressBookWorker;
 import com.nextcloud.talk.jobs.ContactAddressBookWorker;
 import com.nextcloud.talk.jobs.DeleteConversationWorker;
 import com.nextcloud.talk.jobs.DeleteConversationWorker;
 import com.nextcloud.talk.jobs.UploadAndShareFilesWorker;
 import com.nextcloud.talk.jobs.UploadAndShareFilesWorker;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.models.json.participants.Participant;
 import com.nextcloud.talk.models.json.participants.Participant;
@@ -280,13 +281,14 @@ public class ConversationsListController extends BaseController implements Searc
         currentUser = userUtils.getCurrentUser();
         currentUser = userUtils.getCurrentUser();
 
 
         if (currentUser != null) {
         if (currentUser != null) {
-            if (currentUser.isServerEOL()) {
+            if (CapabilitiesUtil.isServerEOL(currentUser)) {
                 showServerEOLDialog();
                 showServerEOLDialog();
                 return;
                 return;
             }
             }
 
 
             credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
             credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
-            shouldUseLastMessageLayout = currentUser.hasSpreedFeatureCapability("last-room-activity");
+            shouldUseLastMessageLayout = CapabilitiesUtil.hasSpreedFeatureCapability(currentUser,
+                                                                                     "last-room-activity");
             if (getActivity() != null && getActivity() instanceof MainActivity) {
             if (getActivity() != null && getActivity() instanceof MainActivity) {
                 loadUserAvatar(((MainActivity) getActivity()).binding.switchAccountButton);
                 loadUserAvatar(((MainActivity) getActivity()).binding.switchAccountButton);
             }
             }
@@ -489,7 +491,7 @@ public class ConversationsListController extends BaseController implements Searc
                         }
                         }
                     }
                     }
 
 
-                    if (currentUser.hasSpreedFeatureCapability("last-room-activity")) {
+                    if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "last-room-activity")) {
                         Collections.sort(callItems, (o1, o2) -> {
                         Collections.sort(callItems, (o1, o2) -> {
                             Conversation conversation1 = ((ConversationItem) o1).getModel();
                             Conversation conversation1 = ((ConversationItem) o1).getModel();
                             Conversation conversation2 = ((ConversationItem) o2).getModel();
                             Conversation conversation2 = ((ConversationItem) o2).getModel();
@@ -817,7 +819,7 @@ public class ConversationsListController extends BaseController implements Searc
         if (showShareToScreen) {
         if (showShareToScreen) {
             Log.d(TAG, "sharing to multiple rooms not yet implemented. onItemLongClick is ignored.");
             Log.d(TAG, "sharing to multiple rooms not yet implemented. onItemLongClick is ignored.");
 
 
-        } else if (currentUser.hasSpreedFeatureCapability("last-room-activity")) {
+        } else if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "last-room-activity")) {
             Object clickedItem = adapter.getItem(position);
             Object clickedItem = adapter.getItem(position);
             if (clickedItem != null) {
             if (clickedItem != null) {
                 Conversation conversation;
                 Conversation conversation;

+ 3 - 2
app/src/main/java/com/nextcloud/talk/controllers/ProfileController.java

@@ -53,6 +53,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.components.filebrowser.controllers.BrowserController;
 import com.nextcloud.talk.components.filebrowser.controllers.BrowserController;
 import com.nextcloud.talk.components.filebrowser.controllers.BrowserForAvatarController;
 import com.nextcloud.talk.components.filebrowser.controllers.BrowserForAvatarController;
 import com.nextcloud.talk.controllers.base.BaseController;
 import com.nextcloud.talk.controllers.base.BaseController;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.models.json.userprofile.Scope;
 import com.nextcloud.talk.models.json.userprofile.Scope;
@@ -170,7 +171,7 @@ public class ProfileController extends BaseController {
                     getActivity().findViewById(R.id.emptyList).setVisibility(View.GONE);
                     getActivity().findViewById(R.id.emptyList).setVisibility(View.GONE);
                     getActivity().findViewById(R.id.userinfo_list).setVisibility(View.VISIBLE);
                     getActivity().findViewById(R.id.userinfo_list).setVisibility(View.VISIBLE);
 
 
-                    if (currentUser.isAvatarEndpointAvailable()) {
+                    if (CapabilitiesUtil.isAvatarEndpointAvailable(currentUser)) {
                         // 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
                         getActivity().findViewById(R.id.avatar_buttons).setVisibility(View.VISIBLE);
                         getActivity().findViewById(R.id.avatar_buttons).setVisibility(View.VISIBLE);
                     }
                     }
@@ -345,7 +346,7 @@ public class ProfileController extends BaseController {
         }
         }
 
 
         // show edit button
         // show edit button
-        if (currentUser.canEditScopes()) {
+        if (CapabilitiesUtil.canEditScopes(currentUser)) {
             ncApi.getEditableUserProfileFields(ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
             ncApi.getEditableUserProfileFields(ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
                     ApiUtils.getUrlForUserFields(currentUser.getBaseUrl()))
                     ApiUtils.getUrlForUserFields(currentUser.getBaseUrl()))
                     .subscribeOn(Schedulers.io())
                     .subscribeOn(Schedulers.io())

+ 6 - 5
app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java

@@ -68,6 +68,7 @@ import com.nextcloud.talk.controllers.base.BaseController;
 import com.nextcloud.talk.jobs.AccountRemovalWorker;
 import com.nextcloud.talk.jobs.AccountRemovalWorker;
 import com.nextcloud.talk.jobs.ContactAddressBookWorker;
 import com.nextcloud.talk.jobs.ContactAddressBookWorker;
 import com.nextcloud.talk.models.RingtoneSettings;
 import com.nextcloud.talk.models.RingtoneSettings;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
 import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
@@ -317,7 +318,7 @@ public class SettingsController extends BaseController {
                     .popChangeHandler(new HorizontalChangeHandler()));
                     .popChangeHandler(new HorizontalChangeHandler()));
         });
         });
 
 
-        if (userUtils.getCurrentUser().isPhoneBookIntegrationAvailable()) {
+        if (CapabilitiesUtil.isPhoneBookIntegrationAvailable(userUtils.getCurrentUser())) {
             phoneBookIntegrationPreference.setVisibility(View.VISIBLE);
             phoneBookIntegrationPreference.setVisibility(View.VISIBLE);
         } else {
         } else {
             phoneBookIntegrationPreference.setVisibility(View.GONE);
             phoneBookIntegrationPreference.setVisibility(View.GONE);
@@ -456,8 +457,8 @@ public class SettingsController extends BaseController {
             ((Checkable) incognitoKeyboardSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getIsKeyboardIncognito());
             ((Checkable) incognitoKeyboardSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getIsKeyboardIncognito());
         }
         }
 
 
-        if (userUtils.getCurrentUser().isReadStatusAvailable()) {
-            ((Checkable) readPrivacyPreference.findViewById(R.id.mp_checkable)).setChecked(!currentUser.isReadStatusPrivate());
+        if (CapabilitiesUtil.isReadStatusAvailable(userUtils.getCurrentUser())) {
+            ((Checkable) readPrivacyPreference.findViewById(R.id.mp_checkable)).setChecked(!CapabilitiesUtil.isReadStatusPrivate(currentUser));
         } else {
         } else {
             readPrivacyPreference.setVisibility(View.GONE);
             readPrivacyPreference.setVisibility(View.GONE);
         }
         }
@@ -537,12 +538,12 @@ public class SettingsController extends BaseController {
 
 
             baseUrlTextView.setText(Uri.parse(currentUser.getBaseUrl()).getHost());
             baseUrlTextView.setText(Uri.parse(currentUser.getBaseUrl()).getHost());
 
 
-            if (currentUser.isServerEOL()) {
+            if (CapabilitiesUtil.isServerEOL(currentUser)) {
                 serverAgeTextView.setTextColor(ContextCompat.getColor(context, R.color.nc_darkRed));
                 serverAgeTextView.setTextColor(ContextCompat.getColor(context, R.color.nc_darkRed));
                 serverAgeTextView.setText(R.string.nc_settings_server_eol);
                 serverAgeTextView.setText(R.string.nc_settings_server_eol);
                 serverAgeIcon.setColorFilter(ContextCompat.getColor(context, R.color.nc_darkRed),
                 serverAgeIcon.setColorFilter(ContextCompat.getColor(context, R.color.nc_darkRed),
                                              PorterDuff.Mode.SRC_IN);
                                              PorterDuff.Mode.SRC_IN);
-            } else if (currentUser.isServerAlmostEOL()) {
+            } else if (CapabilitiesUtil.isServerAlmostEOL(currentUser)) {
                 serverAgeTextView.setTextColor(ContextCompat.getColor(context, R.color.nc_darkYellow));
                 serverAgeTextView.setTextColor(ContextCompat.getColor(context, R.color.nc_darkYellow));
                 serverAgeTextView.setText(R.string.nc_settings_server_almost_eol);
                 serverAgeTextView.setText(R.string.nc_settings_server_almost_eol);
                 serverAgeIcon.setColorFilter(ContextCompat.getColor(context, R.color.nc_darkYellow),
                 serverAgeIcon.setColorFilter(ContextCompat.getColor(context, R.color.nc_darkYellow),

+ 259 - 0
app/src/main/java/com/nextcloud/talk/controllers/base/NewBaseController.kt

@@ -0,0 +1,259 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author BlueLine Labs, Inc.
+ * @author Mario Danic
+ * Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
+ * Copyright (C) 2020 Mario Danic (mario@lovelyhq.com)
+ * Copyright (C) 2016 BlueLine Labs, Inc.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nextcloud.talk.controllers.base
+
+import android.animation.AnimatorInflater
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import androidx.annotation.LayoutRes
+import androidx.annotation.RequiresApi
+import androidx.appcompat.app.ActionBar
+import androidx.core.content.res.ResourcesCompat
+import autodagger.AutoInjector
+import com.bluelinelabs.conductor.Controller
+import com.bluelinelabs.conductor.ControllerChangeHandler
+import com.bluelinelabs.conductor.ControllerChangeType
+import com.google.android.material.appbar.AppBarLayout
+import com.nextcloud.talk.R
+import com.nextcloud.talk.activities.MainActivity
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.controllers.AccountVerificationController
+import com.nextcloud.talk.controllers.ServerSelectionController
+import com.nextcloud.talk.controllers.SwitchAccountController
+import com.nextcloud.talk.controllers.WebViewLoginController
+import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
+import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.preferences.AppPreferences
+import java.util.ArrayList
+import javax.inject.Inject
+import kotlin.jvm.internal.Intrinsics
+
+@AutoInjector(NextcloudTalkApplication::class)
+abstract class NewBaseController(@LayoutRes var layoutRes: Int, args: Bundle? = null) : Controller(args) {
+    enum class AppBarLayoutType {
+        TOOLBAR, SEARCH_BAR, EMPTY
+    }
+
+    @Inject
+    @JvmField
+    var appPreferences: AppPreferences? = null
+
+    @Inject
+    @JvmField
+    var context: Context? = null
+
+    protected open val title: String?
+        get() = null
+
+    protected val actionBar: ActionBar?
+        get() {
+            var actionBarProvider: ActionBarProvider? = null
+            if (this.activity is ActionBarProvider) {
+                try {
+                    actionBarProvider = this.activity as ActionBarProvider?
+                } catch (e: Exception) {
+                    Log.d(TAG, "Failed to fetch the action bar provider", e)
+                }
+            }
+            return actionBarProvider?.supportActionBar
+        }
+
+    init {
+        addLifecycleListener(object : LifecycleListener() {
+            override fun postCreateView(controller: Controller, view: View) {
+                onViewBound(view)
+                actionBar?.let { setTitle() }
+            }
+        })
+        cleanTempCertPreference()
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup,
+        savedViewState: Bundle?
+    ): View {
+        return inflater.inflate(layoutRes, container, false)
+    }
+
+    protected open fun onViewBound(view: View) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences!!.isKeyboardIncognito) {
+            disableKeyboardPersonalisedLearning(view as ViewGroup)
+            if (activity != null && activity is MainActivity) {
+                val activity = activity as MainActivity?
+                disableKeyboardPersonalisedLearning(activity!!.binding.appBar)
+            }
+        }
+    }
+
+    override fun onAttach(view: View) {
+        showSearchOrToolbar()
+        setTitle()
+        if (actionBar != null) {
+            actionBar!!.setDisplayHomeAsUpEnabled(parentController != null || router.backstackSize > 1)
+        }
+        super.onAttach(view)
+    }
+
+    protected fun showSearchOrToolbar() {
+        if (activity != null && activity is MainActivity) {
+            val showSearchBar = appBarLayoutType == AppBarLayoutType.SEARCH_BAR
+            val activity = activity as MainActivity?
+            if (appBarLayoutType == AppBarLayoutType.EMPTY) {
+                activity!!.binding.toolbar.visibility = View.GONE
+                activity.binding.searchToolbar.visibility = View.GONE
+            } else {
+                val layoutParams = activity!!.binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
+                if (showSearchBar) {
+                    activity.binding.searchToolbar.visibility = View.VISIBLE
+                    activity.binding.searchText.hint = searchHint
+                    activity.binding.toolbar.visibility = View.GONE
+                    //layoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
+                    layoutParams.scrollFlags = 0
+                    activity.binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
+                        activity.binding.appBar.context,
+                        R.animator.appbar_elevation_off
+                    )
+                } else {
+                    activity.binding.searchToolbar.visibility = View.GONE
+                    activity.binding.toolbar.visibility = View.VISIBLE
+                    layoutParams.scrollFlags = 0
+                    activity.binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
+                        activity.binding.appBar.context,
+                        R.animator.appbar_elevation_on
+                    )
+                }
+                activity.binding.searchToolbar.layoutParams = layoutParams
+                if (resources != null) {
+                    if (showSearchBar) {
+                        DisplayUtils.applyColorToStatusBar(
+                            activity, ResourcesCompat.getColor(
+                                resources!!,
+                                R.color.bg_default, null
+                            )
+                        )
+                    } else {
+                        DisplayUtils.applyColorToStatusBar(
+                            activity, ResourcesCompat.getColor(
+                                resources!!,
+                                R.color.appbar, null
+                            )
+                        )
+                    }
+                }
+            }
+            if (resources != null) {
+                DisplayUtils.applyColorToNavigationBar(
+                    activity.window,
+                    ResourcesCompat.getColor(resources!!, R.color.bg_default, null)
+                )
+            }
+        }
+    }
+
+    override fun onDetach(view: View) {
+        super.onDetach(view)
+        val imm = context!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        imm.hideSoftInputFromWindow(view.windowToken, 0)
+    }
+
+    protected fun setTitle() {
+        if (title != null && actionBar != null) {
+            run {
+                var parentController = parentController
+                while (parentController != null) {
+                    if (parentController is BaseController && parentController.title != null) {
+                        return
+                    }
+                    parentController = parentController.parentController
+                }
+            }
+            actionBar!!.title = title
+        }
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        if (item.itemId == android.R.id.home) {
+            router.popCurrentController()
+            return true
+        }
+        return super.onOptionsItemSelected(item)
+    }
+
+    override fun onChangeStarted(changeHandler: ControllerChangeHandler, changeType: ControllerChangeType) {
+        super.onChangeStarted(changeHandler, changeType)
+        if (changeType.isEnter && actionBar != null) {
+            configureMenu(actionBar!!)
+        }
+    }
+
+    fun configureMenu(toolbar: ActionBar) {
+        Intrinsics.checkNotNullParameter(toolbar, "toolbar")
+    }
+
+    private fun cleanTempCertPreference() {
+        sharedApplication!!.componentApplication.inject(this)
+        val temporaryClassNames: MutableList<String> = ArrayList()
+        temporaryClassNames.add(ServerSelectionController::class.java.name)
+        temporaryClassNames.add(AccountVerificationController::class.java.name)
+        temporaryClassNames.add(WebViewLoginController::class.java.name)
+        temporaryClassNames.add(SwitchAccountController::class.java.name)
+        if (!temporaryClassNames.contains(javaClass.name)) {
+            appPreferences!!.removeTemporaryClientCertAlias()
+        }
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.O)
+    private fun disableKeyboardPersonalisedLearning(viewGroup: ViewGroup) {
+        var view: View?
+        var editText: EditText
+        for (i in 0 until viewGroup.childCount) {
+            view = viewGroup.getChildAt(i)
+            if (view is EditText) {
+                editText = view
+                editText.imeOptions = editText.imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
+            } else if (view is ViewGroup) {
+                disableKeyboardPersonalisedLearning(view)
+            }
+        }
+    }
+
+    val appBarLayoutType: AppBarLayoutType
+        get() = AppBarLayoutType.TOOLBAR
+    val searchHint: String
+        get() = context!!.getString(R.string.appbar_search_in, context!!.getString(R.string.nc_app_name))
+
+    companion object {
+        private val TAG = BaseController::class.java.simpleName
+    }
+}

+ 2 - 1
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java

@@ -48,6 +48,7 @@ import com.nextcloud.talk.controllers.base.BaseController;
 import com.nextcloud.talk.events.BottomSheetLockEvent;
 import com.nextcloud.talk.events.BottomSheetLockEvent;
 import com.nextcloud.talk.interfaces.ConversationMenuInterface;
 import com.nextcloud.talk.interfaces.ConversationMenuInterface;
 import com.nextcloud.talk.jobs.LeaveConversationWorker;
 import com.nextcloud.talk.jobs.LeaveConversationWorker;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.DisplayUtils;
@@ -151,7 +152,7 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
 
 
             if (conversation.isFavorite()) {
             if (conversation.isFavorite()) {
                 menuItems.add(new MenuItem(getResources().getString(R.string.nc_remove_from_favorites), 97, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_border_black_24dp, R.color.grey_600)));
                 menuItems.add(new MenuItem(getResources().getString(R.string.nc_remove_from_favorites), 97, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_border_black_24dp, R.color.grey_600)));
-            } else if (currentUser.hasSpreedFeatureCapability("favorites")) {
+            } else if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "favorites")) {
                 menuItems.add(new MenuItem(getResources().getString(R.string.nc_add_to_favorites)
                 menuItems.add(new MenuItem(getResources().getString(R.string.nc_add_to_favorites)
                         , 98, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_black_24dp, R.color.grey_600)));
                         , 98, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_black_24dp, R.color.grey_600)));
             }
             }

+ 6 - 4
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java

@@ -34,8 +34,6 @@ import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.TextView;
 
 
-import androidx.annotation.NonNull;
-
 import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
 import com.bluelinelabs.logansquare.LoganSquare;
 import com.bluelinelabs.logansquare.LoganSquare;
@@ -46,6 +44,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.controllers.base.BaseController;
 import com.nextcloud.talk.controllers.base.BaseController;
 import com.nextcloud.talk.events.BottomSheetLockEvent;
 import com.nextcloud.talk.events.BottomSheetLockEvent;
 import com.nextcloud.talk.models.RetrofitBucket;
 import com.nextcloud.talk.models.RetrofitBucket;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.capabilities.Capabilities;
 import com.nextcloud.talk.models.json.capabilities.Capabilities;
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
@@ -69,6 +68,7 @@ import java.util.ArrayList;
 
 
 import javax.inject.Inject;
 import javax.inject.Inject;
 
 
+import androidx.annotation.NonNull;
 import autodagger.AutoInjector;
 import autodagger.AutoInjector;
 import butterknife.BindView;
 import butterknife.BindView;
 import io.reactivex.Observer;
 import io.reactivex.Observer;
@@ -581,8 +581,10 @@ public class OperationsMenuController extends BaseController {
 
 
         int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {4, 1});
         int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {4, 1});
 
 
-        if (localInvitedUsers.size() > 0 || (localInvitedGroups.size() > 0 && currentUser.hasSpreedFeatureCapability("invite-groups-and-mails"))) {
-            if ((localInvitedGroups.size() > 0 && currentUser.hasSpreedFeatureCapability("invite-groups-and-mails"))) {
+        if (localInvitedUsers.size() > 0 || (localInvitedGroups.size() > 0 &&
+                CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails"))) {
+            if ((localInvitedGroups.size() > 0 &&
+                    CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails"))) {
                 for (int i = 0; i < localInvitedGroups.size(); i++) {
                 for (int i = 0; i < localInvitedGroups.size(); i++) {
                     final String groupId = localInvitedGroups.get(i);
                     final String groupId = localInvitedGroups.get(i);
                     retrofitBucket = ApiUtils.getRetrofitBucketForAddParticipantWithSource(
                     retrofitBucket = ApiUtils.getRetrofitBucketForAddParticipantWithSource(

+ 49 - 0
app/src/main/java/com/nextcloud/talk/controllers/util/ControllerViewBindingDelegate.kt

@@ -0,0 +1,49 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author BlueLine Labs, Inc.
+ * Copyright (C) 2016 BlueLine Labs, Inc.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nextcloud.talk.controllers.util
+
+import android.view.View
+import androidx.lifecycle.LifecycleObserver
+import androidx.viewbinding.ViewBinding
+import com.bluelinelabs.conductor.Controller
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+fun <T : ViewBinding> Controller.viewBinding(bindingFactory: (View) -> T) =
+    ControllerViewBindingDelegate(this, bindingFactory)
+
+class ControllerViewBindingDelegate<T : ViewBinding>(
+    controller: Controller,
+    private val viewBinder: (View) -> T
+) : ReadOnlyProperty<Controller, T>, LifecycleObserver {
+
+    private var binding: T? = null
+
+    init {
+        controller.addLifecycleListener(object : Controller.LifecycleListener() {
+            override fun postDestroyView(controller: Controller) {
+                binding = null
+            }
+        })
+    }
+
+    override fun getValue(thisRef: Controller, property: KProperty<*>): T {
+        return binding ?: viewBinder(thisRef.view!!).also { binding = it }
+    }
+}

+ 50 - 84
app/src/main/java/com/nextcloud/talk/models/database/User.java → app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java

@@ -1,7 +1,9 @@
 /*
 /*
  * Nextcloud Talk application
  * Nextcloud Talk application
  *
  *
+ * @author Andy Scherzinger
  * @author Mario Danic
  * @author Mario Danic
+ * Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
  * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
@@ -19,55 +21,24 @@
  */
  */
 package com.nextcloud.talk.models.database;
 package com.nextcloud.talk.models.database;
 
 
-import android.os.Parcelable;
 import android.util.Log;
 import android.util.Log;
 
 
 import com.bluelinelabs.logansquare.LoganSquare;
 import com.bluelinelabs.logansquare.LoganSquare;
 import com.nextcloud.talk.models.json.capabilities.Capabilities;
 import com.nextcloud.talk.models.json.capabilities.Capabilities;
 
 
 import java.io.IOException;
 import java.io.IOException;
-import java.io.Serializable;
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.Map;
 
 
-import io.requery.Entity;
-import io.requery.Generated;
-import io.requery.Key;
-import io.requery.Persistable;
+import androidx.annotation.Nullable;
 
 
-@Entity
-public interface User extends Parcelable, Persistable, Serializable {
-    static final String TAG = "UserEntity";
+public abstract class CapabilitiesUtil {
+    private static final String TAG = CapabilitiesUtil.class.getSimpleName();
 
 
-    @Key
-    @Generated
-    long getId();
-
-    String getUserId();
-
-    String getUsername();
-
-    String getBaseUrl();
-
-    String getToken();
-
-    String getDisplayName();
-
-    String getPushConfigurationState();
-
-    String getCapabilities();
-
-    String getClientCertificate();
-
-    String getExternalSignalingServer();
-
-    boolean getCurrent();
-
-    boolean getScheduledForDeletion();
-
-    default boolean hasNotificationsCapability(String capabilityName) {
-        if (getCapabilities() != null) {
+    public static boolean hasNotificationsCapability(@Nullable UserEntity user, String capabilityName) {
+        if (user != null && user.getCapabilities() != null) {
             try {
             try {
-                Capabilities capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 if (capabilities.getNotificationsCapability() != null && capabilities.getNotificationsCapability().getFeatures() != null) {
                 if (capabilities.getNotificationsCapability() != null && capabilities.getNotificationsCapability().getFeatures() != null) {
                     return capabilities.getSpreedCapability().getFeatures().contains(capabilityName);
                     return capabilities.getSpreedCapability().getFeatures().contains(capabilityName);
                 }
                 }
@@ -78,10 +49,10 @@ public interface User extends Parcelable, Persistable, Serializable {
         return false;
         return false;
     }
     }
 
 
-    default boolean hasExternalCapability(String capabilityName) {
-        if (getCapabilities() != null) {
+    public static boolean hasExternalCapability(@Nullable UserEntity user, String capabilityName) {
+        if (user != null && user.getCapabilities() != null) {
             try {
             try {
-                Capabilities capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 if (capabilities.getExternalCapability() != null && capabilities.getExternalCapability().containsKey("v1")) {
                 if (capabilities.getExternalCapability() != null && capabilities.getExternalCapability().containsKey("v1")) {
                     return capabilities.getExternalCapability().get("v1").contains("capabilityName");
                     return capabilities.getExternalCapability().get("v1").contains("capabilityName");
                 }
                 }
@@ -92,20 +63,20 @@ public interface User extends Parcelable, Persistable, Serializable {
         return false;
         return false;
     }
     }
 
 
-    default boolean isServerEOL() {
+    public static boolean isServerEOL(@Nullable UserEntity user) {
         // Capability is available since Talk 4 => Nextcloud 14 => Autmn 2018
         // Capability is available since Talk 4 => Nextcloud 14 => Autmn 2018
-        return !hasSpreedFeatureCapability("no-ping");
+        return !hasSpreedFeatureCapability(user, "no-ping");
     }
     }
 
 
-    default boolean isServerAlmostEOL() {
+    public static boolean isServerAlmostEOL(@Nullable UserEntity user) {
         // Capability is available since Talk 8 => Nextcloud 18 => January 2020
         // Capability is available since Talk 8 => Nextcloud 18 => January 2020
-        return !hasSpreedFeatureCapability("chat-replies");
+        return !hasSpreedFeatureCapability(user, "chat-replies");
     }
     }
 
 
-    default boolean hasSpreedFeatureCapability(String capabilityName) {
-        if (getCapabilities() != null) {
+    public static boolean hasSpreedFeatureCapability(@Nullable UserEntity user, String capabilityName) {
+        if (user != null && user.getCapabilities() != null) {
             try {
             try {
-                Capabilities capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 if (capabilities != null && capabilities.getSpreedCapability() != null &&
                 if (capabilities != null && capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability().getFeatures() != null) {
                         capabilities.getSpreedCapability().getFeatures() != null) {
                     return capabilities.getSpreedCapability().getFeatures().contains(capabilityName);
                     return capabilities.getSpreedCapability().getFeatures().contains(capabilityName);
@@ -117,11 +88,10 @@ public interface User extends Parcelable, Persistable, Serializable {
         return false;
         return false;
     }
     }
 
 
-    default int getMessageMaxLength() {
-        if (getCapabilities() != null) {
-            Capabilities capabilities = null;
+    public static Integer getMessageMaxLength(@Nullable UserEntity user) {
+        if (user != null && user.getCapabilities() != null) {
             try {
             try {
-                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 if (capabilities != null && capabilities.getSpreedCapability() != null && capabilities.getSpreedCapability().getConfig() != null
                 if (capabilities != null && capabilities.getSpreedCapability() != null && capabilities.getSpreedCapability().getConfig() != null
                         && capabilities.getSpreedCapability().getConfig().containsKey("chat")) {
                         && capabilities.getSpreedCapability().getConfig().containsKey("chat")) {
                     HashMap<String, String> chatConfigHashMap = capabilities.getSpreedCapability().getConfig().get("chat");
                     HashMap<String, String> chatConfigHashMap = capabilities.getSpreedCapability().getConfig().get("chat");
@@ -135,52 +105,49 @@ public interface User extends Parcelable, Persistable, Serializable {
                     }
                     }
                 }
                 }
             } catch (IOException e) {
             } catch (IOException e) {
-                e.printStackTrace();
+                Log.e(TAG, "Failed to get capabilities for the user");
             }
             }
         }
         }
         return 1000;
         return 1000;
     }
     }
 
 
-    default boolean isPhoneBookIntegrationAvailable() {
-        if (getCapabilities() != null) {
-            Capabilities capabilities;
+    public static boolean isPhoneBookIntegrationAvailable(@Nullable UserEntity user) {
+        if (user != null && user.getCapabilities() != null) {
             try {
             try {
-                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 return capabilities != null &&
                 return capabilities != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability().getFeatures() != null &&
                         capabilities.getSpreedCapability().getFeatures() != null &&
                         capabilities.getSpreedCapability().getFeatures().contains("phonebook-search");
                         capabilities.getSpreedCapability().getFeatures().contains("phonebook-search");
             } catch (IOException e) {
             } catch (IOException e) {
-                e.printStackTrace();
+                Log.e(TAG, "Failed to get capabilities for the user");
             }
             }
         }
         }
         return false;
         return false;
     }
     }
 
 
-    default boolean isReadStatusAvailable() {
-        if (getCapabilities() != null) {
-            Capabilities capabilities;
+    public static boolean isReadStatusAvailable(@Nullable UserEntity user) {
+        if (user != null && user.getCapabilities() != null) {
             try {
             try {
-                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 if (capabilities != null &&
                 if (capabilities != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability().getConfig() != null &&
                         capabilities.getSpreedCapability().getConfig() != null &&
                         capabilities.getSpreedCapability().getConfig().containsKey("chat")) {
                         capabilities.getSpreedCapability().getConfig().containsKey("chat")) {
-                    HashMap<String, String> map = capabilities.getSpreedCapability().getConfig().get("chat");
+                    Map<String, String> map = capabilities.getSpreedCapability().getConfig().get("chat");
                     return map != null && map.containsKey("read-privacy");
                     return map != null && map.containsKey("read-privacy");
                 }
                 }
             } catch (IOException e) {
             } catch (IOException e) {
-                e.printStackTrace();
+                Log.e(TAG, "Failed to get capabilities for the user");
             }
             }
         }
         }
         return false;
         return false;
     }
     }
 
 
-    default boolean isReadStatusPrivate() {
-        if (getCapabilities() != null) {
-            Capabilities capabilities;
+    public static boolean isReadStatusPrivate(@Nullable UserEntity user) {
+        if (user != null && user.getCapabilities() != null) {
             try {
             try {
-                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 if (capabilities != null &&
                 if (capabilities != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability().getConfig() != null &&
                         capabilities.getSpreedCapability().getConfig() != null &&
@@ -191,17 +158,16 @@ public interface User extends Parcelable, Persistable, Serializable {
                     }
                     }
                 }
                 }
             } catch (IOException e) {
             } catch (IOException e) {
-                e.printStackTrace();
+                Log.e(TAG, "Failed to get capabilities for the user");
             }
             }
         }
         }
         return false;
         return false;
     }
     }
 
 
-    default String getAttachmentFolder() {
-        if (getCapabilities() != null) {
-            Capabilities capabilities;
+    public static String getAttachmentFolder(@Nullable UserEntity user) {
+        if (user != null && user.getCapabilities() != null) {
             try {
             try {
-                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                Capabilities capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 if (capabilities != null &&
                 if (capabilities != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability().getConfig() != null &&
                         capabilities.getSpreedCapability().getConfig() != null &&
@@ -218,11 +184,11 @@ public interface User extends Parcelable, Persistable, Serializable {
         return "/Talk";
         return "/Talk";
     }
     }
 
 
-    default String getServerName() {
-        if (getCapabilities() != null) {
+    public static String getServerName(@Nullable UserEntity user) {
+        if (user != null && user.getCapabilities() != null) {
             Capabilities capabilities;
             Capabilities capabilities;
             try {
             try {
-                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 if (capabilities != null && capabilities.getThemingCapability() != null) {
                 if (capabilities != null && capabilities.getThemingCapability() != null) {
                     return capabilities.getThemingCapability().getName();
                     return capabilities.getThemingCapability().getName();
                 }
                 }
@@ -234,33 +200,33 @@ public interface User extends Parcelable, Persistable, Serializable {
     }
     }
 
 
     // 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
-    default boolean isAvatarEndpointAvailable() {
-        if (getCapabilities() != null) {
+    public static boolean isAvatarEndpointAvailable(@Nullable UserEntity user) {
+        if (user != null && user.getCapabilities() != null) {
             Capabilities capabilities;
             Capabilities capabilities;
             try {
             try {
-                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 return (capabilities != null &&
                 return (capabilities != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability() != null &&
                         capabilities.getSpreedCapability().getFeatures() != null &&
                         capabilities.getSpreedCapability().getFeatures() != null &&
                         capabilities.getSpreedCapability().getFeatures().contains("temp-user-avatar-api"));
                         capabilities.getSpreedCapability().getFeatures().contains("temp-user-avatar-api"));
             } catch (IOException e) {
             } catch (IOException e) {
-                e.printStackTrace();
+                Log.e("User.java", "Failed to get server name", e);
             }
             }
         }
         }
         return false;
         return false;
     }
     }
 
 
-    default boolean canEditScopes() {
-        if (getCapabilities() != null) {
+    public static boolean canEditScopes(@Nullable UserEntity user) {
+        if (user != null && user.getCapabilities() != null) {
             Capabilities capabilities;
             Capabilities capabilities;
             try {
             try {
-                capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
+                capabilities = LoganSquare.parse(user.getCapabilities(), Capabilities.class);
                 return (capabilities != null &&
                 return (capabilities != null &&
                         capabilities.getProvisioningCapability() != null &&
                         capabilities.getProvisioningCapability() != null &&
                         capabilities.getProvisioningCapability().getAccountPropertyScopesVersion() != null &&
                         capabilities.getProvisioningCapability().getAccountPropertyScopesVersion() != null &&
                         capabilities.getProvisioningCapability().getAccountPropertyScopesVersion() > 1);
                         capabilities.getProvisioningCapability().getAccountPropertyScopesVersion() > 1);
             } catch (IOException e) {
             } catch (IOException e) {
-                e.printStackTrace();
+                Log.e("User.java", "Failed to get server name", e);
             }
             }
         }
         }
         return false;
         return false;

+ 53 - 0
app/src/main/java/com/nextcloud/talk/models/database/User.kt

@@ -0,0 +1,53 @@
+/*
+ * 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.database
+
+import android.os.Parcelable
+import android.util.Log
+import com.bluelinelabs.logansquare.LoganSquare
+import com.nextcloud.talk.models.json.capabilities.Capabilities
+import io.requery.Entity
+import io.requery.Generated
+import io.requery.Key
+import io.requery.Persistable
+import java.io.IOException
+import java.io.Serializable
+
+@Entity
+interface User : Parcelable, Persistable, Serializable {
+    @get:Generated
+    @get:Key
+    val id: Long
+    val userId: String?
+    val username: String?
+    val baseUrl: String?
+    val token: String?
+    val displayName: String?
+    val pushConfigurationState: String?
+    var capabilities: String?
+    val clientCertificate: String?
+    val externalSignalingServer: String?
+    val current: Boolean
+    val scheduledForDeletion: Boolean
+
+    companion object {
+        const val TAG = "UserEntity"
+    }
+}

+ 3 - 1
app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java

@@ -22,6 +22,7 @@ package com.nextcloud.talk.models.json.conversations;
 
 
 import com.bluelinelabs.logansquare.annotation.JsonField;
 import com.bluelinelabs.logansquare.annotation.JsonField;
 import com.bluelinelabs.logansquare.annotation.JsonObject;
 import com.bluelinelabs.logansquare.annotation.JsonObject;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.chat.ChatMessage;
 import com.nextcloud.talk.models.json.chat.ChatMessage;
 import com.nextcloud.talk.models.json.converters.EnumLobbyStateConverter;
 import com.nextcloud.talk.models.json.converters.EnumLobbyStateConverter;
@@ -108,7 +109,8 @@ public class Conversation {
     }
     }
 
 
     private boolean isLockedOneToOne(UserEntity conversationUser) {
     private boolean isLockedOneToOne(UserEntity conversationUser) {
-        return (getType() == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL && conversationUser.hasSpreedFeatureCapability("locked-one-to-one-rooms"));
+        return (getType() == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
+                CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms"));
     }
     }
 
 
     public boolean canModerate(UserEntity conversationUser) {
     public boolean canModerate(UserEntity conversationUser) {

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

@@ -31,6 +31,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
 import com.nextcloud.talk.R
 import com.nextcloud.talk.R
 import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
 import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
 import com.nextcloud.talk.controllers.ChatController
 import com.nextcloud.talk.controllers.ChatController
+import com.nextcloud.talk.models.database.CapabilitiesUtil
 
 
 class AttachmentDialog(val activity: Activity, var chatController: ChatController) : BottomSheetDialog(activity) {
 class AttachmentDialog(val activity: Activity, var chatController: ChatController) : BottomSheetDialog(activity) {
 
 
@@ -51,7 +52,7 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
         window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
         window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
         unbinder = ButterKnife.bind(this, view)
         unbinder = ButterKnife.bind(this, view)
 
 
-        var serverName = chatController.conversationUser?.serverName
+        var serverName = CapabilitiesUtil.getServerName(chatController.conversationUser)
         attachFromCloud?.text = chatController.resources?.let {
         attachFromCloud?.text = chatController.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)

+ 13 - 11
app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java

@@ -27,6 +27,7 @@ import com.nextcloud.talk.BuildConfig;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.models.RetrofitBucket;
 import com.nextcloud.talk.models.RetrofitBucket;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 
 
 import java.util.HashMap;
 import java.util.HashMap;
@@ -115,7 +116,7 @@ public class ApiUtils {
         return getConversationApiVersion(capabilities, versions);
         return getConversationApiVersion(capabilities, versions);
     }
     }
 
 
-    public static int getConversationApiVersion(UserEntity capabilities, int[] versions) throws NoSupportedApiException {
+    public static int getConversationApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
         boolean hasApiV4 = false;
         boolean hasApiV4 = false;
         for (int version : versions) {
         for (int version : versions) {
             hasApiV4 |= version == 4;
             hasApiV4 |= version == 4;
@@ -127,16 +128,17 @@ public class ApiUtils {
         }
         }
 
 
         for (int version : versions) {
         for (int version : versions) {
-            if (capabilities.hasSpreedFeatureCapability("conversation-v" + version)) {
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation-v" + version)) {
                 return version;
                 return version;
             }
             }
 
 
             // Fallback for old API versions
             // Fallback for old API versions
             if ((version == 1 || version == 2)) {
             if ((version == 1 || version == 2)) {
-                if (capabilities.hasSpreedFeatureCapability("conversation-v2")) {
+                if (CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation-v2")) {
                     return version;
                     return version;
                 }
                 }
-                if (version == 1  && capabilities.hasSpreedFeatureCapability("conversation")) {
+                if (version == 1  &&
+                        CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation")) {
                     return version;
                     return version;
                 }
                 }
             }
             }
@@ -144,20 +146,20 @@ public class ApiUtils {
         throw new NoSupportedApiException();
         throw new NoSupportedApiException();
     }
     }
 
 
-    public static int getSignalingApiVersion(UserEntity capabilities, int[] versions) throws NoSupportedApiException {
+    public static int getSignalingApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
         for (int version : versions) {
         for (int version : versions) {
-            if (capabilities.hasSpreedFeatureCapability("signaling-v" + version)) {
+            CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v" + version)) {
                 return version;
                 return version;
             }
             }
 
 
             if (version == 2 &&
             if (version == 2 &&
-                    capabilities.hasSpreedFeatureCapability("sip-support") &&
-                    !capabilities.hasSpreedFeatureCapability("signaling-v3")) {
+                    CapabilitiesUtil.hasSpreedFeatureCapability(user, "sip-support") &&
+                    !CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v3")) {
                 return version;
                 return version;
             }
             }
 
 
             if (version == 1 &&
             if (version == 1 &&
-                    !capabilities.hasSpreedFeatureCapability("signaling-v3")) {
+                    !CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v3")) {
                 // Has no capability, we just assume it is always there when there is no v3 or later
                 // Has no capability, we just assume it is always there when there is no v3 or later
                 return version;
                 return version;
             }
             }
@@ -165,9 +167,9 @@ public class ApiUtils {
         throw new NoSupportedApiException();
         throw new NoSupportedApiException();
     }
     }
 
 
-    public static int getChatApiVersion(UserEntity capabilities, int[] versions) throws NoSupportedApiException {
+    public static int getChatApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
         for (int version : versions) {
         for (int version : versions) {
-            if (version == 1 && capabilities.hasSpreedFeatureCapability("chat-v2")) {
+            if (version == 1 && CapabilitiesUtil.hasSpreedFeatureCapability(user, "chat-v2")) {
                 // Do not question that chat-v2 capability shows the availability of api/v1/ endpoint *see no evil*
                 // Do not question that chat-v2 capability shows the availability of api/v1/ endpoint *see no evil*
                 return version;
                 return version;
             }
             }

+ 2 - 1
app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.java

@@ -26,6 +26,7 @@ import autodagger.AutoInjector;
 import com.nextcloud.talk.api.NcApi;
 import com.nextcloud.talk.api.NcApi;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.models.database.ArbitraryStorageEntity;
 import com.nextcloud.talk.models.database.ArbitraryStorageEntity;
+import com.nextcloud.talk.models.database.CapabilitiesUtil;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.database.UserEntity;
 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;
@@ -76,7 +77,7 @@ public class DatabaseStorageModule implements StorageModule {
         if (!key.equals("message_notification_level")) {
         if (!key.equals("message_notification_level")) {
             arbitraryStorageUtils.storeStorageSetting(accountIdentifier, key, value, conversationToken);
             arbitraryStorageUtils.storeStorageSetting(accountIdentifier, key, value, conversationToken);
         } else {
         } else {
-            if (conversationUser.hasSpreedFeatureCapability("notification-levels")) {
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "notification-levels")) {
                 if (!TextUtils.isEmpty(messageNotificationLevel) && !messageNotificationLevel.equals(value)) {
                 if (!TextUtils.isEmpty(messageNotificationLevel) && !messageNotificationLevel.equals(value)) {
                     int intValue;
                     int intValue;
                     switch (value) {
                     switch (value) {

+ 21 - 12
app/src/main/res/layout/controller_conversation_info.xml

@@ -128,7 +128,7 @@
                 android:id="@+id/participants_list_category"
                 android:id="@+id/participants_list_category"
                 android:layout_width="match_parent"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_below="@+id/webinar_settings"
+                android:layout_below="@+id/settings"
                 android:visibility="gone"
                 android:visibility="gone"
                 apc:cardBackgroundColor="@color/bg_default"
                 apc:cardBackgroundColor="@color/bg_default"
                 apc:cardElevation="0dp"
                 apc:cardElevation="0dp"
@@ -180,21 +180,30 @@
 
 
             </com.yarolegovich.mp.MaterialPreferenceCategory>
             </com.yarolegovich.mp.MaterialPreferenceCategory>
 
 
-            <include
-                layout="@layout/notification_settings_item"
+            <LinearLayout
+                android:id="@+id/settings"
                 android:layout_width="match_parent"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_below="@id/otherRoomOptions"
                 android:layout_below="@id/otherRoomOptions"
-                android:visibility="gone"
-                tools:visibility="gone" />
+                android:orientation="vertical">
 
 
-            <include
-                layout="@layout/webinar_info_item"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_below="@id/notification_settings"
-                android:visibility="gone"
-                tools:visibility="visible" />
+                <include
+                    android:id="@+id/notification_settings_view"
+                    layout="@layout/notification_settings_item"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone"
+                    tools:visibility="gone" />
+
+                <include
+                    android:id="@+id/webinar_info_view"
+                    layout="@layout/webinar_info_item"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone"
+                    tools:visibility="visible" />
+
+            </LinearLayout>
         </RelativeLayout>
         </RelativeLayout>
     </ScrollView>
     </ScrollView>
 </RelativeLayout>
 </RelativeLayout>