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";