Browse Source

create new capabilities util add license header and format kotlin code

Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Andy Scherzinger 2 years ago
parent
commit
5ffa3c44fd

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

@@ -65,7 +65,6 @@ import io.reactivex.schedulers.Schedulers
 import io.requery.Persistable
 import io.requery.android.sqlcipher.SqlCipherDatabaseSource
 import io.requery.reactivex.ReactiveEntityStore
-import kotlinx.coroutines.DelicateCoroutinesApi
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import org.parceler.Parcels

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

@@ -69,7 +69,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppT
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
 import com.nextcloud.talk.controllers.base.NewBaseController
 import com.nextcloud.talk.controllers.util.viewBinding
-import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
 import com.nextcloud.talk.data.user.UsersRepository
 import com.nextcloud.talk.data.user.model.UserNgEntity
 import com.nextcloud.talk.databinding.ControllerSettingsBinding
@@ -89,6 +88,7 @@ import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
 import com.nextcloud.talk.utils.NotificationUtils.getMessageRingtoneUri
 import com.nextcloud.talk.utils.SecurityUtils
 import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ARE_CALL_SOUNDS
+import com.nextcloud.talk.utils.database.user.CapabilitiesNgUtil
 import com.nextcloud.talk.utils.database.user.UserUtils
 import com.nextcloud.talk.utils.preferences.MagicUserInputModule
 import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
@@ -103,7 +103,6 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import okhttp3.RequestBody
 import java.net.URI
 import java.net.URISyntaxException
-import java.util.ArrayList
 import java.util.Arrays
 import java.util.Locale
 import javax.inject.Inject
@@ -190,7 +189,7 @@ class SettingsController : NewBaseController(R.layout.controller_settings) {
     }
 
     private fun setupPhoneBookIntegration() {
-        if (CapabilitiesUtil.isPhoneBookIntegrationAvailable(userRepository.getActiveUser())) {
+        if (CapabilitiesNgUtil.isPhoneBookIntegrationAvailable(userRepository.getActiveUser())) {
             binding.settingsPhoneBookIntegration.visibility = View.VISIBLE
         } else {
             binding.settingsPhoneBookIntegration.visibility = View.GONE
@@ -645,7 +644,7 @@ class SettingsController : NewBaseController(R.layout.controller_settings) {
 
     private fun setupServerAgeWarning() {
         when {
-            CapabilitiesUtil.isServerEOL(currentUser) -> {
+            CapabilitiesNgUtil.isServerEOL(currentUser) -> {
                 binding.serverAgeWarningText.setTextColor(ContextCompat.getColor((context)!!, R.color.nc_darkRed))
                 binding.serverAgeWarningText.setText(R.string.nc_settings_server_eol)
                 binding.serverAgeWarningIcon.setColorFilter(
@@ -653,7 +652,7 @@ class SettingsController : NewBaseController(R.layout.controller_settings) {
                     PorterDuff.Mode.SRC_IN
                 )
             }
-            CapabilitiesUtil.isServerAlmostEOL(currentUser) -> {
+            CapabilitiesNgUtil.isServerAlmostEOL(currentUser) -> {
                 binding.serverAgeWarningText.setTextColor(
                     ContextCompat.getColor((context)!!, R.color.nc_darkYellow)
                 )
@@ -685,7 +684,7 @@ class SettingsController : NewBaseController(R.layout.controller_settings) {
 
         if (CapabilitiesUtil.isReadStatusAvailable(userUtils.currentUser)) {
             (binding.settingsReadPrivacy.findViewById<View>(R.id.mp_checkable) as Checkable).isChecked =
-                !CapabilitiesUtil.isReadStatusPrivate(currentUser)
+                !CapabilitiesNgUtil.isReadStatusPrivate(currentUser)
         } else {
             binding.settingsReadPrivacy.visibility = View.GONE
         }

+ 4 - 2
app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt

@@ -2,6 +2,8 @@
  * Nextcloud Talk application
  *
  * @author Álvaro Brey
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
  * Copyright (C) 2022 Álvaro Brey
  * Copyright (C) 2022 Nextcloud GmbH
  *
@@ -57,12 +59,12 @@ class RepositoryModule {
     }
 
     @Provides
-    fun provideUsersRepository(database : TalkDatabase): UsersRepository {
+    fun provideUsersRepository(database: TalkDatabase): UsersRepository {
         return UsersRepositoryImpl(database.usersDao())
     }
 
     @Provides
-    fun provideArbitraryStoragesRepository(database : TalkDatabase): ArbitraryStoragesRepository {
+    fun provideArbitraryStoragesRepository(database: TalkDatabase): ArbitraryStoragesRepository {
         return ArbitraryStoragesRepositoryImpl(database.arbitraryStoragesDao())
     }
 }

+ 81 - 52
app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt

@@ -1,62 +1,91 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger <infoi@andy-scherzinger.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
 package com.nextcloud.talk.data.source.local
 
 import androidx.room.migration.Migration
 import androidx.sqlite.db.SupportSQLiteDatabase
 
 object Migrations {
-    val MIGRATION_7_8 = object : Migration(7, 8) {
+    val MIGRATION_6_8 = object : Migration(6, 8) {
         override fun migrate(database: SupportSQLiteDatabase) {
-            // Create the new tables
-            database.execSQL(
-                "CREATE TABLE User_new (" +
-                    "id INTEGER NOT NULL, " +
-                    "userId TEXT, " +
-                    "username TEXT, " +
-                    "baseUrl TEXT, " +
-                    "token TEXT, " +
-                    "displayName TEXT, " +
-                    "pushConfigurationState TEXT, " +
-                    "capabilities TEXT, " +
-                    "clientCertificate TEXT, " +
-                    "externalSignalingServer TEXT, " +
-                    "current INTEGER NOT NULL, " +
-                    "scheduledForDeletion INTEGER NOT NULL, " +
-                    "PRIMARY KEY(id)" +
-                    ")"
-            )
-            database.execSQL(
-                "CREATE TABLE ArbitraryStorage_new (" +
-                    "accountIdentifier INTEGER NOT NULL, " +
-                    "\"key\" TEXT, " +
-                    "object TEXT, " +
-                    "value TEXT, " +
-                    "PRIMARY KEY(accountIdentifier)" +
-                    ")"
-            )
-            // Copy the data
-            database.execSQL(
-                "INSERT INTO User_new (" +
-                    "id, userId, username, baseUrl, token, displayName, pushConfigurationState, capabilities, " +
-                    "clientCertificate, externalSignalingServer, current, scheduledForDeletion) " +
-                    "SELECT " +
-                    "id, userId, username, baseUrl, token, displayName, pushConfigurationState, capabilities, " +
-                    "clientCertificate, externalSignalingServer, current, scheduledForDeletion " +
-                    "FROM User"
-            )
-            database.execSQL(
-                "INSERT INTO ArbitraryStorage_new (" +
-                    "accountIdentifier, \"key\", object, value) " +
-                    "SELECT " +
-                    "accountIdentifier, \"key\", object, value " +
-                    "FROM ArbitraryStorage"
-            )
-            // Remove the old table
-            database.execSQL("DROP TABLE User")
-            database.execSQL("DROP TABLE ArbitraryStorage")
+            migrateToRoom(database)
+        }
+    }
 
-            // Change the table name to the correct one
-            database.execSQL("ALTER TABLE User_new RENAME TO User")
-            database.execSQL("ALTER TABLE ArbitraryStorage_new RENAME TO ArbitraryStorage")
+    val MIGRATION_7_8 = object : Migration(7, 8) {
+        override fun migrate(database: SupportSQLiteDatabase) {
+            migrateToRoom(database)
         }
     }
-}
+
+    fun migrateToRoom(database: SupportSQLiteDatabase) {
+        database.execSQL(
+            "CREATE TABLE User_new (" +
+                "id INTEGER NOT NULL, " +
+                "userId TEXT, " +
+                "username TEXT, " +
+                "baseUrl TEXT, " +
+                "token TEXT, " +
+                "displayName TEXT, " +
+                "pushConfigurationState TEXT, " +
+                "capabilities TEXT, " +
+                "clientCertificate TEXT, " +
+                "externalSignalingServer TEXT, " +
+                "current INTEGER NOT NULL, " +
+                "scheduledForDeletion INTEGER NOT NULL, " +
+                "PRIMARY KEY(id)" +
+                ")"
+        )
+        database.execSQL(
+            "CREATE TABLE ArbitraryStorage_new (" +
+                "accountIdentifier INTEGER NOT NULL, " +
+                "\"key\" TEXT, " +
+                "object TEXT, " +
+                "value TEXT, " +
+                "PRIMARY KEY(accountIdentifier)" +
+                ")"
+        )
+        // Copy the data
+        database.execSQL(
+            "INSERT INTO User_new (" +
+                "id, userId, username, baseUrl, token, displayName, pushConfigurationState, capabilities, " +
+                "clientCertificate, externalSignalingServer, current, scheduledForDeletion) " +
+                "SELECT " +
+                "id, userId, username, baseUrl, token, displayName, pushConfigurationState, capabilities, " +
+                "clientCertificate, externalSignalingServer, current, scheduledForDeletion " +
+                "FROM User"
+        )
+        database.execSQL(
+            "INSERT INTO ArbitraryStorage_new (" +
+                "accountIdentifier, \"key\", object, value) " +
+                "SELECT " +
+                "accountIdentifier, \"key\", object, value " +
+                "FROM ArbitraryStorage"
+        )
+        // Remove the old table
+        database.execSQL("DROP TABLE User")
+        database.execSQL("DROP TABLE ArbitraryStorage")
+
+        // Change the table name to the correct one
+        database.execSQL("ALTER TABLE User_new RENAME TO User")
+        database.execSQL("ALTER TABLE ArbitraryStorage_new RENAME TO ArbitraryStorage")
+    }
+}

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

@@ -83,7 +83,7 @@ abstract class TalkDatabase : RoomDatabase() {
             return Room
                 .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName)
                 .openHelperFactory(factory)
-                .addMigrations(Migrations.MIGRATION_7_8)
+                .addMigrations(Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8)
                 .allowMainThreadQueries()
                 .addCallback(
                     object : RoomDatabase.Callback() {

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

@@ -22,7 +22,6 @@ package com.nextcloud.talk.data.source.local.converters
 
 import androidx.room.TypeConverter
 import com.nextcloud.talk.models.ExternalSignalingServer
-import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings
 
 class ExternalSignalingServerConverter {
     val json = JsonConfiguration.customJsonConfiguration

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

@@ -41,4 +41,4 @@ class HashMapHashMapConverter {
 
         return LoganSquare.parseMap(value, HashMap::class.java) as HashMap<String, HashMap<String, String>>?
     }
-}
+}

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

@@ -27,7 +27,7 @@ import kotlinx.serialization.json.Json
 sealed class JsonConfiguration {
     companion object {
         val customJsonConfiguration = Json {
-            prettyPrint = true;
+            prettyPrint = true
             useArrayPolymorphism = true
         }
     }

+ 2 - 2
app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt

@@ -89,7 +89,7 @@ abstract class UsersDao {
     abstract suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity?
 
     @Transaction
-    open suspend fun setUserAsActiveWithId(id: Long) : Boolean {
+    open suspend fun setUserAsActiveWithId(id: Long): Boolean {
         val users = getUsers()
         for (user in users) {
             // removed from clause: && UserStatus.ACTIVE == user.status
@@ -120,7 +120,7 @@ abstract class UsersDao {
 
         return setAnyUserAsActive()
     }
-    
+
     @Transaction
     open suspend fun setAnyUserAsActive(): Boolean {
         val users = getUsers()

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

@@ -46,4 +46,4 @@ interface UsersRepository {
     suspend fun deleteUserWithId(id: Long)
     suspend fun setAnyUserAsActive(): Boolean
     suspend fun markUserForDeletion(id: Long): Boolean
-}
+}

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

@@ -23,7 +23,6 @@ import android.os.Parcelable
 import com.nextcloud.talk.models.ExternalSignalingServer
 import com.nextcloud.talk.models.json.capabilities.Capabilities
 import com.nextcloud.talk.models.json.push.PushConfigurationState
-import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings
 import com.nextcloud.talk.utils.ApiUtils
 import kotlinx.android.parcel.Parcelize
 import kotlinx.serialization.Serializable

+ 0 - 51
app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java

@@ -24,7 +24,6 @@ package com.nextcloud.talk.models.database;
 import android.util.Log;
 
 import com.bluelinelabs.logansquare.LoganSquare;
-import com.nextcloud.talk.data.user.model.UserNgEntity;
 import com.nextcloud.talk.models.json.capabilities.Capabilities;
 
 import java.io.IOException;
@@ -67,18 +66,6 @@ public abstract class CapabilitiesUtil {
         return false;
     }
 
-    @Deprecated
-    public static boolean isServerEOL(@Nullable UserNgEntity user) {
-        // Capability is available since Talk 4 => Nextcloud 14 => Autmn 2018
-        return !hasSpreedFeatureCapability(user, "no-ping");
-    }
-
-    @Deprecated
-    public static boolean isServerAlmostEOL(@Nullable UserNgEntity user) {
-        // Capability is available since Talk 8 => Nextcloud 18 => January 2020
-        return !hasSpreedFeatureCapability(user, "chat-replies");
-    }
-
     @Deprecated
     public static boolean isServerEOL(@Nullable UserEntity user) {
         // Capability is available since Talk 4 => Nextcloud 14 => Autmn 2018
@@ -95,17 +82,6 @@ public abstract class CapabilitiesUtil {
         return hasSpreedFeatureCapability(user, "chat-read-marker");
     }
 
-    public static boolean hasSpreedFeatureCapability(@Nullable UserNgEntity user, String capabilityName) {
-        if (user != null && user.getCapabilities() != null) {
-            Capabilities capabilities = user.getCapabilities();
-            if (capabilities != null && capabilities.getSpreedCapability() != null &&
-                capabilities.getSpreedCapability().getFeatures() != null) {
-                return capabilities.getSpreedCapability().getFeatures().contains(capabilityName);
-            }
-        }
-        return false;
-    }
-
     @Deprecated
     public static boolean hasSpreedFeatureCapability(@Nullable UserEntity user, String capabilityName) {
         if (user != null && user.getCapabilities() != null) {
@@ -166,17 +142,6 @@ public abstract class CapabilitiesUtil {
         return false;
     }
 
-    public static boolean isPhoneBookIntegrationAvailable(@Nullable UserNgEntity user) {
-        if (user != null && user.getCapabilities() != null) {
-            Capabilities capabilities = user.getCapabilities();
-            return capabilities != null &&
-                capabilities.getSpreedCapability() != null &&
-                capabilities.getSpreedCapability().getFeatures() != null &&
-                capabilities.getSpreedCapability().getFeatures().contains("phonebook-search");
-        }
-        return false;
-    }
-
     public static boolean isReadStatusAvailable(@Nullable UserEntity user) {
         if (user != null && user.getCapabilities() != null) {
             try {
@@ -216,22 +181,6 @@ public abstract class CapabilitiesUtil {
         return false;
     }
 
-    public static boolean isReadStatusPrivate(@Nullable UserNgEntity user) {
-        if (user != null && user.getCapabilities() != null) {
-            Capabilities capabilities = user.getCapabilities();
-            if (capabilities != null &&
-                capabilities.getSpreedCapability() != null &&
-                capabilities.getSpreedCapability().getConfig() != null &&
-                capabilities.getSpreedCapability().getConfig().containsKey("chat")) {
-                HashMap<String, String> map = capabilities.getSpreedCapability().getConfig().get("chat");
-                if (map != null && map.containsKey("read-privacy")) {
-                    return Integer.parseInt(map.get("read-privacy")) == 1;
-                }
-            }
-        }
-        return false;
-    }
-
     public static boolean isUserStatusAvailable(@Nullable UserEntity user) {
         if (user != null && user.getCapabilities() != null) {
             try {

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

@@ -29,7 +29,6 @@ import io.requery.Entity;
 import io.requery.Generated;
 import io.requery.Key;
 import io.requery.Persistable;
-import io.requery.Table;
 
 @Entity
 public interface User extends Parcelable, Persistable, Serializable {

+ 8 - 3
app/src/main/java/com/nextcloud/talk/users/UserManager.kt

@@ -114,7 +114,8 @@ class UserManager internal constructor(private val userRepository: UsersReposito
     }
 
     suspend fun createOrUpdateUser(
-        username: String?, token: String?,
+        username: String?,
+        token: String?,
         serverUrl: String?,
         displayName: String?,
         pushConfigurationState: String?,
@@ -166,8 +167,12 @@ class UserManager internal constructor(private val userRepository: UsersReposito
             if (token != null && token != user.token) {
                 user.token = token
             }
-            if (displayName != null && user.displayName == null || displayName != null && (user.displayName
-                    != null) && displayName != user.displayName
+            if (
+                displayName != null &&
+                user.displayName == null ||
+                displayName != null &&
+                (user.displayName != null) &&
+                displayName != user.displayName
             ) {
                 user.displayName = displayName
             }

+ 230 - 0
app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesNgUtil.java

@@ -0,0 +1,230 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Mario Danic
+ * Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.utils.database.user;
+
+import com.nextcloud.talk.data.user.model.UserNgEntity;
+import com.nextcloud.talk.models.json.capabilities.Capabilities;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import androidx.annotation.Nullable;
+
+public abstract class CapabilitiesNgUtil {
+    private static final String TAG = CapabilitiesNgUtil.class.getSimpleName();
+
+    public static boolean hasNotificationsCapability(@Nullable UserNgEntity user, String capabilityName) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities.getNotificationsCapability() != null &&
+                capabilities.getNotificationsCapability().getFeatures() != null) {
+                return capabilities.getSpreedCapability().getFeatures().contains(capabilityName);
+            }
+        }
+        return false;
+    }
+
+    public static boolean hasExternalCapability(@Nullable UserNgEntity user, String capabilityName) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities.getExternalCapability() != null &&
+                capabilities.getExternalCapability().containsKey("v1")) {
+                return capabilities.getExternalCapability().get("v1").contains(capabilityName);
+            }
+        }
+        return false;
+    }
+
+    public static boolean isServerEOL(@Nullable UserNgEntity user) {
+        // Capability is available since Talk 4 => Nextcloud 14 => Autmn 2018
+        return !hasSpreedFeatureCapability(user, "no-ping");
+    }
+
+    public static boolean isServerAlmostEOL(@Nullable UserNgEntity user) {
+        // Capability is available since Talk 8 => Nextcloud 18 => January 2020
+        return !hasSpreedFeatureCapability(user, "chat-replies");
+    }
+
+    public static boolean canSetChatReadMarker(@Nullable UserNgEntity user) {
+        return hasSpreedFeatureCapability(user, "chat-read-marker");
+    }
+
+    public static boolean hasSpreedFeatureCapability(@Nullable UserNgEntity user, String capabilityName) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities != null && capabilities.getSpreedCapability() != null &&
+                capabilities.getSpreedCapability().getFeatures() != null) {
+                return capabilities.getSpreedCapability().getFeatures().contains(capabilityName);
+            }
+        }
+        return false;
+    }
+
+    public static Integer getMessageMaxLength(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities != null &&
+                capabilities.getSpreedCapability() != null &&
+                capabilities.getSpreedCapability().getConfig() != null &&
+                capabilities.getSpreedCapability().getConfig().containsKey("chat")) {
+                HashMap<String, String> chatConfigHashMap = capabilities
+                    .getSpreedCapability()
+                    .getConfig()
+                    .get("chat");
+                if (chatConfigHashMap != null && chatConfigHashMap.containsKey("max-length")) {
+                    int chatSize = Integer.parseInt(chatConfigHashMap.get("max-length"));
+                    if (chatSize > 0) {
+                        return chatSize;
+                    } else {
+                        return 1000;
+                    }
+                }
+            }
+        }
+        return 1000;
+    }
+
+    public static boolean isPhoneBookIntegrationAvailable(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            return capabilities != null &&
+                capabilities.getSpreedCapability() != null &&
+                capabilities.getSpreedCapability().getFeatures() != null &&
+                capabilities.getSpreedCapability().getFeatures().contains("phonebook-search");
+        }
+        return false;
+    }
+
+    public static boolean isReadStatusAvailable(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities != null &&
+                capabilities.getSpreedCapability() != null &&
+                capabilities.getSpreedCapability().getConfig() != null &&
+                capabilities.getSpreedCapability().getConfig().containsKey("chat")) {
+                Map<String, String> map = capabilities.getSpreedCapability().getConfig().get("chat");
+                return map != null && map.containsKey("read-privacy");
+            }
+        }
+        return false;
+    }
+
+    public static boolean isReadStatusPrivate(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities != null &&
+                capabilities.getSpreedCapability() != null &&
+                capabilities.getSpreedCapability().getConfig() != null &&
+                capabilities.getSpreedCapability().getConfig().containsKey("chat")) {
+                HashMap<String, String> map = capabilities.getSpreedCapability().getConfig().get("chat");
+                if (map != null && map.containsKey("read-privacy")) {
+                    return Integer.parseInt(map.get("read-privacy")) == 1;
+                }
+            }
+        }
+        return false;
+    }
+
+    public static boolean isUserStatusAvailable(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities.getUserStatusCapability() != null &&
+                capabilities.getUserStatusCapability().getEnabled() &&
+                capabilities.getUserStatusCapability().getSupportsEmoji()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static String getAttachmentFolder(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities != null &&
+                capabilities.getSpreedCapability() != null &&
+                capabilities.getSpreedCapability().getConfig() != null &&
+                capabilities.getSpreedCapability().getConfig().containsKey("attachments")) {
+                HashMap<String, String> map = capabilities.getSpreedCapability().getConfig().get("attachments");
+                if (map != null && map.containsKey("folder")) {
+                    return map.get("folder");
+                }
+            }
+        }
+        return "/Talk";
+    }
+
+    public static String getServerName(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities != null && capabilities.getThemingCapability() != null) {
+                return capabilities.getThemingCapability().getName();
+            }
+        }
+        return "";
+    }
+
+    // TODO later avatar can also be checked via user fields, for now it is in Talk capability
+    public static boolean isAvatarEndpointAvailable(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            return (capabilities != null &&
+                capabilities.getSpreedCapability() != null &&
+                capabilities.getSpreedCapability().getFeatures() != null &&
+                capabilities.getSpreedCapability().getFeatures().contains("temp-user-avatar-api"));
+        }
+        return false;
+    }
+
+    public static boolean canEditScopes(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            return (capabilities != null &&
+                capabilities.getProvisioningCapability() != null &&
+                capabilities.getProvisioningCapability().getAccountPropertyScopesVersion() != null &&
+                capabilities.getProvisioningCapability().getAccountPropertyScopesVersion() > 1);
+        }
+        return false;
+    }
+
+    public static boolean isAbleToCall(@Nullable UserNgEntity user) {
+        if (user != null && user.getCapabilities() != null) {
+            Capabilities capabilities = user.getCapabilities();
+            if (capabilities != null &&
+                capabilities.getSpreedCapability() != null &&
+                capabilities.getSpreedCapability().getConfig() != null &&
+                capabilities.getSpreedCapability().getConfig().containsKey("call") &&
+                capabilities.getSpreedCapability().getConfig().get("call") != null &&
+                capabilities.getSpreedCapability().getConfig().get("call").containsKey("enabled")) {
+                return Boolean.parseBoolean(
+                    capabilities.getSpreedCapability().getConfig().get("call").get("enabled"));
+            } else {
+                // older nextcloud versions without the capability can't disable the calls
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isUnifiedSearchAvailable(@Nullable final UserNgEntity user) {
+        return hasSpreedFeatureCapability(user, "unified-search");
+    }
+}