From db1dd66204227d415c98a67ebe9ac551304dc244 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Sun, 12 Jun 2022 19:01:47 +0200 Subject: [PATCH 01/55] port room database layer from master-broken to up-to-date master Signed-off-by: Andy Scherzinger --- app/build.gradle | 10 ++ .../talk/data/source/local/TalkDatabase.kt | 74 +++++++++++ .../local/converters/CapabilitiesConverter.kt | 45 +++++++ .../converters/HashMapHashMapConverter.kt | 44 +++++++ .../local/converters/JsonConfiguration.kt | 34 +++++ .../converters/PushConfigurationConverter.kt | 48 +++++++ .../converters/SignalingSettingsConverter.kt | 48 +++++++ .../com/nextcloud/talk/data/user/UsersDao.kt | 118 ++++++++++++++++++ .../nextcloud/talk/data/user/model/User.kt | 87 +++++++++++++ .../talk/data/user/model/UserNgEntity.kt | 73 +++++++++++ .../models/json/capabilities/Capabilities.kt | 2 + .../capabilities/NotificationsCapability.kt | 2 + .../capabilities/ProvisioningCapability.kt | 2 + .../json/capabilities/SpreedCapability.kt | 2 + .../json/capabilities/ThemingCapability.kt | 2 + .../json/capabilities/UserStatusCapability.kt | 2 + .../json/push/PushConfigurationState.kt | 2 + .../json/signaling/settings/IceServer.kt | 2 + .../{Settings.kt => SignalingSettings.kt} | 4 +- .../settings/SignalingSettingsOcs.kt | 2 +- build.gradle | 1 + 21 files changed, 602 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/source/local/converters/CapabilitiesConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/source/local/converters/JsonConfiguration.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/source/local/converters/PushConfigurationConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/source/local/converters/SignalingSettingsConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/user/model/User.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/user/model/UserNgEntity.kt rename app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/{Settings.kt => SignalingSettings.kt} (94%) diff --git a/app/build.gradle b/app/build.gradle index 5b906e092..fd85870a1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,6 +33,7 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'com.github.spotbugs' apply plugin: 'io.gitlab.arturbosch.detekt' apply plugin: "org.jlleitschuh.gradle.ktlint" +apply plugin: 'kotlinx-serialization' android { compileSdkVersion 31 @@ -157,6 +158,7 @@ ext { materialDialogsVersion = "3.3.0" parcelerVersion = "1.1.13" retrofit2Version = "2.9.0" + roomVersion = "2.2.5" workVersion = "2.7.1" markwonVersion = "4.6.2" espressoVersion = "3.4.0" @@ -179,6 +181,9 @@ dependencies { detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.20.0") implementation fileTree(include: ['*'], dir: 'libs') + + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3" + implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' @@ -236,6 +241,11 @@ dependencies { implementation 'io.requery:requery-android:1.6.1' implementation 'net.zetetic:android-database-sqlcipher:4.5.1' kapt 'io.requery:requery-processor:1.6.1' + + implementation "androidx.room:room-runtime:${roomVersion}" + kapt "androidx.room:room-compiler:${roomVersion}" // For Kotlin use kapt instead of annotationProcessor + implementation "androidx.room:room-ktx:${roomVersion}" + implementation "org.parceler:parceler-api:$parcelerVersion" implementation 'net.orange-box.storebox:storebox-lib:1.4.0' implementation "com.jakewharton:butterknife:${butterknifeVersion}" diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt new file mode 100644 index 000000000..c70ada54f --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -0,0 +1,74 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.source.local + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import androidx.sqlite.db.SupportSQLiteDatabase +import com.nextcloud.talk.data.source.local.converters.CapabilitiesConverter +import com.nextcloud.talk.data.source.local.converters.HashMapHashMapConverter +import com.nextcloud.talk.data.source.local.converters.PushConfigurationConverter +import com.nextcloud.talk.data.source.local.converters.SignalingSettingsConverter +import com.nextcloud.talk.data.user.UsersDao +import com.nextcloud.talk.data.user.model.UserNgEntity + +@Database( + entities = [UserNgEntity::class], + version = 1, + exportSchema = true +) +@TypeConverters( + PushConfigurationConverter::class, + CapabilitiesConverter::class, + SignalingSettingsConverter::class, + HashMapHashMapConverter::class +) + +abstract class TalkDatabase : RoomDatabase() { + + abstract fun usersDao(): UsersDao + + companion object { + private const val DB_NAME = "talk.db" + + @Volatile + private var INSTANCE: TalkDatabase? = null + + fun getInstance(context: Context): TalkDatabase = + INSTANCE ?: synchronized(this) { + INSTANCE ?: build(context).also { INSTANCE = it } + } + + private fun build(context: Context) = + Room.databaseBuilder(context.applicationContext, TalkDatabase::class.java, DB_NAME) + .fallbackToDestructiveMigration() + .addCallback(object : RoomDatabase.Callback() { + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + db.execSQL("PRAGMA defer_foreign_keys = 1") + } + }) + .build() + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/CapabilitiesConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/CapabilitiesConverter.kt new file mode 100644 index 000000000..55747fffa --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/CapabilitiesConverter.kt @@ -0,0 +1,45 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.source.local.converters + +import androidx.room.TypeConverter +import com.bluelinelabs.logansquare.LoganSquare +import com.nextcloud.talk.models.json.capabilities.Capabilities + +class CapabilitiesConverter { + @TypeConverter + fun fromCapabilitiesToString(capabilities: Capabilities?): String { + return if (capabilities == null) { + "" + } else { + LoganSquare.serialize(capabilities) + } + } + + @TypeConverter + fun fromStringToCapabilities(value: String): Capabilities? { + if (value.isBlank()) { + return null + } + + return LoganSquare.parse(value, Capabilities::class.java) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt new file mode 100644 index 000000000..019ecba35 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.source.local.converters + +import androidx.room.TypeConverter +import com.bluelinelabs.logansquare.LoganSquare + +class HashMapHashMapConverter { + @TypeConverter + fun fromDoubleHashMapToString(map: HashMap>?): String? { + return if (map == null) { + LoganSquare.serialize(hashMapOf>()) + } else { + return LoganSquare.serialize(map) + } + } + + @TypeConverter + fun fromStringToDoubleHashMap(value: String?): HashMap>? { + if (value.isNullOrEmpty()) { + return hashMapOf() + } + + return LoganSquare.parseMap(value, HashMap::class.java) as HashMap>? + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/JsonConfiguration.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/JsonConfiguration.kt new file mode 100644 index 000000000..8ea6e8ffe --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/JsonConfiguration.kt @@ -0,0 +1,34 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * @author Mario Danic + * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.source.local.converters + +import kotlinx.serialization.json.Json + +sealed class JsonConfiguration { + companion object { + val customJsonConfiguration = Json { + prettyPrint = true; + useArrayPolymorphism = true + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/PushConfigurationConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/PushConfigurationConverter.kt new file mode 100644 index 000000000..2a1e038ec --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/PushConfigurationConverter.kt @@ -0,0 +1,48 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * @author Mario Danic + * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.source.local.converters + +import androidx.room.TypeConverter +import com.nextcloud.talk.models.json.push.PushConfigurationState + +class PushConfigurationConverter { + val json = JsonConfiguration.customJsonConfiguration + + @TypeConverter + fun fromPushConfigurationToString(pushConfiguration: PushConfigurationState?): String { + return if (pushConfiguration == null) { + "" + } else { + json.encodeToString(PushConfigurationState.serializer(), pushConfiguration) + } + } + + @TypeConverter + fun fromStringToPushConfiguration(value: String): PushConfigurationState? { + if (value.isBlank()) { + return null + } + + return json.decodeFromString(PushConfigurationState.serializer(), value) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/SignalingSettingsConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/SignalingSettingsConverter.kt new file mode 100644 index 000000000..481f32395 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/SignalingSettingsConverter.kt @@ -0,0 +1,48 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * @author Mario Danic + * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.source.local.converters + +import androidx.room.TypeConverter +import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings + +class SignalingSettingsConverter { + val json = JsonConfiguration.customJsonConfiguration + + @TypeConverter + fun fromSignalingSettingsToString(signalingSettings: SignalingSettings?): String { + return if (signalingSettings == null) { + "" + } else { + json.encodeToString(SignalingSettings.serializer(), signalingSettings) + } + } + + @TypeConverter + fun fromStringToSignalingSettings(value: String): SignalingSettings? { + if (value.isBlank()) { + return null + } + + return json.decodeFromString(SignalingSettings.serializer(), value) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt new file mode 100644 index 000000000..5392548a2 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt @@ -0,0 +1,118 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.user + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update +import com.nextcloud.talk.data.user.model.UserNgEntity +import java.lang.Boolean.FALSE +import java.lang.Boolean.TRUE + +@Dao +abstract class UsersDao { + // get active user + @Query("SELECT * FROM users where current = 1") + abstract fun getActiveUser(): UserNgEntity? + + @Query("SELECT * FROM users WHERE current = 1") + abstract fun getActiveUserLiveData(): LiveData + + @Query("SELECT * from users ORDER BY current DESC") + abstract fun getUsersLiveData(): LiveData> + + @Query("SELECT * from users WHERE current != 1 ORDER BY current DESC") + abstract fun getUsersLiveDataWithoutActive(): LiveData> + + @Query("DELETE FROM users WHERE id = :id") + abstract suspend fun deleteUserWithId(id: Long) + + @Update + abstract suspend fun updateUser(user: UserNgEntity): Int + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun saveUser(user: UserNgEntity): Long + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract suspend fun saveUsers(vararg users: UserNgEntity): List + + // get all users not scheduled for deletion + @Query("SELECT * FROM users where current != 0") + abstract fun getUsers(): List + + @Query("SELECT * FROM users where id = :id") + abstract fun getUserWithId(id: Long): UserNgEntity + + @Query("SELECT * FROM users where current = 0") + abstract fun getUsersScheduledForDeletion(): List + + @Query("SELECT * FROM users WHERE username = :username AND base_url = :server") + abstract suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity? + + @Transaction + open suspend fun setUserAsActiveWithId(id: Long) : Boolean { + val users = getUsers() + for (user in users) { + // removed from clause: && UserStatus.ACTIVE == user.status + if (user.id != id) { + user.current = TRUE + updateUser(user) + } // removed from clause: && UserStatus.ACTIVE != user.status + else if (user.id == id) { + user.current = TRUE + updateUser(user) + } + } + + return true + } + + @Transaction + open suspend fun markUserForDeletion(id: Long): Boolean { + val users = getUsers() + for (user in users) { + if (user.id == id) { + // TODO currently we only have a boolean, no intermediate states + user.current = FALSE + updateUser(user) + break + } + } + + return setAnyUserAsActive() + } + + @Transaction + open suspend fun setAnyUserAsActive(): Boolean { + val users = getUsers() + for (user in users) { + user.current = TRUE + updateUser(user) + return true + } + + return false + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt b/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt new file mode 100644 index 000000000..03804b138 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt @@ -0,0 +1,87 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.data.user.model + +import android.os.Parcelable +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 + +@Parcelize +@Serializable +data class User( + var id: Long? = null, + var userId: String, + var username: String, + var baseUrl: String, + var token: String? = null, + var displayName: String? = null, + var pushConfiguration: PushConfigurationState? = null, + var capabilities: Capabilities? = null, + var clientCertificate: String? = null, + var signalingSettings: SignalingSettings? = null, + var current: Boolean = java.lang.Boolean.FALSE, + var scheduledForDeletion: Boolean = java.lang.Boolean.FALSE +) : Parcelable + +fun User.getMaxMessageLength(): Int { + return capabilities?.spreedCapability?.config?.get("chat")?.get("max-length")?.toInt() ?: 1000 +} + +fun User.getAttachmentsConfig(key: String): Any? { + return capabilities?.spreedCapability?.config?.get("attachments")?.get(key) +} + +fun User.canUserCreateGroupConversations(): Boolean { + val canCreateValue = capabilities?.spreedCapability?.config?.get("conversations")?.get("can-create") + canCreateValue?.let { + return it.toBoolean() + } + return true +} + +fun User.getCredentials(): String = ApiUtils.getCredentials(username, token) + +fun User.hasSpreedFeatureCapability(capabilityName: String): Boolean { + return capabilities?.spreedCapability?.features?.contains(capabilityName) ?: false +} + +fun User.toUserEntity(): UserNgEntity { + var userNgEntity: UserNgEntity? = null + this.id?.let { + userNgEntity = UserNgEntity(it, userId, username, baseUrl) + } ?: run { + userNgEntity = UserNgEntity(userId = this.userId, username = this.username, baseUrl = this.baseUrl) + } + + userNgEntity!!.token = this.token + userNgEntity!!.displayName = this.displayName + userNgEntity!!.pushConfiguration = this.pushConfiguration + userNgEntity!!.capabilities = this.capabilities + userNgEntity!!.clientCertificate = this.clientCertificate + userNgEntity!!.externalSignalingServer = this.signalingSettings + userNgEntity!!.current = this.current + userNgEntity!!.scheduledForDeletion = this.scheduledForDeletion + + return userNgEntity!! +} diff --git a/app/src/main/java/com/nextcloud/talk/data/user/model/UserNgEntity.kt b/app/src/main/java/com/nextcloud/talk/data/user/model/UserNgEntity.kt new file mode 100644 index 000000000..a7f1629a0 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/user/model/UserNgEntity.kt @@ -0,0 +1,73 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.user.model + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +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 +import java.lang.Boolean.FALSE + +@Parcelize +@Serializable +@Entity(tableName = "users") +data class UserNgEntity( + @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long = 0, + @ColumnInfo(name = "user_id") var userId: String, + @ColumnInfo(name = "username") var username: String, + @ColumnInfo(name = "base_url") var baseUrl: String, + @ColumnInfo(name = "token") var token: String? = null, + @ColumnInfo(name = "display_name") var displayName: String? = null, + @ColumnInfo(name = "push_configuration_state") var pushConfiguration: PushConfigurationState? = null, + @ColumnInfo(name = "capabilities") var capabilities: Capabilities? = null, + @ColumnInfo(name = "client_certificate") var clientCertificate: String? = null, + @ColumnInfo(name = "external_signaling_server") var externalSignalingServer: SignalingSettings? = null, + @ColumnInfo(name = "current") var current: Boolean = FALSE, + @ColumnInfo(name = "scheduled_for_deletion") var scheduledForDeletion: Boolean = FALSE, +) : Parcelable { + + fun hasSpreedFeatureCapability(capabilityName: String): Boolean { + return capabilities?.spreedCapability?.features?.contains(capabilityName) ?: false + + } +} + +fun UserNgEntity.canUserCreateGroupConversations(): Boolean { + val canCreateValue = capabilities?.spreedCapability?.config?.get("conversations")?.get("can-create") + canCreateValue?.let { + return it.toBoolean() + } + return true +} + +fun UserNgEntity.toUser(): User { + return User(this.id, this.userId, this.username, this.baseUrl, this.token, this.displayName, this + .pushConfiguration, this.capabilities, this.clientCertificate, this.externalSignalingServer, this.current, + this.scheduledForDeletion) +} + +fun UserNgEntity.getCredentials(): String = ApiUtils.getCredentials(username, token) diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.kt index 6682469b5..e1ca8d043 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class Capabilities( @JsonField(name = ["spreed"]) var spreedCapability: SpreedCapability?, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/NotificationsCapability.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/NotificationsCapability.kt index 6ea1ccfe5..ac8d207f4 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/NotificationsCapability.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/NotificationsCapability.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class NotificationsCapability( @JsonField(name = ["ocs-endpoints"]) var features: List? diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ProvisioningCapability.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ProvisioningCapability.kt index cb809cdf5..2ff775777 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ProvisioningCapability.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ProvisioningCapability.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class ProvisioningCapability( @JsonField(name = ["AccountPropertyScopesVersion"]) var accountPropertyScopesVersion: Int? diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/SpreedCapability.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/SpreedCapability.kt index cc0665ec5..658301ee7 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/SpreedCapability.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/SpreedCapability.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class SpreedCapability( @JsonField(name = ["features"]) var features: List?, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ThemingCapability.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ThemingCapability.kt index 61b3344d5..6dfdcae7b 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ThemingCapability.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/ThemingCapability.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class ThemingCapability( @JsonField(name = ["name"]) var name: String?, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/UserStatusCapability.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/UserStatusCapability.kt index eda9cb399..2ca1edf2f 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/UserStatusCapability.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/UserStatusCapability.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class UserStatusCapability( @JsonField(name = ["enabled"]) var enabled: Boolean, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/push/PushConfigurationState.kt b/app/src/main/java/com/nextcloud/talk/models/json/push/PushConfigurationState.kt index ff68fd87f..c4eed4464 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/push/PushConfigurationState.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/push/PushConfigurationState.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class PushConfigurationState( @JsonField(name = ["pushToken"]) var pushToken: String?, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/IceServer.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/IceServer.kt index 85a4efbc9..90d57ff79 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/IceServer.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/IceServer.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class IceServer( @Deprecated("") @JsonField(name = ["url"]) diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/Settings.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt similarity index 94% rename from app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/Settings.kt rename to app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt index f04b3095c..4b1a1cd6b 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/Settings.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt @@ -25,10 +25,12 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject -data class Settings( +@Serializable +data class SignalingSettings( @JsonField(name = ["stunservers"]) var stunServers: List? = null, @JsonField(name = ["turnservers"]) diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettingsOcs.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettingsOcs.kt index 473b8a57c..2b3358155 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettingsOcs.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettingsOcs.kt @@ -33,7 +33,7 @@ data class SignalingSettingsOcs( @JsonField(name = ["meta"]) var meta: GenericMeta?, @JsonField(name = ["data"]) - var settings: Settings? = null + var settings: SignalingSettings? = null ) : Parcelable { // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' constructor() : this(null, null) diff --git a/build.gradle b/build.gradle index d45a09421..dfc0d62f2 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}" + classpath "org.jetbrains.kotlin:kotlin-serialization:${kotlinVersion}" classpath 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.5' classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.20.0" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.3.0" From 05db482d0652dc0bb7930f8945e637903a252e68 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Sun, 19 Jun 2022 22:17:21 +0200 Subject: [PATCH 02/55] adding arbitrary storage implementation, initial steps towards new util classes Signed-off-by: Andy Scherzinger --- app/build.gradle | 3 + .../talk/activities/MainActivityTest.kt | 44 ++-- .../java/com/nextcloud/talk/ui/LoginIT.java | 2 +- .../nextcloud/talk/activities/MainActivity.kt | 15 +- .../talk/controllers/SettingsController.kt | 12 +- .../talk/dagger/modules/DatabaseModule.java | 9 +- .../talk/dagger/modules/RepositoryModule.kt | 15 ++ .../talk/data/source/local/Migrations.kt | 62 ++++++ .../talk/data/source/local/TalkDatabase.kt | 50 +++-- .../ExternalSignalingServerConverter.kt | 47 ++++ .../talk/data/storage/ArbitraryStoragesDao.kt | 44 ++++ .../storage/ArbitraryStoragesRepository.kt | 29 +++ .../ArbitraryStoragesRepositoryImpl.kt | 42 ++++ .../storage/model/ArbitraryStorageNgEntity.kt | 38 ++++ .../com/nextcloud/talk/data/user/UsersDao.kt | 37 +++- .../talk/data/user/UsersRepository.kt | 49 +++++ .../talk/data/user/UsersRepositoryImpl.kt | 119 ++++++++++ .../nextcloud/talk/data/user/model/User.kt | 20 +- .../talk/data/user/model/UserNgEntity.kt | 31 +-- .../talk/models/ExternalSignalingServer.kt | 2 + .../models/database/CapabilitiesUtil.java | 57 +++++ .../nextcloud/talk/models/database/User.java | 1 + .../com/nextcloud/talk/users/UserManager.kt | 204 ++++++++++++++++++ .../com/nextcloud/talk/utils/ApiUtils.java | 33 +++ .../nextcloud/talk/utils/DisplayUtils.java | 30 +++ .../database/user/CurrentUserProviderNew.kt | 27 +++ .../DatabaseStorageModule.java | 12 +- 27 files changed, 944 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/source/local/converters/ExternalSignalingServerConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesDao.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesRepository.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesRepositoryImpl.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/storage/model/ArbitraryStorageNgEntity.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt create mode 100644 app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt create mode 100644 app/src/main/java/com/nextcloud/talk/users/UserManager.kt create mode 100644 app/src/main/java/com/nextcloud/talk/utils/database/user/CurrentUserProviderNew.kt diff --git a/app/build.gradle b/app/build.gradle index fd85870a1..763a11b0b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -154,6 +154,7 @@ ext { butterknifeVersion = "10.2.3" coilKtVersion = "2.1.0" daggerVersion = "2.42" + lifecycleVersion = '2.2.0' okhttpVersion = "4.10.0" materialDialogsVersion = "3.3.0" parcelerVersion = "1.1.13" @@ -246,6 +247,8 @@ dependencies { kapt "androidx.room:room-compiler:${roomVersion}" // For Kotlin use kapt instead of annotationProcessor implementation "androidx.room:room-ktx:${roomVersion}" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:${lifecycleVersion}" + implementation "org.parceler:parceler-api:$parcelerVersion" implementation 'net.orange-box.storebox:storebox-lib:1.4.0' implementation "com.jakewharton:butterknife:${butterknifeVersion}" diff --git a/app/src/androidTest/java/com/nextcloud/talk/activities/MainActivityTest.kt b/app/src/androidTest/java/com/nextcloud/talk/activities/MainActivityTest.kt index 3b785c6ce..0e3630571 100644 --- a/app/src/androidTest/java/com/nextcloud/talk/activities/MainActivityTest.kt +++ b/app/src/androidTest/java/com/nextcloud/talk/activities/MainActivityTest.kt @@ -1,10 +1,7 @@ package com.nextcloud.talk.activities -import android.util.Log import androidx.test.espresso.intent.rule.IntentsTestRule -import com.nextcloud.talk.models.database.UserEntity -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers +import com.nextcloud.talk.data.user.model.UserNgEntity import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test @@ -18,28 +15,25 @@ class MainActivityTest { ) @Test - fun login() { + suspend fun login() { val sut = activityRule.launchActivity(null) - sut.userUtils.createOrUpdateUser( - "test", - "test", - "http://server/nc", - "test", - null, - true, - "test", - null, - null, - null, - null - ) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { userEntity: UserEntity? -> Log.i("test", "stored: " + userEntity.toString()) }, - { throwable: Throwable? -> Log.e("test", "throwable") }, - { Log.d("test", "complete") } + + sut.usersRepository.insertUser( + UserNgEntity( + 0, + "test", + "test", + "http://server/nc", + "test", + null, + null, + null, + null, + null, + false, + scheduledForDeletion = false ) + ) try { Thread.sleep(2000) @@ -49,7 +43,7 @@ class MainActivityTest { sut.runOnUiThread { sut.resetConversationsList() } - assertTrue(sut.userUtils.getIfUserWithUsernameAndServer("test", "http://server/nc")) + assertTrue(sut.usersRepository.getUserWithUsernameAndServer("test", "http://server/nc") != null) try { } catch (e: InterruptedException) { diff --git a/app/src/androidTest/java/com/nextcloud/talk/ui/LoginIT.java b/app/src/androidTest/java/com/nextcloud/talk/ui/LoginIT.java index e807f3775..2eea9df52 100644 --- a/app/src/androidTest/java/com/nextcloud/talk/ui/LoginIT.java +++ b/app/src/androidTest/java/com/nextcloud/talk/ui/LoginIT.java @@ -143,7 +143,7 @@ public class LoginIT { onView(withId(R.id.user_name)).check(matches(withText("User One"))); activityScenario.onActivity(activity -> { - assertEquals(loginName, Objects.requireNonNull(activity.userUtils.getCurrentUser()).getUserId()); + assertEquals(loginName, Objects.requireNonNull(activity.usersRepository.getActiveUser()).getUserId()); }); } diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index 7c59c31d3..862d3c9fb 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -46,6 +46,7 @@ import com.nextcloud.talk.controllers.ServerSelectionController import com.nextcloud.talk.controllers.SettingsController import com.nextcloud.talk.controllers.WebViewLoginController import com.nextcloud.talk.controllers.base.providers.ActionBarProvider +import com.nextcloud.talk.data.user.UsersRepository import com.nextcloud.talk.databinding.ActivityMainBinding import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.conversations.RoomOverall @@ -72,9 +73,6 @@ import javax.inject.Inject class MainActivity : BaseActivity(), ActionBarProvider { lateinit var binding: ActivityMainBinding - @Inject - lateinit var userUtils: UserUtils - @Inject lateinit var dataStore: ReactiveEntityStore @@ -84,6 +82,9 @@ class MainActivity : BaseActivity(), ActionBarProvider { @Inject lateinit var ncApi: NcApi + @Inject + lateinit var usersRepository: UsersRepository + private var router: Router? = null @Suppress("Detekt.TooGenericExceptionCaught") @@ -114,7 +115,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { onNewIntent(intent) } else if (!router!!.hasRootController()) { if (hasDb) { - if (userUtils.anyUserExists()) { + if (usersRepository.getUsers().isNotEmpty()) { setDefaultRootController() } else { launchLoginScreen() @@ -178,7 +179,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { } fun resetConversationsList() { - if (userUtils.anyUserExists()) { + if (usersRepository.getUsers().isNotEmpty()) { setDefaultRootController() } } @@ -218,7 +219,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat" -> { val user = userId.substringBeforeLast("@") val baseUrl = userId.substringAfterLast("@") - if (userUtils.currentUser?.baseUrl?.endsWith(baseUrl) == true) { + if (usersRepository.getActiveUser()?.baseUrl?.endsWith(baseUrl) == true) { startConversation(user) } else { Snackbar.make( @@ -234,7 +235,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { private fun startConversation(userId: String) { val roomType = "1" - val currentUser = userUtils.currentUser ?: return + val currentUser = usersRepository.getActiveUser() ?: return val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1)) val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt index 9c05a7231..a2a97edac 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt @@ -69,6 +69,9 @@ 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 import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.jobs.ContactAddressBookWorker @@ -115,8 +118,11 @@ class SettingsController : NewBaseController(R.layout.controller_settings) { @Inject lateinit var userUtils: UserUtils + @Inject + lateinit var userRepository: UsersRepository + private var saveStateHandler: LovelySaveStateHandler? = null - private var currentUser: UserEntity? = null + private var currentUser: UserNgEntity? = null private var credentials: String? = null private var proxyTypeChangeListener: OnPreferenceValueChangedListener? = null private var proxyCredentialsChangeListener: OnPreferenceValueChangedListener? = null @@ -134,7 +140,7 @@ class SettingsController : NewBaseController(R.layout.controller_settings) { resources!!.getString(R.string.nc_settings) private fun getCurrentUser() { - currentUser = userUtils.currentUser + currentUser = userRepository.getActiveUser() credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token) } @@ -184,7 +190,7 @@ class SettingsController : NewBaseController(R.layout.controller_settings) { } private fun setupPhoneBookIntegration() { - if (CapabilitiesUtil.isPhoneBookIntegrationAvailable(userUtils.currentUser)) { + if (CapabilitiesUtil.isPhoneBookIntegrationAvailable(userRepository.getActiveUser())) { binding.settingsPhoneBookIntegration.visibility = View.VISIBLE } else { binding.settingsPhoneBookIntegration.visibility = View.GONE diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java index 87be3413f..bbe4b772a 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java @@ -24,6 +24,7 @@ package com.nextcloud.talk.dagger.modules; import android.content.Context; import androidx.annotation.NonNull; import com.nextcloud.talk.R; +import com.nextcloud.talk.data.source.local.TalkDatabase; import com.nextcloud.talk.models.database.Models; import com.nextcloud.talk.utils.preferences.AppPreferences; import dagger.Module; @@ -54,7 +55,7 @@ public class DatabaseModule { .toLowerCase() .replace(" ", "_") .trim() - + ".sqlite", + + ".sqlite_off", context.getString(R.string.nc_talk_database_encryption_key), DB_VERSION); } @@ -74,4 +75,10 @@ public class DatabaseModule { preferences.removeLinkPreviews(); return preferences; } + + @Provides + @Singleton + public TalkDatabase provideTalkDatabase(@NonNull final Context context) { + return TalkDatabase.Companion.getInstance(context); + } } diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index 267ab6b41..68ec493b6 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -22,6 +22,11 @@ package com.nextcloud.talk.dagger.modules import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.data.source.local.TalkDatabase +import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository +import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl +import com.nextcloud.talk.data.user.UsersRepository +import com.nextcloud.talk.data.user.UsersRepositoryImpl import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepository import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepositoryImpl import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository @@ -50,4 +55,14 @@ class RepositoryModule { RemoteFileBrowserItemsRepository { return RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider) } + + @Provides + fun provideUsersRepository(database : TalkDatabase): UsersRepository { + return UsersRepositoryImpl(database.usersDao()) + } + + @Provides + fun provideArbitraryStoragesRepository(database : TalkDatabase): ArbitraryStoragesRepository { + return ArbitraryStoragesRepositoryImpl(database.arbitraryStoragesDao()) + } } diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt new file mode 100644 index 000000000..4123feabd --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt @@ -0,0 +1,62 @@ +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) { + 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") + + // 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") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index c70ada54f..c80a1f9f3 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -26,31 +26,38 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.sqlite.db.SupportSQLiteDatabase +import com.nextcloud.talk.R import com.nextcloud.talk.data.source.local.converters.CapabilitiesConverter +import com.nextcloud.talk.data.source.local.converters.ExternalSignalingServerConverter import com.nextcloud.talk.data.source.local.converters.HashMapHashMapConverter import com.nextcloud.talk.data.source.local.converters.PushConfigurationConverter import com.nextcloud.talk.data.source.local.converters.SignalingSettingsConverter +import com.nextcloud.talk.data.storage.ArbitraryStoragesDao +import com.nextcloud.talk.data.storage.model.ArbitraryStorageNgEntity import com.nextcloud.talk.data.user.UsersDao import com.nextcloud.talk.data.user.model.UserNgEntity +import net.sqlcipher.database.SQLiteDatabase +import net.sqlcipher.database.SupportFactory +import java.util.Locale @Database( - entities = [UserNgEntity::class], - version = 1, + entities = [UserNgEntity::class, ArbitraryStorageNgEntity::class], + version = 8, exportSchema = true ) @TypeConverters( PushConfigurationConverter::class, CapabilitiesConverter::class, + ExternalSignalingServerConverter::class, SignalingSettingsConverter::class, HashMapHashMapConverter::class ) - abstract class TalkDatabase : RoomDatabase() { abstract fun usersDao(): UsersDao + abstract fun arbitraryStoragesDao(): ArbitraryStoragesDao companion object { - private const val DB_NAME = "talk.db" @Volatile private var INSTANCE: TalkDatabase? = null @@ -60,15 +67,32 @@ abstract class TalkDatabase : RoomDatabase() { INSTANCE ?: build(context).also { INSTANCE = it } } - private fun build(context: Context) = - Room.databaseBuilder(context.applicationContext, TalkDatabase::class.java, DB_NAME) - .fallbackToDestructiveMigration() - .addCallback(object : RoomDatabase.Callback() { - override fun onOpen(db: SupportSQLiteDatabase) { - super.onOpen(db) - db.execSQL("PRAGMA defer_foreign_keys = 1") - } - }) + private fun build(context: Context): TalkDatabase { + val passCharArray = context.getString(R.string.nc_talk_database_encryption_key).toCharArray() + val passphrase: ByteArray = SQLiteDatabase.getBytes(passCharArray) + val factory = SupportFactory(passphrase) + + val dbName = context + .resources + .getString(R.string.nc_app_product_name) + .lowercase(Locale.getDefault()) + .replace(" ", "_") + .trim { it <= ' ' } + + ".sqlite" + + return Room + .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName) + .openHelperFactory(factory) + .addMigrations(Migrations.MIGRATION_7_8) + .allowMainThreadQueries() + .addCallback( + object : RoomDatabase.Callback() { + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + db.execSQL("PRAGMA defer_foreign_keys = 1") + } + }) .build() + } } } diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ExternalSignalingServerConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ExternalSignalingServerConverter.kt new file mode 100644 index 000000000..e1f949bc3 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ExternalSignalingServerConverter.kt @@ -0,0 +1,47 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.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 + + @TypeConverter + fun fromExternalSignalingServerToString(externalSignalingServer: ExternalSignalingServer?): String { + return if (externalSignalingServer == null) { + "" + } else { + json.encodeToString(ExternalSignalingServer.serializer(), externalSignalingServer) + } + } + + @TypeConverter + fun fromStringToExternalSignalingServer(value: String): ExternalSignalingServer? { + if (value.isBlank()) { + return null + } + + return json.decodeFromString(ExternalSignalingServer.serializer(), value) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesDao.kt b/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesDao.kt new file mode 100644 index 000000000..4508bc3da --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesDao.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.storage + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.nextcloud.talk.data.storage.model.ArbitraryStorageNgEntity + +@Dao +abstract class ArbitraryStoragesDao { + @Query( + "SELECT * FROM ArbitraryStorage WHERE " + + "accountIdentifier = :accountIdentifier AND " + + "\"key\" = :key AND " + + "object = :objectString" + ) + abstract fun getStorageSetting(accountIdentifier: Long, key: String, objectString: String): ArbitraryStorageNgEntity + + @Query("DELETE FROM ArbitraryStorage WHERE accountIdentifier = :accountIdentifier") + abstract suspend fun deleteArbitraryStorage(accountIdentifier: Long) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun saveArbitraryStorage(arbitraryStorage: ArbitraryStorageNgEntity): Long +} diff --git a/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesRepository.kt b/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesRepository.kt new file mode 100644 index 000000000..6244cd112 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesRepository.kt @@ -0,0 +1,29 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.storage + +import com.nextcloud.talk.data.storage.model.ArbitraryStorageNgEntity + +interface ArbitraryStoragesRepository { + fun getStorageSetting(accountIdentifier: Long, key: String, objectString: String): ArbitraryStorageNgEntity + suspend fun deleteArbitraryStorage(accountIdentifier: Long) + fun saveArbitraryStorage(arbitraryStorage: ArbitraryStorageNgEntity): Long +} diff --git a/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesRepositoryImpl.kt new file mode 100644 index 000000000..48355144c --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/storage/ArbitraryStoragesRepositoryImpl.kt @@ -0,0 +1,42 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.storage + +import com.nextcloud.talk.data.storage.model.ArbitraryStorageNgEntity + +class ArbitraryStoragesRepositoryImpl(private val arbitraryStoragesDao: ArbitraryStoragesDao) : + ArbitraryStoragesRepository { + override fun getStorageSetting( + accountIdentifier: Long, + key: String, + objectString: String + ): ArbitraryStorageNgEntity { + return arbitraryStoragesDao.getStorageSetting(accountIdentifier, key, objectString) + } + + override suspend fun deleteArbitraryStorage(accountIdentifier: Long) { + arbitraryStoragesDao.deleteArbitraryStorage(accountIdentifier) + } + + override fun saveArbitraryStorage(arbitraryStorage: ArbitraryStorageNgEntity): Long { + return arbitraryStoragesDao.saveArbitraryStorage(arbitraryStorage) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/storage/model/ArbitraryStorageNgEntity.kt b/app/src/main/java/com/nextcloud/talk/data/storage/model/ArbitraryStorageNgEntity.kt new file mode 100644 index 000000000..7ec06ab5d --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/storage/model/ArbitraryStorageNgEntity.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.storage.model + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable + +@Parcelize +@Serializable +@Entity(tableName = "ArbitraryStorage") +data class ArbitraryStorageNgEntity( + @PrimaryKey @ColumnInfo(name = "accountIdentifier") var accountIdentifier: Long = 0, + @ColumnInfo(name = "key") var key: String? = null, + @ColumnInfo(name = "object") var storageObject: String? = null, + @ColumnInfo(name = "value") var value: String? = null +) : Parcelable diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt index 5392548a2..30955ec16 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt @@ -2,6 +2,8 @@ * Nextcloud Talk application * * @author Mario Danic + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger * Copyright (C) 2017-2020 Mario Danic * * This program is free software: you can redistribute it and/or modify @@ -34,19 +36,19 @@ import java.lang.Boolean.TRUE @Dao abstract class UsersDao { // get active user - @Query("SELECT * FROM users where current = 1") + @Query("SELECT * FROM User where current = 1") abstract fun getActiveUser(): UserNgEntity? - @Query("SELECT * FROM users WHERE current = 1") + @Query("SELECT * FROM User WHERE current = 1") abstract fun getActiveUserLiveData(): LiveData - @Query("SELECT * from users ORDER BY current DESC") + @Query("SELECT * FROM User ORDER BY current DESC") abstract fun getUsersLiveData(): LiveData> - @Query("SELECT * from users WHERE current != 1 ORDER BY current DESC") + @Query("SELECT * FROM User WHERE current != 1 ORDER BY current DESC") abstract fun getUsersLiveDataWithoutActive(): LiveData> - @Query("DELETE FROM users WHERE id = :id") + @Query("DELETE FROM User WHERE id = :id") abstract suspend fun deleteUserWithId(id: Long) @Update @@ -59,16 +61,31 @@ abstract class UsersDao { abstract suspend fun saveUsers(vararg users: UserNgEntity): List // get all users not scheduled for deletion - @Query("SELECT * FROM users where current != 0") + @Query("SELECT * FROM User where current != 0") abstract fun getUsers(): List - @Query("SELECT * FROM users where id = :id") - abstract fun getUserWithId(id: Long): UserNgEntity + @Query("SELECT * FROM User where id = :id") + abstract fun getUserWithId(id: Long): UserNgEntity? - @Query("SELECT * FROM users where current = 0") + @Query("SELECT * FROM User where id = :id") + abstract fun getUserWithIdLiveData(id: Long): LiveData + + @Query("SELECT * FROM User where id = :id AND scheduledForDeletion != 1") + abstract fun getUserWithIdNotScheduledForDeletion(id: Long): UserNgEntity? + + @Query("SELECT * FROM User where userId = :userId") + abstract fun getUserWithUserId(userId: String): UserNgEntity? + + @Query("SELECT * FROM User where userId != :userId") + abstract fun getUsersWithoutUserId(userId: Long): List + + @Query("SELECT * FROM User where current = 0") abstract fun getUsersScheduledForDeletion(): List - @Query("SELECT * FROM users WHERE username = :username AND base_url = :server") + @Query("SELECT * FROM User where scheduledForDeletion = 0") + abstract fun getUsersNotScheduledForDeletion(): List + + @Query("SELECT * FROM User WHERE username = :username AND baseUrl = :server") abstract suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity? @Transaction diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt new file mode 100644 index 000000000..02f0b4313 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt @@ -0,0 +1,49 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.user + +import androidx.lifecycle.LiveData +import com.nextcloud.talk.data.user.model.UserNgEntity +import com.nextcloud.talk.data.user.model.User + +interface UsersRepository { + fun getActiveUserLiveData(): LiveData + fun getActiveUser(): UserNgEntity? + fun getUsers(): List + fun getUserWithId(id: Long): UserNgEntity? + fun getUserWithIdLiveData(id: Long): LiveData + fun getUserWithIdNotScheduledForDeletion(id: Long): UserNgEntity? + fun getUserWithUserId(userId: String): UserNgEntity? + fun getUsersWithoutUserId(userId: Long): List + fun getUsersLiveData(): LiveData> + fun getUsersLiveDataWithoutActive(): LiveData> + fun getUsersScheduledForDeletion(): List + fun getUsersNotScheduledForDeletion(): List + suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity? + suspend fun updateUser(user: UserNgEntity): Int + suspend fun insertUser(user: UserNgEntity): Long + suspend fun setUserAsActiveWithId(id: Long): Boolean + suspend fun deleteUserWithId(id: Long) + suspend fun setAnyUserAsActive(): Boolean + suspend fun markUserForDeletion(id: Long): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt new file mode 100644 index 000000000..fc5d13186 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt @@ -0,0 +1,119 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2017-2020 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.data.user + +import androidx.lifecycle.LiveData +import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.map +import com.nextcloud.talk.data.user.model.UserNgEntity +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.data.user.model.toUser + +class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository { + override fun getActiveUserLiveData(): LiveData { + return usersDao.getActiveUserLiveData().distinctUntilChanged() + } + + override fun getActiveUser(): UserNgEntity? { + return usersDao.getActiveUser() + } + + override fun getUsers(): List { + return usersDao.getUsers() + } + + override fun getUserWithId(id: Long): UserNgEntity? { + return usersDao.getUserWithId(id) + } + + override fun getUserWithIdLiveData(id: Long): LiveData { + return usersDao.getUserWithIdLiveData(id).distinctUntilChanged() + } + + override fun getUserWithIdNotScheduledForDeletion(id: Long): UserNgEntity? { + return usersDao.getUserWithIdNotScheduledForDeletion(id) + } + + override fun getUserWithUserId(userId: String): UserNgEntity? { + return usersDao.getUserWithUserId(userId) + } + + override fun getUsersWithoutUserId(userId: Long): List { + return usersDao.getUsersWithoutUserId(userId) + } + + override fun getUsersLiveData(): LiveData> { + return usersDao.getUsersLiveData().distinctUntilChanged().map { usersList -> + usersList.map { + it.toUser() + } + } + } + + override fun getUsersLiveDataWithoutActive(): LiveData> { + return usersDao.getUsersLiveDataWithoutActive().distinctUntilChanged().map { usersList -> + usersList.map { + it.toUser() + } + } + } + + override fun getUsersScheduledForDeletion(): List { + return usersDao.getUsersScheduledForDeletion() + } + + override fun getUsersNotScheduledForDeletion(): List { + return usersDao.getUsersNotScheduledForDeletion() + } + + override suspend fun getUserWithUsernameAndServer( + username: String, + server: String + ): UserNgEntity? { + return usersDao.getUserWithUsernameAndServer(username, server) + } + + override suspend fun updateUser(user: UserNgEntity): Int { + return usersDao.updateUser(user) + } + + override suspend fun insertUser(user: UserNgEntity): Long { + return usersDao.saveUser(user) + } + + override suspend fun setUserAsActiveWithId(id: Long): Boolean { + return usersDao.setUserAsActiveWithId(id) + } + + override suspend fun deleteUserWithId(id: Long) { + usersDao.deleteUserWithId(id) + } + + override suspend fun setAnyUserAsActive(): Boolean { + return usersDao.setAnyUserAsActive() + } + + override suspend fun markUserForDeletion(id: Long): Boolean { + return usersDao.markUserForDeletion(id) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt b/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt index 03804b138..fe4a3472d 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt @@ -20,28 +20,30 @@ package com.nextcloud.talk.data.user.model 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 +import java.lang.Boolean.FALSE @Parcelize @Serializable data class User( var id: Long? = null, - var userId: String, - var username: String, - var baseUrl: String, + var userId: String? = null, + var username: String? = null, + var baseUrl: String? = null, var token: String? = null, var displayName: String? = null, - var pushConfiguration: PushConfigurationState? = null, + var pushConfigurationState: PushConfigurationState? = null, var capabilities: Capabilities? = null, var clientCertificate: String? = null, - var signalingSettings: SignalingSettings? = null, - var current: Boolean = java.lang.Boolean.FALSE, - var scheduledForDeletion: Boolean = java.lang.Boolean.FALSE + var externalSignalingServer: ExternalSignalingServer? = null, + var current: Boolean = FALSE, + var scheduledForDeletion: Boolean = FALSE, ) : Parcelable fun User.getMaxMessageLength(): Int { @@ -76,10 +78,10 @@ fun User.toUserEntity(): UserNgEntity { userNgEntity!!.token = this.token userNgEntity!!.displayName = this.displayName - userNgEntity!!.pushConfiguration = this.pushConfiguration + userNgEntity!!.pushConfigurationState = this.pushConfigurationState userNgEntity!!.capabilities = this.capabilities userNgEntity!!.clientCertificate = this.clientCertificate - userNgEntity!!.externalSignalingServer = this.signalingSettings + userNgEntity!!.externalSignalingServer = this.externalSignalingServer userNgEntity!!.current = this.current userNgEntity!!.scheduledForDeletion = this.scheduledForDeletion diff --git a/app/src/main/java/com/nextcloud/talk/data/user/model/UserNgEntity.kt b/app/src/main/java/com/nextcloud/talk/data/user/model/UserNgEntity.kt index a7f1629a0..562bcef3b 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/model/UserNgEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/model/UserNgEntity.kt @@ -1,7 +1,9 @@ /* * Nextcloud Talk application * + * @author Andy Scherzinger * @author Mario Danic + * Copyright (C) 2022 Andy Scherzinger * Copyright (C) 2017-2020 Mario Danic * * This program is free software: you can redistribute it and/or modify @@ -24,9 +26,9 @@ import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +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 @@ -34,25 +36,24 @@ import java.lang.Boolean.FALSE @Parcelize @Serializable -@Entity(tableName = "users") +@Entity(tableName = "User") data class UserNgEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long = 0, - @ColumnInfo(name = "user_id") var userId: String, - @ColumnInfo(name = "username") var username: String, - @ColumnInfo(name = "base_url") var baseUrl: String, + @ColumnInfo(name = "userId") var userId: String? = null, + @ColumnInfo(name = "username") var username: String? = null, + @ColumnInfo(name = "baseUrl") var baseUrl: String? = null, @ColumnInfo(name = "token") var token: String? = null, - @ColumnInfo(name = "display_name") var displayName: String? = null, - @ColumnInfo(name = "push_configuration_state") var pushConfiguration: PushConfigurationState? = null, + @ColumnInfo(name = "displayName") var displayName: String? = null, + @ColumnInfo(name = "pushConfigurationState") var pushConfigurationState: PushConfigurationState? = null, @ColumnInfo(name = "capabilities") var capabilities: Capabilities? = null, - @ColumnInfo(name = "client_certificate") var clientCertificate: String? = null, - @ColumnInfo(name = "external_signaling_server") var externalSignalingServer: SignalingSettings? = null, + @ColumnInfo(name = "clientCertificate") var clientCertificate: String? = null, + @ColumnInfo(name = "externalSignalingServer") var externalSignalingServer: ExternalSignalingServer? = null, @ColumnInfo(name = "current") var current: Boolean = FALSE, - @ColumnInfo(name = "scheduled_for_deletion") var scheduledForDeletion: Boolean = FALSE, + @ColumnInfo(name = "scheduledForDeletion") var scheduledForDeletion: Boolean = FALSE, ) : Parcelable { fun hasSpreedFeatureCapability(capabilityName: String): Boolean { return capabilities?.spreedCapability?.features?.contains(capabilityName) ?: false - } } @@ -65,9 +66,11 @@ fun UserNgEntity.canUserCreateGroupConversations(): Boolean { } fun UserNgEntity.toUser(): User { - return User(this.id, this.userId, this.username, this.baseUrl, this.token, this.displayName, this - .pushConfiguration, this.capabilities, this.clientCertificate, this.externalSignalingServer, this.current, - this.scheduledForDeletion) + return User( + this.id, this.userId, this.username, this.baseUrl, this.token, this.displayName, this.pushConfigurationState, + this.capabilities, this.clientCertificate, this.externalSignalingServer, this.current, + this.scheduledForDeletion + ) } fun UserNgEntity.getCredentials(): String = ApiUtils.getCredentials(username, token) diff --git a/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.kt b/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.kt index c1b476e76..cf5907d9f 100644 --- a/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.kt +++ b/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.kt @@ -25,9 +25,11 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonObject +@Serializable data class ExternalSignalingServer( @JsonField(name = ["externalSignalingServer"]) var externalSignalingServer: String? = null, diff --git a/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java b/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java index 447ffc339..929d328a0 100644 --- a/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java +++ b/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java @@ -24,6 +24,7 @@ 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; @@ -66,11 +67,25 @@ 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 return !hasSpreedFeatureCapability(user, "no-ping"); } + @Deprecated public static boolean isServerAlmostEOL(@Nullable UserEntity user) { // Capability is available since Talk 8 => Nextcloud 18 => January 2020 return !hasSpreedFeatureCapability(user, "chat-replies"); @@ -80,6 +95,18 @@ 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) { try { @@ -123,6 +150,7 @@ public abstract class CapabilitiesUtil { return 1000; } + @Deprecated public static boolean isPhoneBookIntegrationAvailable(@Nullable UserEntity user) { if (user != null && user.getCapabilities() != null) { try { @@ -138,6 +166,17 @@ 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 { @@ -156,6 +195,7 @@ public abstract class CapabilitiesUtil { return false; } + @Deprecated public static boolean isReadStatusPrivate(@Nullable UserEntity user) { if (user != null && user.getCapabilities() != null) { try { @@ -176,6 +216,22 @@ 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 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 { @@ -280,6 +336,7 @@ public abstract class CapabilitiesUtil { return false; } + @Deprecated private static Capabilities parseUserCapabilities(@NonNull final UserEntity user) throws IOException { return LoganSquare.parse(user.getCapabilities(), Capabilities.class); } diff --git a/app/src/main/java/com/nextcloud/talk/models/database/User.java b/app/src/main/java/com/nextcloud/talk/models/database/User.java index 47e9fac7e..fd966dee3 100644 --- a/app/src/main/java/com/nextcloud/talk/models/database/User.java +++ b/app/src/main/java/com/nextcloud/talk/models/database/User.java @@ -29,6 +29,7 @@ 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 { diff --git a/app/src/main/java/com/nextcloud/talk/users/UserManager.kt b/app/src/main/java/com/nextcloud/talk/users/UserManager.kt new file mode 100644 index 000000000..8f4786cfe --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/users/UserManager.kt @@ -0,0 +1,204 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Andy Scherzinger + * Copyright (C) 2022 Andy Scherzinger + * Copyright (C) 2017 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.users + +import android.text.TextUtils +import androidx.lifecycle.LiveData +import com.bluelinelabs.logansquare.LoganSquare +import com.nextcloud.talk.data.user.UsersRepository +import com.nextcloud.talk.data.user.model.UserNgEntity +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.utils.database.user.CurrentUserProviderNew + +class UserManager internal constructor(private val userRepository: UsersRepository) : CurrentUserProviderNew { + fun anyUserExists(): Boolean { + return userRepository.getUsers().isNotEmpty() + } + + fun hasMultipleUsers(): Boolean { + return userRepository.getUsers().size > 1 + } + + val users: List + get() = userRepository.getUsers() + + val usersScheduledForDeletion: List + get() = userRepository.getUsersScheduledForDeletion() + + suspend fun setAnyUserAndSetAsActive(): UserNgEntity? { + val results = userRepository.getUsersNotScheduledForDeletion() + if (results.isNotEmpty()) { + val UserNgEntity = results[0] + UserNgEntity.current = true + userRepository.updateUser(UserNgEntity) + return UserNgEntity + } + return null + } + + override val currentUser: UserNgEntity? + get() { + return userRepository.getActiveUser() + } + + suspend fun deleteUser(internalId: Long) { + userRepository.deleteUserWithId(internalId) + } + + suspend fun deleteUserWithId(internalId: Long) { + userRepository.deleteUserWithId(internalId) + } + + fun getUserById(userId: String): UserNgEntity? { + return userRepository.getUserWithUserId(userId) + } + + fun getUserWithId(id: Long): UserNgEntity? { + return userRepository.getUserWithId(id) + } + + suspend fun disableAllUsersWithoutId(userId: Long) { + val results = userRepository.getUsersWithoutUserId(userId) + if (results.isNotEmpty()) { + for (entity in results) { + entity.current = false + userRepository.updateUser(entity) + } + } + } + + suspend fun checkIfUserIsScheduledForDeletion(username: String, server: String): Boolean { + val results = userRepository.getUserWithUsernameAndServer(username, server) + return results?.scheduledForDeletion ?: false + } + + fun getUserWithInternalId(id: Long): UserNgEntity? { + return userRepository.getUserWithIdNotScheduledForDeletion(id) + } + + suspend fun getIfUserWithUsernameAndServer(username: String, server: String): Boolean { + return userRepository.getUserWithUsernameAndServer(username, server) != null + } + + suspend fun scheduleUserForDeletionWithId(id: Long): Boolean { + val result = userRepository.getUserWithId(id) + + if (result != null) { + result.scheduledForDeletion = true + result.current = false + userRepository.updateUser(result) + } + + return setAnyUserAndSetAsActive() != null + } + + suspend fun createOrUpdateUser( + username: String?, token: String?, + serverUrl: String?, + displayName: String?, + pushConfigurationState: String?, + currentUser: Boolean?, + userId: String?, + internalId: Long?, + capabilities: String?, + certificateAlias: String?, + externalSignalingServer: String? + ): LiveData { + var user = if (internalId == null && username != null && serverUrl != null) { + userRepository.getUserWithUsernameAndServer(username, serverUrl) + } else if (internalId != null) { + userRepository.getUserWithId(internalId) + } else { + null + } + + if (user == null) { + user = UserNgEntity() + user.baseUrl = serverUrl + user.username = username + user.token = token + if (!TextUtils.isEmpty(displayName)) { + user.displayName = displayName + } + if (pushConfigurationState != null) { + user.pushConfigurationState = LoganSquare + .parse(pushConfigurationState, PushConfigurationState::class.java) + } + if (!TextUtils.isEmpty(userId)) { + user.userId = userId + } + if (!TextUtils.isEmpty(capabilities)) { + user.capabilities = LoganSquare.parse(capabilities, Capabilities::class.java) + } + if (!TextUtils.isEmpty(certificateAlias)) { + user.clientCertificate = certificateAlias + } + if (!TextUtils.isEmpty(externalSignalingServer)) { + user.externalSignalingServer = LoganSquare + .parse(externalSignalingServer, ExternalSignalingServer::class.java) + } + user.current = true + } else { + if (userId != null && (user.userId == null || user.userId != userId)) { + user.userId = userId + } + if (token != null && token != user.token) { + user.token = token + } + if (displayName != null && user.displayName == null || displayName != null && (user.displayName + != null) && displayName != user.displayName + ) { + user.displayName = displayName + } + if (pushConfigurationState != null) { + val newPushConfigurationState = LoganSquare + .parse(pushConfigurationState, PushConfigurationState::class.java) + if (newPushConfigurationState != user.pushConfigurationState) { + user.pushConfigurationState = newPushConfigurationState + } + } + if (capabilities != null) { + val newCapabilities = LoganSquare.parse(capabilities, Capabilities::class.java) + if (newCapabilities != user.capabilities) { + user.capabilities = newCapabilities + } + } + if (certificateAlias != null && certificateAlias != user.clientCertificate) { + user.clientCertificate = certificateAlias + } + if (externalSignalingServer != null) { + val newExternalSignalingServer = LoganSquare + .parse(externalSignalingServer, ExternalSignalingServer::class.java) + if (newExternalSignalingServer != user.externalSignalingServer) { + user.externalSignalingServer = newExternalSignalingServer + } + } + if (currentUser != null) { + user.current = currentUser + } + } + userRepository.insertUser(user) + return userRepository.getUserWithIdLiveData(user.id) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 9ca98882d..8b0e88adb 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -28,6 +28,7 @@ import android.util.Log; import com.nextcloud.talk.BuildConfig; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; +import com.nextcloud.talk.data.user.model.UserNgEntity; import com.nextcloud.talk.models.RetrofitBucket; import com.nextcloud.talk.models.database.CapabilitiesUtil; import com.nextcloud.talk.models.database.UserEntity; @@ -123,6 +124,38 @@ public class ApiUtils { return getConversationApiVersion(capabilities, versions); } + public static int getConversationApiVersion(UserNgEntity user, int[] versions) throws NoSupportedApiException { + boolean hasApiV4 = false; + for (int version : versions) { + hasApiV4 |= version == APIv4; + } + + if (!hasApiV4) { + Exception e = new Exception("Api call did not try conversation-v4 api"); + Log.d(TAG, e.getMessage(), e); + } + + for (int version : versions) { + if (user.hasSpreedFeatureCapability("conversation-v" + version)) { + return version; + } + + // Fallback for old API versions + if ((version == APIv1 || version == APIv2)) { + if (user.hasSpreedFeatureCapability("conversation-v2")) { + return version; + } + if (version == APIv1 && + user.hasSpreedFeatureCapability("mention-flag") && + !user.hasSpreedFeatureCapability("conversation-v4")) { + return version; + } + } + } + throw new NoSupportedApiException(); + } + + @Deprecated public static int getConversationApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException { boolean hasApiV4 = false; for (int version : versions) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java index 526b09c04..d9e3ffe6d 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java @@ -79,6 +79,7 @@ import com.facebook.widget.text.span.BetterImageSpan; import com.google.android.material.chip.ChipDrawable; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; +import com.nextcloud.talk.data.user.model.UserNgEntity; import com.nextcloud.talk.events.UserMentionClickEvent; import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.utils.text.Spans; @@ -565,6 +566,7 @@ public class DisplayUtils { } } + @Deprecated public static void loadAvatarImage(UserEntity user, SimpleDraweeView avatarImageView, boolean deleteCache) { String avatarId; if (!TextUtils.isEmpty(user.getUserId())) { @@ -593,6 +595,34 @@ public class DisplayUtils { avatarImageView.setController(draweeController); } + public static void loadAvatarImage(UserNgEntity user, SimpleDraweeView avatarImageView, boolean deleteCache) { + String avatarId; + if (!TextUtils.isEmpty(user.getUserId())) { + avatarId = user.getUserId(); + } else { + avatarId = user.getUsername(); + } + + String avatarString = ApiUtils.getUrlForAvatar(user.getBaseUrl(), avatarId, true); + + // clear cache + if (deleteCache) { + Uri avatarUri = Uri.parse(avatarString); + + ImagePipeline imagePipeline = Fresco.getImagePipeline(); + imagePipeline.evictFromMemoryCache(avatarUri); + imagePipeline.evictFromDiskCache(avatarUri); + imagePipeline.evictFromCache(avatarUri); + } + + DraweeController draweeController = Fresco.newDraweeControllerBuilder() + .setOldController(avatarImageView.getController()) + .setAutoPlayAnimations(true) + .setImageRequest(DisplayUtils.getImageRequestForUrl(avatarString, null)) + .build(); + avatarImageView.setController(draweeController); + } + public static void loadAvatarPlaceholder(final SimpleDraweeView targetView) { final Context context = targetView.getContext(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/CurrentUserProviderNew.kt b/app/src/main/java/com/nextcloud/talk/utils/database/user/CurrentUserProviderNew.kt new file mode 100644 index 000000000..63d0fdf66 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/CurrentUserProviderNew.kt @@ -0,0 +1,27 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.utils.database.user + +import com.nextcloud.talk.data.user.model.UserNgEntity + +interface CurrentUserProviderNew { + val currentUser: UserNgEntity? +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.java index 3ec3b2eb8..3e4601089 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.java +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.java @@ -26,7 +26,6 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; -import autodagger.AutoInjector; import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.models.database.ArbitraryStorageEntity; @@ -35,21 +34,20 @@ import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.json.generic.GenericOverall; import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils; -import com.nextcloud.talk.utils.database.user.UserUtils; import com.yarolegovich.mp.io.StorageModule; import org.jetbrains.annotations.NotNull; +import java.util.Set; + +import javax.inject.Inject; + +import autodagger.AutoInjector; import io.reactivex.Observer; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import javax.inject.Inject; - -import java.util.Collections; -import java.util.Set; - @AutoInjector(NextcloudTalkApplication.class) public class DatabaseStorageModule implements StorageModule { private static final String TAG = "DatabaseStorageModule"; From 349d5fe19b8da059e0b9c095174dbd024b38ac4c Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 20 Jun 2022 13:21:12 +0200 Subject: [PATCH 03/55] dual-mode of DB fpr requery and room Signed-off-by: Andy Scherzinger --- .../com/nextcloud/talk/dagger/modules/DatabaseModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java index bbe4b772a..c71c1008f 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java @@ -41,7 +41,7 @@ import javax.inject.Singleton; @Module public class DatabaseModule { - public static final int DB_VERSION = 7; + public static final int DB_VERSION = 8; @Provides @Singleton @@ -55,7 +55,7 @@ public class DatabaseModule { .toLowerCase() .replace(" ", "_") .trim() - + ".sqlite_off", + + ".sqlite", context.getString(R.string.nc_talk_database_encryption_key), DB_VERSION); } From 75c8625aee34691ea213899072af1d19b841d556 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 20 Jun 2022 13:28:16 +0200 Subject: [PATCH 04/55] run user repo call in coroutine Signed-off-by: Andy Scherzinger --- .../nextcloud/talk/activities/MainActivity.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index 862d3c9fb..d6a803ad9 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -58,7 +58,6 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY -import com.nextcloud.talk.utils.database.user.UserUtils import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable @@ -66,6 +65,9 @@ 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 import javax.inject.Inject @@ -115,10 +117,16 @@ class MainActivity : BaseActivity(), ActionBarProvider { onNewIntent(intent) } else if (!router!!.hasRootController()) { if (hasDb) { - if (usersRepository.getUsers().isNotEmpty()) { - setDefaultRootController() - } else { - launchLoginScreen() + GlobalScope.launch { + if (usersRepository.getUsers().isNotEmpty()) { + runOnUiThread { + setDefaultRootController() + } + } else { + runOnUiThread { + launchLoginScreen() + } + } } } else { launchLoginScreen() From 5ffa3c44fd05c6c8fdda1bcaf04fbab8f9c2087d Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 20 Jun 2022 13:46:32 +0200 Subject: [PATCH 05/55] create new capabilities util add license header and format kotlin code Signed-off-by: Andy Scherzinger --- .../nextcloud/talk/activities/MainActivity.kt | 1 - .../talk/controllers/SettingsController.kt | 11 +- .../talk/dagger/modules/RepositoryModule.kt | 6 +- .../talk/data/source/local/Migrations.kt | 135 ++++++---- .../talk/data/source/local/TalkDatabase.kt | 2 +- .../ExternalSignalingServerConverter.kt | 1 - .../converters/HashMapHashMapConverter.kt | 2 +- .../local/converters/JsonConfiguration.kt | 2 +- .../com/nextcloud/talk/data/user/UsersDao.kt | 4 +- .../talk/data/user/UsersRepository.kt | 2 +- .../nextcloud/talk/data/user/model/User.kt | 1 - .../models/database/CapabilitiesUtil.java | 51 ---- .../nextcloud/talk/models/database/User.java | 1 - .../com/nextcloud/talk/users/UserManager.kt | 11 +- .../database/user/CapabilitiesNgUtil.java | 230 ++++++++++++++++++ 15 files changed, 335 insertions(+), 125 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesNgUtil.java diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index d6a803ad9..10bf10f84 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/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 diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt index a2a97edac..d6593607a 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt +++ b/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(R.id.mp_checkable) as Checkable).isChecked = - !CapabilitiesUtil.isReadStatusPrivate(currentUser) + !CapabilitiesNgUtil.isReadStatusPrivate(currentUser) } else { binding.settingsReadPrivacy.visibility = View.GONE } diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index 68ec493b6..bb8a5f255 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/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 * 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()) } } diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt index 4123feabd..a3d23dfec 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt +++ b/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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.nextcloud.talk.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") - - // 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") + migrateToRoom(database) } } -} \ No newline at end of file + + 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") + } +} diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index c80a1f9f3..c6da1d53c 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -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() { diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ExternalSignalingServerConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ExternalSignalingServerConverter.kt index e1f949bc3..71dd1fac4 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ExternalSignalingServerConverter.kt +++ b/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 diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt index 019ecba35..a40220a82 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt +++ b/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>? } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/JsonConfiguration.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/JsonConfiguration.kt index 8ea6e8ffe..6f40e18c3 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/JsonConfiguration.kt +++ b/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 } } diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt index 30955ec16..0fdbf3af5 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt +++ b/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() diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt index 02f0b4313..19596ae9e 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt +++ b/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 -} \ No newline at end of file +} diff --git a/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt b/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt index fe4a3472d..564f0722f 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt +++ b/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 diff --git a/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java b/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java index 929d328a0..135946486 100644 --- a/app/src/main/java/com/nextcloud/talk/models/database/CapabilitiesUtil.java +++ b/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 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 { diff --git a/app/src/main/java/com/nextcloud/talk/models/database/User.java b/app/src/main/java/com/nextcloud/talk/models/database/User.java index fd966dee3..47e9fac7e 100644 --- a/app/src/main/java/com/nextcloud/talk/models/database/User.java +++ b/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 { diff --git a/app/src/main/java/com/nextcloud/talk/users/UserManager.kt b/app/src/main/java/com/nextcloud/talk/users/UserManager.kt index 8f4786cfe..5b4fa14c1 100644 --- a/app/src/main/java/com/nextcloud/talk/users/UserManager.kt +++ b/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 } diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesNgUtil.java b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesNgUtil.java new file mode 100644 index 000000000..010bf98e0 --- /dev/null +++ b/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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.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 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 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 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 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"); + } +} From 730aeb2944caaf326048d0f6f4b9df24aba00428 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 20 Jun 2022 14:47:37 +0200 Subject: [PATCH 06/55] fix detekt and ktlint issues Signed-off-by: Andy Scherzinger --- .../talk/data/source/local/Migrations.kt | 1 + .../com/nextcloud/talk/data/user/UsersDao.kt | 1 + .../talk/data/user/UsersRepository.kt | 1 + .../talk/data/user/UsersRepositoryImpl.kt | 1 + .../nextcloud/talk/data/user/model/User.kt | 10 +- .../com/nextcloud/talk/users/UserManager.kt | 222 +++++++++++------- 6 files changed, 150 insertions(+), 86 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt index a3d23dfec..1cbc9d026 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt @@ -23,6 +23,7 @@ package com.nextcloud.talk.data.source.local import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +@Suppress("MagicNumber") object Migrations { val MIGRATION_6_8 = object : Migration(6, 8) { override fun migrate(database: SupportSQLiteDatabase) { diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt index 0fdbf3af5..d58ec58c2 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersDao.kt @@ -34,6 +34,7 @@ import java.lang.Boolean.FALSE import java.lang.Boolean.TRUE @Dao +@Suppress("TooManyFunctions") abstract class UsersDao { // get active user @Query("SELECT * FROM User where current = 1") diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt index 19596ae9e..2e8780da9 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepository.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.LiveData import com.nextcloud.talk.data.user.model.UserNgEntity import com.nextcloud.talk.data.user.model.User +@Suppress("TooManyFunctions") interface UsersRepository { fun getActiveUserLiveData(): LiveData fun getActiveUser(): UserNgEntity? diff --git a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt index fc5d13186..5ca2e75a0 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/UsersRepositoryImpl.kt @@ -29,6 +29,7 @@ import com.nextcloud.talk.data.user.model.UserNgEntity import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.toUser +@Suppress("TooManyFunctions") class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository { override fun getActiveUserLiveData(): LiveData { return usersDao.getActiveUserLiveData().distinctUntilChanged() diff --git a/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt b/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt index 564f0722f..b56b44347 100644 --- a/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt +++ b/app/src/main/java/com/nextcloud/talk/data/user/model/User.kt @@ -20,6 +20,7 @@ package com.nextcloud.talk.data.user.model import android.os.Parcelable +import com.nextcloud.talk.data.user.model.User.Companion.DEFAULT_CHAT_MESSAGE_LENGTH import com.nextcloud.talk.models.ExternalSignalingServer import com.nextcloud.talk.models.json.capabilities.Capabilities import com.nextcloud.talk.models.json.push.PushConfigurationState @@ -43,10 +44,15 @@ data class User( var externalSignalingServer: ExternalSignalingServer? = null, var current: Boolean = FALSE, var scheduledForDeletion: Boolean = FALSE, -) : Parcelable +) : Parcelable { + companion object { + const val DEFAULT_CHAT_MESSAGE_LENGTH: Int = 1000 + } +} fun User.getMaxMessageLength(): Int { - return capabilities?.spreedCapability?.config?.get("chat")?.get("max-length")?.toInt() ?: 1000 + return capabilities?.spreedCapability?.config?.get("chat")?.get("max-length")?.toInt() + ?: DEFAULT_CHAT_MESSAGE_LENGTH } fun User.getAttachmentsConfig(key: String): Any? { diff --git a/app/src/main/java/com/nextcloud/talk/users/UserManager.kt b/app/src/main/java/com/nextcloud/talk/users/UserManager.kt index 5b4fa14c1..3014569de 100644 --- a/app/src/main/java/com/nextcloud/talk/users/UserManager.kt +++ b/app/src/main/java/com/nextcloud/talk/users/UserManager.kt @@ -31,6 +31,7 @@ import com.nextcloud.talk.models.json.capabilities.Capabilities import com.nextcloud.talk.models.json.push.PushConfigurationState import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew +@Suppress("TooManyFunctions") class UserManager internal constructor(private val userRepository: UsersRepository) : CurrentUserProviderNew { fun anyUserExists(): Boolean { return userRepository.getUsers().isNotEmpty() @@ -49,10 +50,10 @@ class UserManager internal constructor(private val userRepository: UsersReposito suspend fun setAnyUserAndSetAsActive(): UserNgEntity? { val results = userRepository.getUsersNotScheduledForDeletion() if (results.isNotEmpty()) { - val UserNgEntity = results[0] - UserNgEntity.current = true - userRepository.updateUser(UserNgEntity) - return UserNgEntity + val user = results[0] + user.current = true + userRepository.updateUser(user) + return user } return null } @@ -115,95 +116,148 @@ class UserManager internal constructor(private val userRepository: UsersReposito suspend fun createOrUpdateUser( username: String?, - token: String?, - serverUrl: String?, - displayName: String?, - pushConfigurationState: String?, - currentUser: Boolean?, - userId: String?, - internalId: Long?, - capabilities: String?, - certificateAlias: String?, - externalSignalingServer: String? + userAttributes: UserAttributes, ): LiveData { - var user = if (internalId == null && username != null && serverUrl != null) { - userRepository.getUserWithUsernameAndServer(username, serverUrl) - } else if (internalId != null) { - userRepository.getUserWithId(internalId) + var user = if (userAttributes.id == null && username != null && userAttributes.serverUrl != null) { + userRepository.getUserWithUsernameAndServer(username, userAttributes.serverUrl) + } else if (userAttributes.id != null) { + userRepository.getUserWithId(userAttributes.id) } else { null } if (user == null) { - user = UserNgEntity() - user.baseUrl = serverUrl - user.username = username - user.token = token - if (!TextUtils.isEmpty(displayName)) { - user.displayName = displayName - } - if (pushConfigurationState != null) { - user.pushConfigurationState = LoganSquare - .parse(pushConfigurationState, PushConfigurationState::class.java) - } - if (!TextUtils.isEmpty(userId)) { - user.userId = userId - } - if (!TextUtils.isEmpty(capabilities)) { - user.capabilities = LoganSquare.parse(capabilities, Capabilities::class.java) - } - if (!TextUtils.isEmpty(certificateAlias)) { - user.clientCertificate = certificateAlias - } - if (!TextUtils.isEmpty(externalSignalingServer)) { - user.externalSignalingServer = LoganSquare - .parse(externalSignalingServer, ExternalSignalingServer::class.java) - } - user.current = true + user = createUser( + username, + userAttributes + ) } else { - if (userId != null && (user.userId == null || user.userId != userId)) { - user.userId = userId - } - if (token != null && token != user.token) { - user.token = token - } - if ( - displayName != null && - user.displayName == null || - displayName != null && - (user.displayName != null) && - displayName != user.displayName - ) { - user.displayName = displayName - } - if (pushConfigurationState != null) { - val newPushConfigurationState = LoganSquare - .parse(pushConfigurationState, PushConfigurationState::class.java) - if (newPushConfigurationState != user.pushConfigurationState) { - user.pushConfigurationState = newPushConfigurationState - } - } - if (capabilities != null) { - val newCapabilities = LoganSquare.parse(capabilities, Capabilities::class.java) - if (newCapabilities != user.capabilities) { - user.capabilities = newCapabilities - } - } - if (certificateAlias != null && certificateAlias != user.clientCertificate) { - user.clientCertificate = certificateAlias - } - if (externalSignalingServer != null) { - val newExternalSignalingServer = LoganSquare - .parse(externalSignalingServer, ExternalSignalingServer::class.java) - if (newExternalSignalingServer != user.externalSignalingServer) { - user.externalSignalingServer = newExternalSignalingServer - } - } - if (currentUser != null) { - user.current = currentUser - } + updateUserData( + user, + userAttributes + ) } userRepository.insertUser(user) return userRepository.getUserWithIdLiveData(user.id) } + + private fun updateUserData(user: UserNgEntity, userAttributes: UserAttributes) { + updateUserIdIfNeeded(userAttributes, user) + updateTokenIfNeeded(userAttributes, user) + updateDisplayNameIfNeeded(userAttributes, user) + updatePushConfigurationStateIfNeeded(userAttributes, user) + updateCapabilitiesIfNeeded(userAttributes, user) + updateCertificateAliasIfNeeded(userAttributes, user) + updateExternalSignalingServerIfNeeded(userAttributes, user) + updateCurrentUserStatusIfNeeded(userAttributes, user) + } + + private fun updateCurrentUserStatusIfNeeded(userAttributes: UserAttributes, user: UserNgEntity) { + if (userAttributes.currentUser != null) { + user.current = userAttributes.currentUser + } + } + + private fun updateExternalSignalingServerIfNeeded(userAttributes: UserAttributes, user: UserNgEntity) { + if (userAttributes.externalSignalingServer != null) { + val newExternalSignalingServer = LoganSquare + .parse(userAttributes.externalSignalingServer, ExternalSignalingServer::class.java) + if (newExternalSignalingServer != user.externalSignalingServer) { + user.externalSignalingServer = newExternalSignalingServer + } + } + } + + private fun updateCertificateAliasIfNeeded(userAttributes: UserAttributes, user: UserNgEntity) { + if (userAttributes.certificateAlias != null && userAttributes.certificateAlias != user.clientCertificate) { + user.clientCertificate = userAttributes.certificateAlias + } + } + + private fun updateCapabilitiesIfNeeded(userAttributes: UserAttributes, user: UserNgEntity) { + if (userAttributes.capabilities != null) { + val newCapabilities = LoganSquare.parse(userAttributes.capabilities, Capabilities::class.java) + if (newCapabilities != user.capabilities) { + user.capabilities = newCapabilities + } + } + } + + private fun updatePushConfigurationStateIfNeeded(userAttributes: UserAttributes, user: UserNgEntity) { + if (userAttributes.pushConfigurationState != null) { + val newPushConfigurationState = LoganSquare + .parse(userAttributes.pushConfigurationState, PushConfigurationState::class.java) + if (newPushConfigurationState != user.pushConfigurationState) { + user.pushConfigurationState = newPushConfigurationState + } + } + } + + private fun updateDisplayNameIfNeeded(userAttributes: UserAttributes, user: UserNgEntity) { + if (validDisplayName(userAttributes.displayName, user)) { + user.displayName = userAttributes.displayName + } + } + + private fun updateTokenIfNeeded(userAttributes: UserAttributes, user: UserNgEntity) { + if (userAttributes.token != null && userAttributes.token != user.token) { + user.token = userAttributes.token + } + } + + private fun updateUserIdIfNeeded(userAttributes: UserAttributes, user: UserNgEntity) { + if (userAttributes.userId != null && (user.userId == null || user.userId != userAttributes.userId)) { + user.userId = userAttributes.userId + } + } + + private fun createUser(username: String?, userAttributes: UserAttributes): UserNgEntity { + val user = UserNgEntity() + user.baseUrl = userAttributes.serverUrl + user.username = username + user.token = userAttributes.token + if (!TextUtils.isEmpty(userAttributes.displayName)) { + user.displayName = userAttributes.displayName + } + if (userAttributes.pushConfigurationState != null) { + user.pushConfigurationState = LoganSquare + .parse(userAttributes.pushConfigurationState, PushConfigurationState::class.java) + } + if (!TextUtils.isEmpty(userAttributes.userId)) { + user.userId = userAttributes.userId + } + if (!TextUtils.isEmpty(userAttributes.capabilities)) { + user.capabilities = LoganSquare.parse(userAttributes.capabilities, Capabilities::class.java) + } + if (!TextUtils.isEmpty(userAttributes.certificateAlias)) { + user.clientCertificate = userAttributes.certificateAlias + } + if (!TextUtils.isEmpty(userAttributes.externalSignalingServer)) { + user.externalSignalingServer = LoganSquare + .parse(userAttributes.externalSignalingServer, ExternalSignalingServer::class.java) + } + user.current = true + return user + } + + private fun validDisplayName(displayName: String?, user: UserNgEntity): Boolean { + return if (displayName == null) { + false + } else { + user.displayName == null || user.displayName != null && displayName != user.displayName + } + } + + data class UserAttributes( + val id: Long?, + val serverUrl: String?, + val currentUser: Boolean?, + val userId: String?, + val token: String?, + val displayName: String?, + val pushConfigurationState: String?, + val capabilities: String?, + val certificateAlias: String?, + val externalSignalingServer: String? + ) } From 8c27b54377a98421b5d14998141610cef0f3123b Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 20 Jun 2022 18:33:19 +0200 Subject: [PATCH 07/55] move everything to flows Signed-off-by: Andy Scherzinger --- .idea/codeStyles/Project.xml | 3 - app/build.gradle | 1 + .../nextcloud/talk/activities/MainActivity.kt | 151 ++++++++++-------- .../talk/controllers/SettingsController.kt | 37 ++++- .../com/nextcloud/talk/data/user/UsersDao.kt | 87 +++++----- .../talk/data/user/UsersRepository.kt | 35 ++-- .../talk/data/user/UsersRepositoryImpl.kt | 54 +++---- .../com/nextcloud/talk/users/UserManager.kt | 134 ++++++++++------ .../database/user/CurrentUserProviderNew.kt | 3 +- .../talk/utils/database/user/UserModule.kt | 10 ++ 10 files changed, 306 insertions(+), 209 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 531d00c0d..c9f0f1591 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -66,9 +66,6 @@ - -