From d7c50ddc2d8264b88d0103a7cc7ac0d70fd5d699 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 21 Dec 2022 17:34:46 +0100 Subject: [PATCH 001/152] Save spaces-related capabilities in database --- .../files/details/FileDetailsViewModel.kt | 2 +- .../40.json | 779 ++++++++++++++++++ .../roommigrations/MigrationToDB28Test.kt | 4 +- .../roommigrations/MigrationToDB30Test.kt | 4 +- .../roommigrations/MigrationToDB36Test.kt | 2 +- .../owncloud/android/data/OwncloudDatabase.kt | 4 +- .../owncloud/android/data/ProviderMeta.java | 4 +- .../OCLocalCapabilitiesDataSource.kt | 10 +- .../mapper/RemoteCapabilityMapper.kt | 17 +- .../capabilities/db/OCCapabilityEntity.kt | 15 +- .../data/migrations/AutoMigration39To40.kt | 63 ++ .../OCRemoteCapabilitiesDataSourceTest.kt | 2 +- .../domain/capabilities/model/OCCapability.kt | 27 +- .../capabilities/model/OCCapabilityTest.kt | 4 +- .../owncloud/android/testutil/OCCapability.kt | 5 +- 15 files changed, 902 insertions(+), 40 deletions(-) create mode 100644 owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt index 08bb7742877..9487af537ea 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt @@ -142,7 +142,7 @@ class FileDetailsViewModel( coroutineDispatcher = coroutinesDispatcherProvider.io, liveData = _openInWebUriLiveData, useCase = openInWebUseCase, - useCaseParams = GetUrlToOpenInWebUseCase.Params(openWebEndpoint = capabilities.value?.filesOcisProviders?.openWebUrl!!, fileId = fileId), + useCaseParams = GetUrlToOpenInWebUseCase.Params(openWebEndpoint = capabilities.value?.filesAppProviders?.openWebUrlAppProviders!!, fileId = fileId), showLoading = false, requiresConnection = true, ) diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json new file mode 100644 index 00000000000..b48a15cece3 --- /dev/null +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -0,0 +1,779 @@ +{ + "formatVersion": 1, + "database": { + "version": 40, + "identityHash": "f805f9babbe5ac10107c1a7dfe48e4a4", + "entities": [ + { + "tableName": "folder_backup", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountName` TEXT NOT NULL, `behavior` TEXT NOT NULL, `sourcePath` TEXT NOT NULL, `uploadPath` TEXT NOT NULL, `wifiOnly` INTEGER NOT NULL, `chargingOnly` INTEGER NOT NULL, `name` TEXT NOT NULL, `lastSyncTimestamp` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "behavior", + "columnName": "behavior", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sourcePath", + "columnName": "sourcePath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uploadPath", + "columnName": "uploadPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wifiOnly", + "columnName": "wifiOnly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "chargingOnly", + "columnName": "chargingOnly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastSyncTimestamp", + "columnName": "lastSyncTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "capabilities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` TEXT, `version_major` INTEGER NOT NULL, `version_minor` INTEGER NOT NULL, `version_micro` INTEGER NOT NULL, `version_string` TEXT, `version_edition` TEXT, `core_pollinterval` INTEGER NOT NULL, `dav_chunking_version` TEXT NOT NULL, `sharing_api_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_write` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_public_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_days` INTEGER NOT NULL, `sharing_public_expire_date_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_upload` INTEGER NOT NULL DEFAULT -1, `sharing_public_multiple` INTEGER NOT NULL DEFAULT -1, `supports_upload_only` INTEGER NOT NULL DEFAULT -1, `sharing_resharing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_outgoing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_incoming` INTEGER NOT NULL DEFAULT -1, `sharing_user_profile_picture` INTEGER NOT NULL DEFAULT -1, `files_bigfilechunking` INTEGER NOT NULL DEFAULT -1, `files_undelete` INTEGER NOT NULL DEFAULT -1, `files_versioning` INTEGER NOT NULL DEFAULT -1, `files_private_links` INTEGER NOT NULL DEFAULT -1, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `enabledAppProviders` INTEGER, `versionAppProviders` TEXT, `appsUrlAppProviders` TEXT, `openUrlAppProviders` TEXT, `openWebUrlAppProviders` TEXT, `newUrlAppProviders` TEXT, `enabledSpaces` INTEGER, `projectsSpaces` INTEGER, `shareJailSpaces` INTEGER)", + "fields": [ + { + "fieldPath": "accountName", + "columnName": "account", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "versionMajor", + "columnName": "version_major", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "versionMinor", + "columnName": "version_minor", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "versionMicro", + "columnName": "version_micro", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "versionString", + "columnName": "version_string", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "versionEdition", + "columnName": "version_edition", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "corePollInterval", + "columnName": "core_pollinterval", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "davChunkingVersion", + "columnName": "dav_chunking_version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filesSharingApiEnabled", + "columnName": "sharing_api_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicEnabled", + "columnName": "sharing_public_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicPasswordEnforced", + "columnName": "sharing_public_password_enforced", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicPasswordEnforcedReadOnly", + "columnName": "sharing_public_password_enforced_read_only", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicPasswordEnforcedReadWrite", + "columnName": "sharing_public_password_enforced_read_write", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicPasswordEnforcedUploadOnly", + "columnName": "sharing_public_password_enforced_public_only", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicExpireDateEnabled", + "columnName": "sharing_public_expire_date_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicExpireDateDays", + "columnName": "sharing_public_expire_date_days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "filesSharingPublicExpireDateEnforced", + "columnName": "sharing_public_expire_date_enforced", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicUpload", + "columnName": "sharing_public_upload", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicMultiple", + "columnName": "sharing_public_multiple", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingPublicSupportsUploadOnly", + "columnName": "supports_upload_only", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingResharing", + "columnName": "sharing_resharing", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingFederationOutgoing", + "columnName": "sharing_federation_outgoing", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingFederationIncoming", + "columnName": "sharing_federation_incoming", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesSharingUserProfilePicture", + "columnName": "sharing_user_profile_picture", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesBigFileChunking", + "columnName": "files_bigfilechunking", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesUndelete", + "columnName": "files_undelete", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesVersioning", + "columnName": "files_versioning", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "filesPrivateLinks", + "columnName": "files_private_links", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "appProviders.enabledAppProviders", + "columnName": "enabledAppProviders", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "appProviders.versionAppProviders", + "columnName": "versionAppProviders", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "appProviders.appsUrlAppProviders", + "columnName": "appsUrlAppProviders", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "appProviders.openUrlAppProviders", + "columnName": "openUrlAppProviders", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "appProviders.openWebUrlAppProviders", + "columnName": "openWebUrlAppProviders", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "appProviders.newUrlAppProviders", + "columnName": "newUrlAppProviders", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "spaces.enabledSpaces", + "columnName": "enabledSpaces", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "spaces.projectsSpaces", + "columnName": "projectsSpaces", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "spaces.shareJailSpaces", + "columnName": "shareJailSpaces", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "files", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`parentId` INTEGER, `owner` TEXT NOT NULL, `remotePath` TEXT NOT NULL, `remoteId` TEXT, `length` INTEGER NOT NULL, `creationTimestamp` INTEGER, `modificationTimestamp` INTEGER NOT NULL, `mimeType` TEXT NOT NULL, `etag` TEXT, `permissions` TEXT, `privateLink` TEXT, `storagePath` TEXT, `name` TEXT, `treeEtag` TEXT, `keepInSync` INTEGER, `lastSyncDateForData` INTEGER, `fileShareViaLink` INTEGER, `lastSyncDateForProperties` INTEGER, `needsToUpdateThumbnail` INTEGER NOT NULL, `modifiedAtLastSyncForData` INTEGER, `etagInConflict` TEXT, `fileIsDownloading` INTEGER, `sharedWithSharee` INTEGER, `sharedByLink` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "parentId", + "columnName": "parentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "owner", + "columnName": "owner", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remotePath", + "columnName": "remotePath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remoteId", + "columnName": "remoteId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "length", + "columnName": "length", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "creationTimestamp", + "columnName": "creationTimestamp", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modificationTimestamp", + "columnName": "modificationTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mimeType", + "columnName": "mimeType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "etag", + "columnName": "etag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "privateLink", + "columnName": "privateLink", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "storagePath", + "columnName": "storagePath", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "treeEtag", + "columnName": "treeEtag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "availableOfflineStatus", + "columnName": "keepInSync", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastSyncDateForData", + "columnName": "lastSyncDateForData", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "fileShareViaLink", + "columnName": "fileShareViaLink", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastSyncDateForProperties", + "columnName": "lastSyncDateForProperties", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "needsToUpdateThumbnail", + "columnName": "needsToUpdateThumbnail", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "modifiedAtLastSyncForData", + "columnName": "modifiedAtLastSyncForData", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "etagInConflict", + "columnName": "etagInConflict", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "fileIsDownloading", + "columnName": "fileIsDownloading", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sharedWithSharee", + "columnName": "sharedWithSharee", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sharedByLink", + "columnName": "sharedByLink", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "files_sync", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`fileId` INTEGER NOT NULL, `uploadWorkerUuid` BLOB, `downloadWorkerUuid` BLOB, `isSynchronizing` INTEGER NOT NULL, PRIMARY KEY(`fileId`), FOREIGN KEY(`fileId`) REFERENCES `files`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "fileId", + "columnName": "fileId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uploadWorkerUuid", + "columnName": "uploadWorkerUuid", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "downloadWorkerUuid", + "columnName": "downloadWorkerUuid", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "isSynchronizing", + "columnName": "isSynchronizing", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "fileId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "files", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "fileId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "ocshares", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`share_type` INTEGER NOT NULL, `share_with` TEXT, `path` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `shared_date` INTEGER NOT NULL, `expiration_date` INTEGER NOT NULL, `token` TEXT, `shared_with_display_name` TEXT, `share_with_additional_info` TEXT, `is_directory` INTEGER NOT NULL, `id_remote_shared` TEXT NOT NULL, `owner_share` TEXT NOT NULL, `name` TEXT, `url` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "shareType", + "columnName": "share_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shareWith", + "columnName": "share_with", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sharedDate", + "columnName": "shared_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expirationDate", + "columnName": "expiration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sharedWithDisplayName", + "columnName": "shared_with_display_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sharedWithAdditionalInfo", + "columnName": "share_with_additional_info", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isFolder", + "columnName": "is_directory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteId", + "columnName": "id_remote_shared", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountOwner", + "columnName": "owner_share", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shareLink", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "user_quotas", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountName` TEXT NOT NULL, `used` INTEGER NOT NULL, `available` INTEGER NOT NULL, PRIMARY KEY(`accountName`))", + "fields": [ + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "used", + "columnName": "used", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "available", + "columnName": "available", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "accountName" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "transfers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localPath` TEXT NOT NULL, `remotePath` TEXT NOT NULL, `accountName` TEXT NOT NULL, `fileSize` INTEGER NOT NULL, `status` INTEGER NOT NULL, `localBehaviour` INTEGER NOT NULL, `forceOverwrite` INTEGER NOT NULL, `transferEndTimestamp` INTEGER, `lastResult` INTEGER, `createdBy` INTEGER NOT NULL, `transferId` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "localPath", + "columnName": "localPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remotePath", + "columnName": "remotePath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fileSize", + "columnName": "fileSize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localBehaviour", + "columnName": "localBehaviour", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "forceOverwrite", + "columnName": "forceOverwrite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "transferEndTimestamp", + "columnName": "transferEndTimestamp", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastResult", + "columnName": "lastResult", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "createdBy", + "columnName": "createdBy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "transferId", + "columnName": "transferId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f805f9babbe5ac10107c1a7dfe48e4a4')" + ] + } +} \ No newline at end of file diff --git a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB28Test.kt b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB28Test.kt index dba340f61ac..5a4625561e2 100644 --- a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB28Test.kt +++ b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB28Test.kt @@ -28,7 +28,7 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_ACC import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_CORE_POLLINTERVAL import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME -import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAYOR +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAJOR import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MICRO import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MINOR import com.owncloud.android.data.migrations.MIGRATION_27_28 @@ -84,7 +84,7 @@ class MigrationToDB28Test : MigrationTest() { companion object { val cvWithDefaultValues = ContentValues().apply { put(CAPABILITIES_ACCOUNT_NAME, OC_CAPABILITY.accountName) - put(CAPABILITIES_VERSION_MAYOR, OC_CAPABILITY.versionMayor) + put(CAPABILITIES_VERSION_MAJOR, OC_CAPABILITY.versionMajor) put(CAPABILITIES_VERSION_MINOR, OC_CAPABILITY.versionMinor) put(CAPABILITIES_VERSION_MICRO, OC_CAPABILITY.versionMicro) put(CAPABILITIES_CORE_POLLINTERVAL, OC_CAPABILITY.corePollInterval) diff --git a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB30Test.kt b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB30Test.kt index 3123e66d8f5..1592cfb9a80 100644 --- a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB30Test.kt +++ b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB30Test.kt @@ -30,7 +30,7 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_COR import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_DAV_CHUNKING_VERSION import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME -import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAYOR +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAJOR import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MICRO import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MINOR import com.owncloud.android.testutil.OC_CAPABILITY @@ -107,7 +107,7 @@ class MigrationToDB30Test : MigrationTest() { companion object { val cvWithDefaultValues = ContentValues().apply { put(CAPABILITIES_ACCOUNT_NAME, OC_CAPABILITY.accountName) - put(CAPABILITIES_VERSION_MAYOR, OC_CAPABILITY.versionMayor) + put(CAPABILITIES_VERSION_MAJOR, OC_CAPABILITY.versionMajor) put(CAPABILITIES_VERSION_MINOR, OC_CAPABILITY.versionMinor) put(CAPABILITIES_VERSION_MICRO, OC_CAPABILITY.versionMicro) put(CAPABILITIES_CORE_POLLINTERVAL, OC_CAPABILITY.corePollInterval) diff --git a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB36Test.kt b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB36Test.kt index da319b23592..7e4fc0fb7e3 100644 --- a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB36Test.kt +++ b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB36Test.kt @@ -76,7 +76,7 @@ class MigrationToDB36Test : MigrationTest() { "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", arrayOf( OC_CAPABILITY.accountName, - OC_CAPABILITY.versionMayor, + OC_CAPABILITY.versionMajor, OC_CAPABILITY.versionMinor, OC_CAPABILITY.versionMicro, OC_CAPABILITY.versionString, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/OwncloudDatabase.kt b/owncloudData/src/main/java/com/owncloud/android/data/OwncloudDatabase.kt index d655619db47..8ab36962772 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/OwncloudDatabase.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/OwncloudDatabase.kt @@ -35,6 +35,7 @@ import com.owncloud.android.data.files.db.OCFileEntity import com.owncloud.android.data.files.db.OCFileSyncEntity import com.owncloud.android.data.folderbackup.db.FolderBackUpEntity import com.owncloud.android.data.folderbackup.db.FolderBackupDao +import com.owncloud.android.data.migrations.AutoMigration39To40 import com.owncloud.android.data.migrations.MIGRATION_27_28 import com.owncloud.android.data.migrations.MIGRATION_28_29 import com.owncloud.android.data.migrations.MIGRATION_29_30 @@ -64,7 +65,8 @@ import com.owncloud.android.data.user.db.UserQuotaEntity ], autoMigrations = [ AutoMigration(from = 36, to = 37), - AutoMigration(from = 38, to = 39) + AutoMigration(from = 38, to = 39), + AutoMigration(from = 39, to = 40, spec = AutoMigration39To40::class), ], version = ProviderMeta.DB_VERSION, exportSchema = true diff --git a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java index 08d58a3f4aa..c7901b225bc 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java +++ b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java @@ -31,7 +31,7 @@ public class ProviderMeta { public static final String DB_NAME = "filelist"; public static final String NEW_DB_NAME = "owncloud_database"; - public static final int DB_VERSION = 39; + public static final int DB_VERSION = 40; private ProviderMeta() { } @@ -63,7 +63,7 @@ static public class ProviderTableMeta implements BaseColumns { // Columns of capabilities table public static final String CAPABILITIES_ACCOUNT_NAME = "account"; - public static final String CAPABILITIES_VERSION_MAYOR = "version_mayor"; + public static final String CAPABILITIES_VERSION_MAJOR = "version_major"; public static final String CAPABILITIES_VERSION_MINOR = "version_minor"; public static final String CAPABILITIES_VERSION_MICRO = "version_micro"; public static final String CAPABILITIES_VERSION_STRING = "version_string"; diff --git a/owncloudData/src/main/java/com/owncloud/android/data/capabilities/datasources/implementation/OCLocalCapabilitiesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/capabilities/datasources/implementation/OCLocalCapabilitiesDataSource.kt index afc79cdae09..15e5f5b4086 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/capabilities/datasources/implementation/OCLocalCapabilitiesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/capabilities/datasources/implementation/OCLocalCapabilitiesDataSource.kt @@ -56,7 +56,7 @@ class OCLocalCapabilitiesDataSource( OCCapability( id = id, accountName = accountName, - versionMayor = versionMayor, + versionMajor = versionMajor, versionMinor = versionMinor, versionMicro = versionMicro, versionString = versionString, @@ -83,14 +83,15 @@ class OCLocalCapabilitiesDataSource( filesUndelete = CapabilityBooleanType.fromValue(filesUndelete), filesVersioning = CapabilityBooleanType.fromValue(filesVersioning), filesPrivateLinks = CapabilityBooleanType.fromValue(filesPrivateLinks), - filesOcisProviders = ocisProvider, + filesAppProviders = appProviders, + spaces = spaces, ) @VisibleForTesting fun OCCapability.toEntity(): OCCapabilityEntity = OCCapabilityEntity( accountName = accountName, - versionMayor = versionMayor, + versionMajor = versionMajor, versionMinor = versionMinor, versionMicro = versionMicro, versionString = versionString, @@ -117,7 +118,8 @@ class OCLocalCapabilitiesDataSource( filesUndelete = filesUndelete.value, filesVersioning = filesVersioning.value, filesPrivateLinks = filesPrivateLinks.value, - ocisProvider = filesOcisProviders, + appProviders = filesAppProviders, + spaces = spaces ) } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/capabilities/datasources/mapper/RemoteCapabilityMapper.kt b/owncloudData/src/main/java/com/owncloud/android/data/capabilities/datasources/mapper/RemoteCapabilityMapper.kt index 1d69f2ee553..a7c3d7e3ed7 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/capabilities/datasources/mapper/RemoteCapabilityMapper.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/capabilities/datasources/mapper/RemoteCapabilityMapper.kt @@ -30,7 +30,7 @@ class RemoteCapabilityMapper : RemoteMapper { remote?.let { OCCapability( accountName = remote.accountName, - versionMayor = remote.versionMayor, + versionMajor = remote.versionMajor, versionMinor = remote.versionMinor, versionMicro = remote.versionMicro, versionString = remote.versionString, @@ -66,7 +66,8 @@ class RemoteCapabilityMapper : RemoteMapper { filesUndelete = CapabilityBooleanType.fromValue(remote.filesUndelete.value), filesVersioning = CapabilityBooleanType.fromValue(remote.filesVersioning.value), filesPrivateLinks = CapabilityBooleanType.fromValue(remote.filesPrivateLinks.value), - filesOcisProviders = remote.filesAppProviders?.firstOrNull()?.toOCISProvider() + filesAppProviders = remote.filesAppProviders?.firstOrNull()?.toAppProviders(), + spaces = remote.spaces?.toSpaces(), ) } @@ -74,7 +75,7 @@ class RemoteCapabilityMapper : RemoteMapper { model?.let { RemoteCapability( accountName = model.accountName!!, - versionMayor = model.versionMayor, + versionMajor = model.versionMajor, versionMinor = model.versionMinor, versionMicro = model.versionMicro, versionString = model.versionString!!, @@ -110,11 +111,15 @@ class RemoteCapabilityMapper : RemoteMapper { filesUndelete = RemoteCapabilityBooleanType.fromValue(model.filesUndelete.value)!!, filesVersioning = RemoteCapabilityBooleanType.fromValue(model.filesVersioning.value)!!, filesPrivateLinks = RemoteCapabilityBooleanType.fromValue(model.filesPrivateLinks.value)!!, - filesAppProviders = null + filesAppProviders = null, + spaces = null, ) } - private fun RemoteCapability.RemoteOCISProvider.toOCISProvider() = - OCCapability.OcisProvider(enabled, version, appsUrl, openUrl, openWebUrl, newUrl) + private fun RemoteCapability.RemoteAppProviders.toAppProviders() = + OCCapability.AppProviders(enabled, version, appsUrl, openUrl, openWebUrl, newUrl) + + private fun RemoteCapability.RemoteSpaces.toSpaces() = + OCCapability.Spaces(enabled, projects, shareJail) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/capabilities/db/OCCapabilityEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/capabilities/db/OCCapabilityEntity.kt index 1fa49f09d85..cc23bcaa132 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/capabilities/db/OCCapabilityEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/capabilities/db/OCCapabilityEntity.kt @@ -50,7 +50,7 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHA import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_USER_PROFILE_PICTURE import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_EDITION -import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAYOR +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAJOR import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MICRO import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MINOR import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_STRING @@ -65,8 +65,8 @@ import com.owncloud.android.domain.capabilities.model.OCCapability data class OCCapabilityEntity( @ColumnInfo(name = CAPABILITIES_ACCOUNT_NAME) val accountName: String?, - @ColumnInfo(name = CAPABILITIES_VERSION_MAYOR) - val versionMayor: Int, + @ColumnInfo(name = CAPABILITIES_VERSION_MAJOR) + val versionMajor: Int, @ColumnInfo(name = CAPABILITIES_VERSION_MINOR) val versionMinor: Int, @ColumnInfo(name = CAPABILITIES_VERSION_MICRO) @@ -120,7 +120,9 @@ data class OCCapabilityEntity( @ColumnInfo(name = CAPABILITIES_FILES_PRIVATE_LINKS, defaultValue = capabilityBooleanTypeUnknownString) val filesPrivateLinks: Int, @Embedded - val ocisProvider: OCCapability.OcisProvider? + val appProviders: OCCapability.AppProviders?, + @Embedded + val spaces: OCCapability.Spaces?, ) { @PrimaryKey(autoGenerate = true) var id: Int = 0 @@ -128,7 +130,7 @@ data class OCCapabilityEntity( fun fromCursor(cursor: Cursor): OCCapabilityEntity = cursor.use { it -> OCCapabilityEntity( it.getString(it.getColumnIndexOrThrow(CAPABILITIES_ACCOUNT_NAME)), - it.getInt(it.getColumnIndexOrThrow(CAPABILITIES_VERSION_MAYOR)), + it.getInt(it.getColumnIndexOrThrow(CAPABILITIES_VERSION_MAJOR)), it.getInt(it.getColumnIndexOrThrow(CAPABILITIES_VERSION_MINOR)), it.getInt(it.getColumnIndexOrThrow(CAPABILITIES_VERSION_MICRO)), it.getString(it.getColumnIndexOrThrow(CAPABILITIES_VERSION_STRING)), @@ -156,7 +158,8 @@ data class OCCapabilityEntity( it.getInt(it.getColumnIndexOrThrow(CAPABILITIES_FILES_VERSIONING)), it.getColumnIndex(CAPABILITIES_FILES_PRIVATE_LINKS).takeUnless { it < 0 }?.let { index -> it.getInt(index) } ?: CapabilityBooleanType.UNKNOWN.value, - null + null, + null, ) } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt b/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt new file mode 100644 index 00000000000..ca7b6e55c04 --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt @@ -0,0 +1,63 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.migrations + +import androidx.room.RenameColumn +import androidx.room.migration.AutoMigrationSpec +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAJOR + +@RenameColumn( + tableName = CAPABILITIES_TABLE_NAME, + fromColumnName = "version_mayor", + toColumnName = CAPABILITIES_VERSION_MAJOR +) +@RenameColumn( + tableName = CAPABILITIES_TABLE_NAME, + fromColumnName = "enabled", + toColumnName = "enabledAppProviders" +) +@RenameColumn( + tableName = CAPABILITIES_TABLE_NAME, + fromColumnName = "version", + toColumnName = "versionAppProviders" +) +@RenameColumn( + tableName = CAPABILITIES_TABLE_NAME, + fromColumnName = "appsUrl", + toColumnName = "appsUrlAppProviders" +) +@RenameColumn( + tableName = CAPABILITIES_TABLE_NAME, + fromColumnName = "openUrl", + toColumnName = "openUrlAppProviders" +) +@RenameColumn( + tableName = CAPABILITIES_TABLE_NAME, + fromColumnName = "openWebUrl", + toColumnName = "openWebUrlAppProviders" +) +@RenameColumn( + tableName = CAPABILITIES_TABLE_NAME, + fromColumnName = "newUrl", + toColumnName = "newUrlAppProviders" +) +class AutoMigration39To40: AutoMigrationSpec diff --git a/owncloudData/src/test/java/com/owncloud/android/data/capabilities/datasources/OCRemoteCapabilitiesDataSourceTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/capabilities/datasources/OCRemoteCapabilitiesDataSourceTest.kt index ddc51630ddb..e23dc447c2e 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/capabilities/datasources/OCRemoteCapabilitiesDataSourceTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/capabilities/datasources/OCRemoteCapabilitiesDataSourceTest.kt @@ -68,7 +68,7 @@ class OCRemoteCapabilitiesDataSourceTest { assertNotNull(capabilities) assertEquals(OC_CAPABILITY.accountName, capabilities.accountName) - assertEquals(OC_CAPABILITY.versionMayor, capabilities.versionMayor) + assertEquals(OC_CAPABILITY.versionMajor, capabilities.versionMajor) assertEquals(OC_CAPABILITY.versionMinor, capabilities.versionMinor) assertEquals(OC_CAPABILITY.versionMicro, capabilities.versionMicro) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt index 3d194199fc1..691034d0b1e 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt @@ -22,7 +22,7 @@ package com.owncloud.android.domain.capabilities.model data class OCCapability( val id: Int? = null, var accountName: String?, - val versionMayor: Int, + val versionMajor: Int, val versionMinor: Int, val versionMicro: Int, val versionString: String?, @@ -49,7 +49,8 @@ data class OCCapability( val filesUndelete: CapabilityBooleanType, val filesVersioning: CapabilityBooleanType, val filesPrivateLinks: CapabilityBooleanType, - val filesOcisProviders: OcisProvider?, + val filesAppProviders: AppProviders?, + val spaces: Spaces?, ) { fun isChunkingAllowed(): Boolean { val doubleChunkingVersion = davChunkingVersion.toDoubleOrNull() @@ -60,15 +61,21 @@ data class OCCapability( return filesSharingUserProfilePicture.isTrue || filesSharingUserProfilePicture.isUnknown } - fun isOpenInWebAllowed(): Boolean = filesOcisProviders?.openWebUrl?.isNotBlank() ?: false + fun isOpenInWebAllowed(): Boolean = filesAppProviders?.openWebUrlAppProviders?.isNotBlank() ?: false - data class OcisProvider( - val enabled: Boolean, - val version: String, - val appsUrl: String?, - val openUrl: String?, - val openWebUrl: String?, - val newUrl: String?, + data class AppProviders( + val enabledAppProviders: Boolean, + val versionAppProviders: String, + val appsUrlAppProviders: String?, + val openUrlAppProviders: String?, + val openWebUrlAppProviders: String?, + val newUrlAppProviders: String?, + ) + + data class Spaces( + val enabledSpaces: Boolean, + val projectsSpaces: Boolean, + val shareJailSpaces: Boolean ) } diff --git a/owncloudDomain/src/test/java/com/owncloud/android/domain/capabilities/model/OCCapabilityTest.kt b/owncloudDomain/src/test/java/com/owncloud/android/domain/capabilities/model/OCCapabilityTest.kt index a91a05c8230..064ae3c411d 100644 --- a/owncloudDomain/src/test/java/com/owncloud/android/domain/capabilities/model/OCCapabilityTest.kt +++ b/owncloudDomain/src/test/java/com/owncloud/android/domain/capabilities/model/OCCapabilityTest.kt @@ -76,10 +76,10 @@ class OCCapabilityTest { @Test fun isOpenInWebAllowed() { - val item1 = OC_CAPABILITY.copy(filesOcisProviders = OCCapability.OcisProvider(true, "", null, null, "/open-with-web", null)) + val item1 = OC_CAPABILITY.copy(filesAppProviders = OCCapability.AppProviders(true, "", null, null, "/open-with-web", null)) assertTrue(item1.isOpenInWebAllowed()) - val item2 = OC_CAPABILITY.copy(filesOcisProviders = null) + val item2 = OC_CAPABILITY.copy(filesAppProviders = null) assertFalse(item2.isOpenInWebAllowed()) } } diff --git a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCCapability.kt b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCCapability.kt index fc436a387c0..6e342254b03 100644 --- a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCCapability.kt +++ b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCCapability.kt @@ -25,7 +25,7 @@ import com.owncloud.android.domain.capabilities.model.OCCapability val OC_CAPABILITY = OCCapability( accountName = OC_ACCOUNT_NAME, - versionMayor = 2, + versionMajor = 2, versionMinor = 1, versionMicro = 0, versionString = "1.0.0", @@ -52,5 +52,6 @@ val OC_CAPABILITY = filesUndelete = CapabilityBooleanType.FALSE, filesVersioning = CapabilityBooleanType.FALSE, filesPrivateLinks = CapabilityBooleanType.TRUE, - filesOcisProviders = null, + filesAppProviders = null, + spaces = null ) From 62b08fc531e5a4a1d4d92dfba6d1367c2c76104e Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 21 Dec 2022 17:35:07 +0100 Subject: [PATCH 002/152] Update library reference --- owncloud-android-library | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owncloud-android-library b/owncloud-android-library index 602b7e75480..159dcd6d689 160000 --- a/owncloud-android-library +++ b/owncloud-android-library @@ -1 +1 @@ -Subproject commit 602b7e75480e8db7996e4316e77805911c8d48da +Subproject commit 159dcd6d68983ba63db235819061531f29558e62 From 3310bd73a32c9862d9d446f71d461a586e8d88f9 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Fri, 23 Dec 2022 14:49:43 +0100 Subject: [PATCH 003/152] Added new fragment for spaces list and empty view --- .../android/extensions/FileListOptionExt.kt | 3 + .../files/filelist/MainFileListViewModel.kt | 5 ++ .../presentation/spaces/SpacesListFragment.kt | 63 +++++++++++++++ .../android/ui/activity/DrawerActivity.kt | 30 +++++++ .../android/ui/activity/FileActivity.java | 6 ++ .../ui/activity/FileDisplayActivity.kt | 40 ++++++++-- .../src/main/res/drawable/ic_spaces.xml | 9 +++ .../main/res/layout/spaces_list_fragment.xml | 78 +++++++++++++++++++ .../src/main/res/menu/bottom_navbar_menu.xml | 5 ++ owncloudApp/src/main/res/values/strings.xml | 5 ++ .../data/files/repository/OCFileRepository.kt | 1 + .../domain/capabilities/model/OCCapability.kt | 4 + .../domain/files/model/FileListOption.kt | 3 +- 13 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt create mode 100644 owncloudApp/src/main/res/drawable/ic_spaces.xml create mode 100644 owncloudApp/src/main/res/layout/spaces_list_fragment.xml diff --git a/owncloudApp/src/main/java/com/owncloud/android/extensions/FileListOptionExt.kt b/owncloudApp/src/main/java/com/owncloud/android/extensions/FileListOptionExt.kt index e0f6e1109da..02fc881793d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/extensions/FileListOptionExt.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/extensions/FileListOptionExt.kt @@ -26,6 +26,7 @@ import com.owncloud.android.domain.files.model.FileListOption @StringRes fun FileListOption.toTitleStringRes(): Int = when (this) { FileListOption.ALL_FILES -> R.string.file_list_empty_title_all_files + FileListOption.SPACES_LIST -> R.string.spaces_list_empty_title FileListOption.SHARED_BY_LINK -> R.string.file_list_empty_title_shared_by_links FileListOption.AV_OFFLINE -> R.string.file_list_empty_title_available_offline } @@ -33,6 +34,7 @@ fun FileListOption.toTitleStringRes(): Int = when (this) { @StringRes fun FileListOption.toSubtitleStringRes(): Int = when (this) { FileListOption.ALL_FILES -> R.string.file_list_empty_subtitle_all_files + FileListOption.SPACES_LIST -> R.string.spaces_list_empty_subtitle FileListOption.SHARED_BY_LINK -> R.string.file_list_empty_subtitle_shared_by_links FileListOption.AV_OFFLINE -> R.string.file_list_empty_subtitle_available_offline } @@ -40,6 +42,7 @@ fun FileListOption.toSubtitleStringRes(): Int = when (this) { @DrawableRes fun FileListOption.toDrawableRes(): Int = when (this) { FileListOption.ALL_FILES -> R.drawable.ic_folder + FileListOption.SPACES_LIST -> R.drawable.ic_spaces FileListOption.SHARED_BY_LINK -> R.drawable.ic_shared_by_link FileListOption.AV_OFFLINE -> R.drawable.ic_available_offline } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt index 55e2b678b58..5e4fcb11ee7 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt @@ -182,6 +182,10 @@ class MainFileListViewModel( getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(fileById.owner, ROOT_PATH)).getDataOrNull() } else fileById } + FileListOption.SPACES_LIST -> { + // TODO: Spaces is not applicable here at the moment + parentDir = TODO() + } } } else if (parentId == ROOT_PARENT_ID.toLong()) { // Browsing to parent folder. Root @@ -231,6 +235,7 @@ class MainFileListViewModel( FileListOption.ALL_FILES -> retrieveFlowForAllFiles(currentFolderDisplayed, accountName) FileListOption.SHARED_BY_LINK -> retrieveFlowForShareByLink(currentFolderDisplayed, accountName) FileListOption.AV_OFFLINE -> retrieveFlowForAvailableOffline(currentFolderDisplayed, accountName) + FileListOption.SPACES_LIST -> TODO() }.toFileListUiState( currentFolderDisplayed, accountName, diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt new file mode 100644 index 00000000000..8f8f7ed28f4 --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -0,0 +1,63 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.presentation.spaces + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import com.owncloud.android.databinding.SpacesListFragmentBinding +import com.owncloud.android.domain.files.model.FileListOption +import com.owncloud.android.extensions.toDrawableRes +import com.owncloud.android.extensions.toSubtitleStringRes +import com.owncloud.android.extensions.toTitleStringRes + +class SpacesListFragment : Fragment() { + private var _binding: SpacesListFragmentBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = SpacesListFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + showOrHideEmptyView() + } + + // TODO: Use this method only when necessary, for the moment the empty view is shown always + private fun showOrHideEmptyView() { + binding.recyclerSpacesList.isVisible = false + + with(binding.emptyDataParent) { + root.isVisible = true + listEmptyDatasetIcon.setImageResource(FileListOption.SPACES_LIST.toDrawableRes()) + listEmptyDatasetTitle.setText(FileListOption.SPACES_LIST.toTitleStringRes()) + listEmptyDatasetSubTitle.setText(FileListOption.SPACES_LIST.toSubtitleStringRes()) + } + } +} diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt index db6bc3bfe2b..e85f2248be6 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt @@ -43,6 +43,7 @@ import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.widget.AppCompatImageView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.GravityCompat +import androidx.core.view.get import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout.DrawerListener @@ -50,7 +51,9 @@ import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.navigation.NavigationView import com.owncloud.android.BuildConfig import com.owncloud.android.R +import com.owncloud.android.domain.capabilities.model.OCCapability import com.owncloud.android.domain.files.model.FileListOption +import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.goToUrl import com.owncloud.android.extensions.openPrivacyPolicy import com.owncloud.android.extensions.sendEmail @@ -62,9 +65,11 @@ import com.owncloud.android.presentation.avatar.AvatarUtils import com.owncloud.android.presentation.accounts.AccountsManagementActivity import com.owncloud.android.presentation.accounts.AccountsManagementActivity.Companion.KEY_ACCOUNT_LIST_CHANGED import com.owncloud.android.presentation.accounts.AccountsManagementActivity.Companion.KEY_CURRENT_ACCOUNT_CHANGED +import com.owncloud.android.presentation.capabilities.CapabilityViewModel import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.PreferenceUtils import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import timber.log.Timber import kotlin.math.ceil @@ -75,6 +80,11 @@ import kotlin.math.ceil abstract class DrawerActivity : ToolbarActivity() { private val drawerViewModel by viewModel() + private val capabilitiesViewModel by viewModel { + parametersOf( + account?.name + ) + } private var menuAccountAvatarRadiusDimension = 0f private var currentAccountAvatarRadiusDimension = 0f @@ -212,6 +222,19 @@ abstract class DrawerActivity : ToolbarActivity() { open fun setupNavigationBottomBar(menuItemId: Int) { // Allow or disallow touches with other visible windows getBottomNavigationView()?.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) + if (account != null) { + capabilitiesViewModel.capabilities.observe(this, Event.EventObserver { uiResult: UIResult -> + if (uiResult is UIResult.Success) { + val capabilities = uiResult.data + if (capabilities?.isSpacesAllowed() == true) { + getBottomNavigationView()?.menu?.get(0)?.title = getString(R.string.bottom_nav_personal) + getBottomNavigationView()?.menu?.get(1)?.isVisible = capabilities.isSpacesProjectsAllowed() + } else { + getBottomNavigationView()?.menu?.get(0)?.title = getString(R.string.bottom_nav_files) + } + } + }) + } setCheckedItemAtBottomBar(menuItemId) getBottomNavigationView()?.setOnNavigationItemSelectedListener { menuItem: MenuItem -> bottomBarNavigationTo(menuItem.itemId, getBottomNavigationView()?.selectedItemId == menuItem.itemId) @@ -222,6 +245,7 @@ abstract class DrawerActivity : ToolbarActivity() { private fun bottomBarNavigationTo(menuItemId: Int, isCurrentOptionActive: Boolean) { when (menuItemId) { R.id.nav_all_files -> navigateToOption(FileListOption.ALL_FILES) + R.id.nav_spaces -> navigateToOption(FileListOption.SPACES_LIST) R.id.nav_uploads -> if (!isCurrentOptionActive) { val uploadListIntent = Intent(applicationContext, UploadListActivity::class.java) uploadListIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) @@ -673,6 +697,12 @@ abstract class DrawerActivity : ToolbarActivity() { */ protected abstract fun restart() + /** + * Checks if the spaces tab is currently selected + */ + protected fun isSpacesTabSelected() = + getBottomNavigationView()?.menu?.findItem(R.id.nav_spaces)?.isChecked == true + companion object { private const val KEY_IS_ACCOUNT_CHOOSER_ACTIVE = "IS_ACCOUNT_CHOOSER_ACTIVE" private const val KEY_CHECKED_MENU_ITEM = "CHECKED_MENU_ITEM" diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index 6ca0ec51274..e060ad6a30a 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -415,6 +415,12 @@ public void navigateToOption(FileListOption fileListOption) { case ALL_FILES: restart(); break; + case SPACES_LIST: + intent = new Intent(this, FileDisplayActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(EXTRA_FILE_LIST_OPTION, (Parcelable) FileListOption.SPACES_LIST); + startActivity(intent); + break; case SHARED_BY_LINK: intent = new Intent(this, FileDisplayActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index b9b491e369a..a22b00c7e3e 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -85,6 +85,7 @@ import com.owncloud.android.presentation.files.operations.FileOperation import com.owncloud.android.presentation.files.operations.FileOperationsViewModel import com.owncloud.android.presentation.security.bayPassUnlockOnce import com.owncloud.android.presentation.capabilities.CapabilityViewModel +import com.owncloud.android.presentation.spaces.SpacesListFragment import com.owncloud.android.presentation.transfers.TransfersViewModel import com.owncloud.android.providers.WorkManagerProvider import com.owncloud.android.syncadapter.FileSyncAdapter @@ -143,6 +144,9 @@ class FileDisplayActivity : FileActivity(), private val listMainFileFragment: MainFileListFragment? get() = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FILES) as MainFileListFragment? + private val spacesListFragment: SpacesListFragment? + get() = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_SPACES) as SpacesListFragment? + private val secondFragment: FileFragment? get() = supportFragmentManager.findFragmentByTag(TAG_SECOND_FRAGMENT) as FileFragment? @@ -254,7 +258,11 @@ class FileDisplayActivity : FileActivity(), capabilitiesViewModel.capabilities.observe(this, Event.EventObserver { onCapabilitiesOperationFinish(it) }) - initAndShowListOfFiles() + if (isSpacesTabSelected()) { + initAndShowListOfSpaces() + } else { + initAndShowListOfFiles() + } } startListeningToOperations() @@ -328,7 +336,14 @@ class FileDisplayActivity : FileActivity(), setSearchListener(findViewById(R.id.root_toolbar_search_view)) } val transaction = supportFragmentManager.beginTransaction() - transaction.add(R.id.left_fragment_container, mainListOfFiles, TAG_LIST_OF_FILES) + transaction.replace(R.id.left_fragment_container, mainListOfFiles, TAG_LIST_OF_FILES) + transaction.commit() + } + + private fun initAndShowListOfSpaces() { + val listOfSpaces = SpacesListFragment() + val transaction = supportFragmentManager.beginTransaction() + transaction.replace(R.id.left_fragment_container, listOfSpaces, TAG_LIST_OF_SPACES) transaction.commit() } @@ -685,7 +700,9 @@ class FileDisplayActivity : FileActivity(), super.onResume() setCheckedItemAtBottomBar(getMenuItemForFileListOption(fileListOption)) - listMainFileFragment?.updateFileListOption(fileListOption, file) + if (!isSpacesTabSelected()) { + listMainFileFragment?.updateFileListOption(fileListOption, file) + } // refresh list of files refreshListOfFilesFragment() @@ -844,6 +861,7 @@ class FileDisplayActivity : FileActivity(), FileListOption.AV_OFFLINE -> getString(R.string.drawer_item_only_available_offline) FileListOption.SHARED_BY_LINK -> getString(R.string.drawer_item_shared_by_link_files) FileListOption.ALL_FILES -> getString(R.string.default_display_name_for_root_folder) + FileListOption.SPACES_LIST -> getString(R.string.drawer_item_spaces) } setupRootToolbar(title, isSearchEnabled = true) listMainFileFragment?.setSearchListener(findViewById(R.id.root_toolbar_search_view)) @@ -1326,15 +1344,25 @@ class FileDisplayActivity : FileActivity(), private fun navigateTo(newFileListOption: FileListOption) { if (fileListOption != newFileListOption) { - if (listMainFileFragment != null) { + if (newFileListOption == FileListOption.SPACES_LIST) { + initAndShowListOfSpaces() + fileListOption = FileListOption.SPACES_LIST + updateToolbar(null) + } + else if (listMainFileFragment != null) { fileListOption = newFileListOption file = storageManager.getFileByPath(OCFile.ROOT_PATH) listMainFileFragment?.updateFileListOption(newFileListOption, file) updateToolbar(null) + } else if (spacesListFragment != null) { + fileListOption = newFileListOption + file = storageManager.getFileByPath(OCFile.ROOT_PATH) + initAndShowListOfFiles() + updateToolbar(null) } else { super.navigateToOption(FileListOption.ALL_FILES) } - } else { + } else if (newFileListOption != FileListOption.SPACES_LIST) { browseToRoot() } } @@ -1344,6 +1372,7 @@ class FileDisplayActivity : FileActivity(), } private fun getMenuItemForFileListOption(fileListOption: FileListOption?): Int = when (fileListOption) { + FileListOption.SPACES_LIST -> R.id.nav_spaces FileListOption.SHARED_BY_LINK -> R.id.nav_shared_by_link_files FileListOption.AV_OFFLINE -> R.id.nav_available_offline_files else -> R.id.nav_all_files @@ -1453,6 +1482,7 @@ class FileDisplayActivity : FileActivity(), companion object { private const val TAG_LIST_OF_FILES = "LIST_OF_FILES" + private const val TAG_LIST_OF_SPACES = "LIST_OF_SPACES" private const val TAG_SECOND_FRAGMENT = "SECOND_FRAGMENT" private const val KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW" diff --git a/owncloudApp/src/main/res/drawable/ic_spaces.xml b/owncloudApp/src/main/res/drawable/ic_spaces.xml new file mode 100644 index 00000000000..f7893d63d89 --- /dev/null +++ b/owncloudApp/src/main/res/drawable/ic_spaces.xml @@ -0,0 +1,9 @@ + + + diff --git a/owncloudApp/src/main/res/layout/spaces_list_fragment.xml b/owncloudApp/src/main/res/layout/spaces_list_fragment.xml new file mode 100644 index 00000000000..1ee3a007d50 --- /dev/null +++ b/owncloudApp/src/main/res/layout/spaces_list_fragment.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/owncloudApp/src/main/res/menu/bottom_navbar_menu.xml b/owncloudApp/src/main/res/menu/bottom_navbar_menu.xml index 4f5bd9a3866..b1dacebc1fa 100644 --- a/owncloudApp/src/main/res/menu/bottom_navbar_menu.xml +++ b/owncloudApp/src/main/res/menu/bottom_navbar_menu.xml @@ -21,6 +21,11 @@ android:id="@+id/nav_all_files" android:icon="@drawable/ic_folder" android:title="@string/bottom_nav_files" /> + Sort by All files Available offline + Spaces Shared by link Settings Uploads @@ -113,6 +114,8 @@ Files + Personal + Spaces Uploads Offline Links @@ -148,9 +151,11 @@ An error occurred while copying the file to a temporary folder. Please try to send again. seconds ago No files in here + No spaces No available offline files No shared links Upload some content or sync with your devices! + You don´t have access to any space! Files and folders you mark as available offline will show up here Files and folders you share by link will show up here Loading… diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 37226a0c75d..d35738f639b 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -133,6 +133,7 @@ class OCFileRepository( override fun getSearchFolderContent(fileListOption: FileListOption, folderId: Long, search: String): List = when (fileListOption) { FileListOption.ALL_FILES -> localFileDataSource.getSearchFolderContent(folderId, search) + FileListOption.SPACES_LIST -> emptyList() FileListOption.AV_OFFLINE -> localFileDataSource.getSearchAvailableOfflineFolderContent(folderId, search) FileListOption.SHARED_BY_LINK -> localFileDataSource.getSearchSharedByLinkFolderContent(folderId, search) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt index 691034d0b1e..2a255dcd3a0 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt @@ -63,6 +63,10 @@ data class OCCapability( fun isOpenInWebAllowed(): Boolean = filesAppProviders?.openWebUrlAppProviders?.isNotBlank() ?: false + fun isSpacesAllowed(): Boolean = spaces?.enabledSpaces == true + + fun isSpacesProjectsAllowed(): Boolean = spaces?.projectsSpaces == true + data class AppProviders( val enabledAppProviders: Boolean, val versionAppProviders: String, diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/FileListOption.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/FileListOption.kt index e8918efd341..41ea73f0522 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/FileListOption.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/FileListOption.kt @@ -27,9 +27,10 @@ import kotlinx.parcelize.Parcelize @Parcelize enum class FileListOption : Parcelable { - ALL_FILES, SHARED_BY_LINK, AV_OFFLINE; + ALL_FILES, SPACES_LIST, SHARED_BY_LINK, AV_OFFLINE; fun isAllFiles() = this == ALL_FILES + fun isSpacesList() = this == SPACES_LIST fun isSharedByLink() = this == SHARED_BY_LINK fun isAvailableOffline() = this == AV_OFFLINE } From 0d13afde7e7c4676ab628437d57bf72f761d7e23 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Fri, 23 Dec 2022 15:19:32 +0100 Subject: [PATCH 004/152] Fix for KtLint report --- .../com/owncloud/android/data/migrations/AutoMigration39To40.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt b/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt index ca7b6e55c04..7cd707a913b 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt @@ -60,4 +60,4 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VER fromColumnName = "newUrl", toColumnName = "newUrlAppProviders" ) -class AutoMigration39To40: AutoMigrationSpec +class AutoMigration39To40 : AutoMigrationSpec From 04ef0008157bd8f7582e4ea5f04811f63cb08e9b Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 27 Dec 2022 10:44:46 +0100 Subject: [PATCH 005/152] Correct FAB visibility and navigation between sections --- .../android/dependecyinjection/ViewModelModule.kt | 7 ++++--- .../files/filelist/MainFileListFragment.kt | 11 +++++++---- .../files/filelist/MainFileListViewModel.kt | 5 ++--- .../android/ui/activity/FileDisplayActivity.kt | 10 +++++----- .../android/ui/activity/FolderPickerActivity.java | 3 ++- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index dd8e7700503..4c6fef9ab4f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -24,6 +24,7 @@ package com.owncloud.android.dependecyinjection import com.owncloud.android.MainApp +import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.presentation.files.details.FileDetailsViewModel import com.owncloud.android.presentation.files.filelist.MainFileListViewModel @@ -91,7 +92,7 @@ val viewModelModule = module { viewModel { PreviewImageViewModel(get(), get(), get()) } viewModel { FileOperationsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } - viewModel { (accountName: String, initialFolderToDisplay: OCFile) -> + viewModel { (accountName: String, initialFolderToDisplay: OCFile, fileListOption: FileListOption) -> MainFileListViewModel( get(), get(), @@ -102,9 +103,9 @@ val viewModelModule = module { get(), get(), get(), - get(), accountName, - initialFolderToDisplay + initialFolderToDisplay, + fileListOption, ) } viewModel { TransfersViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index e4bcc73523d..fd73df396ac 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -94,6 +94,7 @@ class MainFileListFragment : Fragment(), parametersOf( requireArguments().getString(ARG_ACCOUNT_NAME), requireArguments().getParcelable(ARG_INITIAL_FOLDER_TO_DISPLAY), + requireArguments().getParcelable(ARG_FILE_LIST_OPTION), ) } private val fileOperationsViewModel by sharedViewModel() @@ -125,8 +126,6 @@ class MainFileListFragment : Fragment(), super.onViewCreated(view, savedInstanceState) initViews() subscribeToViewModels() - - mainFileListViewModel.updateFileListOption(requireArguments().getParcelable(ARG_LIST_FILE_OPTION) ?: FileListOption.ALL_FILES) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -197,6 +196,8 @@ class MainFileListFragment : Fragment(), it.selectAdditionalView(SortOptionsView.AdditionalView.CREATE_FOLDER) } } + + showOrHideFab(requireArguments().getParcelable(ARG_FILE_LIST_OPTION)!!) } private fun toggleSelection(position: Int) { @@ -750,9 +751,9 @@ class MainFileListFragment : Fragment(), companion object { val ARG_PICKING_A_FOLDER = "${MainFileListFragment::class.java.canonicalName}.ARG_PICKING_A_FOLDER}" - val ARG_LIST_FILE_OPTION = "${MainFileListFragment::class.java.canonicalName}.LIST_FILE_OPTION}" val ARG_ACCOUNT_NAME = "${MainFileListFragment::class.java.canonicalName}.ARG_ACCOUNT_NAME}" val ARG_INITIAL_FOLDER_TO_DISPLAY = "${MainFileListFragment::class.java.canonicalName}.ARG_INITIAL_FOLDER_TO_DISPLAY}" + val ARG_FILE_LIST_OPTION = "${MainFileListFragment::class.java.canonicalName}.FILE_LIST_OPTION}" private const val DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER" @@ -760,12 +761,14 @@ class MainFileListFragment : Fragment(), fun newInstance( accountName: String, initialFolderToDisplay: OCFile, - pickingAFolder: Boolean = false + pickingAFolder: Boolean = false, + fileListOption: FileListOption = FileListOption.ALL_FILES, ): MainFileListFragment { val args = Bundle() args.putString(ARG_ACCOUNT_NAME, accountName) args.putParcelable(ARG_INITIAL_FOLDER_TO_DISPLAY, initialFolderToDisplay) args.putBoolean(ARG_PICKING_A_FOLDER, pickingAFolder) + args.putParcelable(ARG_FILE_LIST_OPTION, fileListOption) return MainFileListFragment().apply { arguments = args } } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt index 5e4fcb11ee7..b74845bd5a8 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt @@ -23,7 +23,6 @@ package com.owncloud.android.presentation.files.filelist import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.work.WorkManager import com.owncloud.android.data.preferences.datasources.SharedPreferencesProvider import com.owncloud.android.datamodel.FileDataStorageManager.Companion.ROOT_PARENT_ID import com.owncloud.android.domain.availableoffline.usecases.GetFilesAvailableOfflineFromAccountAsStreamUseCase @@ -66,16 +65,16 @@ class MainFileListViewModel( private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, private val sharedPreferencesProvider: SharedPreferencesProvider, private val synchronizeFolderUseCase: SynchronizeFolderUseCase, - private val workManager: WorkManager, accountNameParam: String, initialFolderToDisplay: OCFile, + fileListOptionParam: FileListOption, ) : ViewModel() { private val showHiddenFiles: Boolean = sharedPreferencesProvider.getBoolean(PREF_SHOW_HIDDEN_FILES, false) private val accountName: MutableStateFlow = MutableStateFlow(accountNameParam) val currentFolderDisplayed: MutableStateFlow = MutableStateFlow(initialFolderToDisplay) - val fileListOption: MutableStateFlow = MutableStateFlow(FileListOption.ALL_FILES) + val fileListOption: MutableStateFlow = MutableStateFlow(fileListOptionParam) private val searchFilter: MutableStateFlow = MutableStateFlow("") private val sortTypeAndOrder = MutableStateFlow(Pair(SortType.SORT_TYPE_BY_NAME, SortOrder.SORT_ORDER_ASCENDING)) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index a22b00c7e3e..fd3d5ed70f5 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -326,10 +326,11 @@ class FileDisplayActivity : FileActivity(), } } - private fun initAndShowListOfFiles() { + private fun initAndShowListOfFiles(fileListOption: FileListOption = FileListOption.ALL_FILES) { val mainListOfFiles = MainFileListFragment.newInstance( accountName = account.name, initialFolderToDisplay = file, + fileListOption = fileListOption, ).apply { fileActions = this@FileDisplayActivity uploadActions = this@FileDisplayActivity @@ -1345,11 +1346,10 @@ class FileDisplayActivity : FileActivity(), private fun navigateTo(newFileListOption: FileListOption) { if (fileListOption != newFileListOption) { if (newFileListOption == FileListOption.SPACES_LIST) { - initAndShowListOfSpaces() fileListOption = FileListOption.SPACES_LIST + initAndShowListOfSpaces() updateToolbar(null) - } - else if (listMainFileFragment != null) { + } else if (listMainFileFragment != null) { fileListOption = newFileListOption file = storageManager.getFileByPath(OCFile.ROOT_PATH) listMainFileFragment?.updateFileListOption(newFileListOption, file) @@ -1357,7 +1357,7 @@ class FileDisplayActivity : FileActivity(), } else if (spacesListFragment != null) { fileListOption = newFileListOption file = storageManager.getFileByPath(OCFile.ROOT_PATH) - initAndShowListOfFiles() + initAndShowListOfFiles(newFileListOption) updateToolbar(null) } else { super.navigateToOption(FileListOption.ALL_FILES) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java index a978f48a846..c245fc873d0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java @@ -38,6 +38,7 @@ import androidx.fragment.app.FragmentTransaction; import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.domain.files.model.FileListOption; import com.owncloud.android.domain.files.model.OCFile; import com.owncloud.android.presentation.files.filelist.MainFileListFragment; import com.owncloud.android.ui.fragment.FileFragment; @@ -126,7 +127,7 @@ private void initAndShowListOfFilesFragment() { safeInitialFolder = getFile(); } - MainFileListFragment mainListOfFiles = MainFileListFragment.newInstance(getAccount().name, safeInitialFolder, true); + MainFileListFragment mainListOfFiles = MainFileListFragment.newInstance(getAccount().name, safeInitialFolder, true, FileListOption.ALL_FILES); mainListOfFiles.setFileActions(this); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.add(R.id.fragment_container, mainListOfFiles, TAG_LIST_OF_FOLDERS); From 42c80970599e22db5d6578ec6278db6ce437dca8 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 27 Dec 2022 11:11:42 +0100 Subject: [PATCH 006/152] Fix unit DB tests --- .../android/data/roommigrations/MigrationToDB28Test.kt | 4 ++-- .../android/data/roommigrations/MigrationToDB30Test.kt | 4 ++-- .../src/main/java/com/owncloud/android/data/ProviderMeta.java | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB28Test.kt b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB28Test.kt index 5a4625561e2..7fd8b83129c 100644 --- a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB28Test.kt +++ b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB28Test.kt @@ -28,7 +28,7 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_ACC import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_CORE_POLLINTERVAL import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME -import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAJOR +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.LEGACY_CAPABILITIES_VERSION_MAYOR import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MICRO import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MINOR import com.owncloud.android.data.migrations.MIGRATION_27_28 @@ -84,7 +84,7 @@ class MigrationToDB28Test : MigrationTest() { companion object { val cvWithDefaultValues = ContentValues().apply { put(CAPABILITIES_ACCOUNT_NAME, OC_CAPABILITY.accountName) - put(CAPABILITIES_VERSION_MAJOR, OC_CAPABILITY.versionMajor) + put(LEGACY_CAPABILITIES_VERSION_MAYOR, OC_CAPABILITY.versionMajor) put(CAPABILITIES_VERSION_MINOR, OC_CAPABILITY.versionMinor) put(CAPABILITIES_VERSION_MICRO, OC_CAPABILITY.versionMicro) put(CAPABILITIES_CORE_POLLINTERVAL, OC_CAPABILITY.corePollInterval) diff --git a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB30Test.kt b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB30Test.kt index 1592cfb9a80..b72ec3be08d 100644 --- a/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB30Test.kt +++ b/owncloudData/src/androidTest/java/com/owncloud/android/data/roommigrations/MigrationToDB30Test.kt @@ -30,7 +30,7 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_COR import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_DAV_CHUNKING_VERSION import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME -import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAJOR +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.LEGACY_CAPABILITIES_VERSION_MAYOR import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MICRO import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MINOR import com.owncloud.android.testutil.OC_CAPABILITY @@ -107,7 +107,7 @@ class MigrationToDB30Test : MigrationTest() { companion object { val cvWithDefaultValues = ContentValues().apply { put(CAPABILITIES_ACCOUNT_NAME, OC_CAPABILITY.accountName) - put(CAPABILITIES_VERSION_MAJOR, OC_CAPABILITY.versionMajor) + put(LEGACY_CAPABILITIES_VERSION_MAYOR, OC_CAPABILITY.versionMajor) put(CAPABILITIES_VERSION_MINOR, OC_CAPABILITY.versionMinor) put(CAPABILITIES_VERSION_MICRO, OC_CAPABILITY.versionMicro) put(CAPABILITIES_CORE_POLLINTERVAL, OC_CAPABILITY.corePollInterval) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java index c7901b225bc..5d5c62f8597 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java +++ b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java @@ -63,6 +63,7 @@ static public class ProviderTableMeta implements BaseColumns { // Columns of capabilities table public static final String CAPABILITIES_ACCOUNT_NAME = "account"; + public static final String LEGACY_CAPABILITIES_VERSION_MAYOR = "version_mayor"; public static final String CAPABILITIES_VERSION_MAJOR = "version_major"; public static final String CAPABILITIES_VERSION_MINOR = "version_minor"; public static final String CAPABILITIES_VERSION_MICRO = "version_micro"; From 7b69502ca32976af59d3f2f36e84f9ee8fc69cf2 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 27 Dec 2022 12:36:18 +0100 Subject: [PATCH 007/152] Fix spaces tab visibility when changing between oC10 and oCIS --- .../main/java/com/owncloud/android/ui/activity/DrawerActivity.kt | 1 + owncloudApp/src/main/res/menu/bottom_navbar_menu.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt index e85f2248be6..3defdde2a42 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt @@ -231,6 +231,7 @@ abstract class DrawerActivity : ToolbarActivity() { getBottomNavigationView()?.menu?.get(1)?.isVisible = capabilities.isSpacesProjectsAllowed() } else { getBottomNavigationView()?.menu?.get(0)?.title = getString(R.string.bottom_nav_files) + getBottomNavigationView()?.menu?.get(1)?.isVisible = false } } }) diff --git a/owncloudApp/src/main/res/menu/bottom_navbar_menu.xml b/owncloudApp/src/main/res/menu/bottom_navbar_menu.xml index b1dacebc1fa..e64a943fb1e 100644 --- a/owncloudApp/src/main/res/menu/bottom_navbar_menu.xml +++ b/owncloudApp/src/main/res/menu/bottom_navbar_menu.xml @@ -25,6 +25,7 @@ android:id="@+id/nav_spaces" android:icon="@drawable/ic_spaces" android:title="@string/bottom_nav_spaces" + android:visible="false" /> Date: Thu, 29 Dec 2022 09:48:37 +0100 Subject: [PATCH 008/152] Fix spaces tab visibility when rotating screen --- .../android/ui/activity/DrawerActivity.kt | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt index 3defdde2a42..d5c8ac6634d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt @@ -223,17 +223,11 @@ abstract class DrawerActivity : ToolbarActivity() { // Allow or disallow touches with other visible windows getBottomNavigationView()?.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) if (account != null) { + capabilitiesViewModel.capabilities.value?.let { + setSpacesVisibilityBottomBar(it.peekContent()) + } capabilitiesViewModel.capabilities.observe(this, Event.EventObserver { uiResult: UIResult -> - if (uiResult is UIResult.Success) { - val capabilities = uiResult.data - if (capabilities?.isSpacesAllowed() == true) { - getBottomNavigationView()?.menu?.get(0)?.title = getString(R.string.bottom_nav_personal) - getBottomNavigationView()?.menu?.get(1)?.isVisible = capabilities.isSpacesProjectsAllowed() - } else { - getBottomNavigationView()?.menu?.get(0)?.title = getString(R.string.bottom_nav_files) - getBottomNavigationView()?.menu?.get(1)?.isVisible = false - } - } + setSpacesVisibilityBottomBar(uiResult) }) } setCheckedItemAtBottomBar(menuItemId) @@ -243,6 +237,19 @@ abstract class DrawerActivity : ToolbarActivity() { } } + private fun setSpacesVisibilityBottomBar(uiResult: UIResult) { + if (uiResult is UIResult.Success) { + val capabilities = uiResult.data + if (capabilities?.isSpacesAllowed() == true) { + getBottomNavigationView()?.menu?.get(0)?.title = getString(R.string.bottom_nav_personal) + getBottomNavigationView()?.menu?.get(1)?.isVisible = capabilities.isSpacesProjectsAllowed() + } else { + getBottomNavigationView()?.menu?.get(0)?.title = getString(R.string.bottom_nav_files) + getBottomNavigationView()?.menu?.get(1)?.isVisible = false + } + } + } + private fun bottomBarNavigationTo(menuItemId: Int, isCurrentOptionActive: Boolean) { when (menuItemId) { R.id.nav_all_files -> navigateToOption(FileListOption.ALL_FILES) From 6657e5f1f60fc81ebec6c142c84f3d76f82bdce7 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Mon, 9 Jan 2023 15:03:33 +0100 Subject: [PATCH 009/152] CR changes --- .../files/details/FileDetailsViewModel.kt | 2 +- .../android/ui/activity/DrawerActivity.kt | 3 -- owncloudApp/src/main/res/values/strings.xml | 2 +- .../40.json | 42 +++++++++---------- .../owncloud/android/data/ProviderMeta.java | 3 +- .../capabilities/db/OCCapabilityEntity.kt | 6 ++- .../domain/capabilities/model/OCCapability.kt | 24 +++++------ 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt index 9487af537ea..221015dd3c1 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt @@ -142,7 +142,7 @@ class FileDetailsViewModel( coroutineDispatcher = coroutinesDispatcherProvider.io, liveData = _openInWebUriLiveData, useCase = openInWebUseCase, - useCaseParams = GetUrlToOpenInWebUseCase.Params(openWebEndpoint = capabilities.value?.filesAppProviders?.openWebUrlAppProviders!!, fileId = fileId), + useCaseParams = GetUrlToOpenInWebUseCase.Params(openWebEndpoint = capabilities.value?.filesAppProviders?.openWebUrl!!, fileId = fileId), showLoading = false, requiresConnection = true, ) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt index d5c8ac6634d..0b910b9e528 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt @@ -223,9 +223,6 @@ abstract class DrawerActivity : ToolbarActivity() { // Allow or disallow touches with other visible windows getBottomNavigationView()?.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) if (account != null) { - capabilitiesViewModel.capabilities.value?.let { - setSpacesVisibilityBottomBar(it.peekContent()) - } capabilitiesViewModel.capabilities.observe(this, Event.EventObserver { uiResult: UIResult -> setSpacesVisibilityBottomBar(uiResult) }) diff --git a/owncloudApp/src/main/res/values/strings.xml b/owncloudApp/src/main/res/values/strings.xml index 2ca8fa99baf..19c9fa7a15a 100644 --- a/owncloudApp/src/main/res/values/strings.xml +++ b/owncloudApp/src/main/res/values/strings.xml @@ -155,7 +155,7 @@ No available offline files No shared links Upload some content or sync with your devices! - You don´t have access to any space! + You don\'t have access to any space! Files and folders you mark as available offline will show up here Files and folders you share by link will show up here Loading… diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json index b48a15cece3..10a6b97f5d4 100644 --- a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 40, - "identityHash": "f805f9babbe5ac10107c1a7dfe48e4a4", + "identityHash": "be3530216034282e4a6680bf121e3096", "entities": [ { "tableName": "folder_backup", @@ -74,7 +74,7 @@ }, { "tableName": "capabilities", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` TEXT, `version_major` INTEGER NOT NULL, `version_minor` INTEGER NOT NULL, `version_micro` INTEGER NOT NULL, `version_string` TEXT, `version_edition` TEXT, `core_pollinterval` INTEGER NOT NULL, `dav_chunking_version` TEXT NOT NULL, `sharing_api_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_write` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_public_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_days` INTEGER NOT NULL, `sharing_public_expire_date_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_upload` INTEGER NOT NULL DEFAULT -1, `sharing_public_multiple` INTEGER NOT NULL DEFAULT -1, `supports_upload_only` INTEGER NOT NULL DEFAULT -1, `sharing_resharing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_outgoing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_incoming` INTEGER NOT NULL DEFAULT -1, `sharing_user_profile_picture` INTEGER NOT NULL DEFAULT -1, `files_bigfilechunking` INTEGER NOT NULL DEFAULT -1, `files_undelete` INTEGER NOT NULL DEFAULT -1, `files_versioning` INTEGER NOT NULL DEFAULT -1, `files_private_links` INTEGER NOT NULL DEFAULT -1, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `enabledAppProviders` INTEGER, `versionAppProviders` TEXT, `appsUrlAppProviders` TEXT, `openUrlAppProviders` TEXT, `openWebUrlAppProviders` TEXT, `newUrlAppProviders` TEXT, `enabledSpaces` INTEGER, `projectsSpaces` INTEGER, `shareJailSpaces` INTEGER)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` TEXT, `version_major` INTEGER NOT NULL, `version_minor` INTEGER NOT NULL, `version_micro` INTEGER NOT NULL, `version_string` TEXT, `version_edition` TEXT, `core_pollinterval` INTEGER NOT NULL, `dav_chunking_version` TEXT NOT NULL, `sharing_api_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_write` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_public_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_days` INTEGER NOT NULL, `sharing_public_expire_date_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_upload` INTEGER NOT NULL DEFAULT -1, `sharing_public_multiple` INTEGER NOT NULL DEFAULT -1, `supports_upload_only` INTEGER NOT NULL DEFAULT -1, `sharing_resharing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_outgoing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_incoming` INTEGER NOT NULL DEFAULT -1, `sharing_user_profile_picture` INTEGER NOT NULL DEFAULT -1, `files_bigfilechunking` INTEGER NOT NULL DEFAULT -1, `files_undelete` INTEGER NOT NULL DEFAULT -1, `files_versioning` INTEGER NOT NULL DEFAULT -1, `files_private_links` INTEGER NOT NULL DEFAULT -1, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `app_providers_enabled` INTEGER, `app_providers_version` TEXT, `app_providers_appsUrl` TEXT, `app_providers_openUrl` TEXT, `app_providers_openWebUrl` TEXT, `app_providers_newUrl` TEXT, `spaces_enabled` INTEGER, `spaces_projects` INTEGER, `spaces_shareJail` INTEGER)", "fields": [ { "fieldPath": "accountName", @@ -270,56 +270,56 @@ "notNull": true }, { - "fieldPath": "appProviders.enabledAppProviders", - "columnName": "enabledAppProviders", + "fieldPath": "appProviders.enabled", + "columnName": "app_providers_enabled", "affinity": "INTEGER", "notNull": false }, { - "fieldPath": "appProviders.versionAppProviders", - "columnName": "versionAppProviders", + "fieldPath": "appProviders.version", + "columnName": "app_providers_version", "affinity": "TEXT", "notNull": false }, { - "fieldPath": "appProviders.appsUrlAppProviders", - "columnName": "appsUrlAppProviders", + "fieldPath": "appProviders.appsUrl", + "columnName": "app_providers_appsUrl", "affinity": "TEXT", "notNull": false }, { - "fieldPath": "appProviders.openUrlAppProviders", - "columnName": "openUrlAppProviders", + "fieldPath": "appProviders.openUrl", + "columnName": "app_providers_openUrl", "affinity": "TEXT", "notNull": false }, { - "fieldPath": "appProviders.openWebUrlAppProviders", - "columnName": "openWebUrlAppProviders", + "fieldPath": "appProviders.openWebUrl", + "columnName": "app_providers_openWebUrl", "affinity": "TEXT", "notNull": false }, { - "fieldPath": "appProviders.newUrlAppProviders", - "columnName": "newUrlAppProviders", + "fieldPath": "appProviders.newUrl", + "columnName": "app_providers_newUrl", "affinity": "TEXT", "notNull": false }, { - "fieldPath": "spaces.enabledSpaces", - "columnName": "enabledSpaces", + "fieldPath": "spaces.enabled", + "columnName": "spaces_enabled", "affinity": "INTEGER", "notNull": false }, { - "fieldPath": "spaces.projectsSpaces", - "columnName": "projectsSpaces", + "fieldPath": "spaces.projects", + "columnName": "spaces_projects", "affinity": "INTEGER", "notNull": false }, { - "fieldPath": "spaces.shareJailSpaces", - "columnName": "shareJailSpaces", + "fieldPath": "spaces.shareJail", + "columnName": "spaces_shareJail", "affinity": "INTEGER", "notNull": false } @@ -773,7 +773,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f805f9babbe5ac10107c1a7dfe48e4a4')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'be3530216034282e4a6680bf121e3096')" ] } } \ No newline at end of file diff --git a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java index 5d5c62f8597..fb491176c90 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java +++ b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java @@ -96,8 +96,9 @@ static public class ProviderTableMeta implements BaseColumns { public static final String CAPABILITIES_FILES_BIGFILECHUNKING = "files_bigfilechunking"; public static final String CAPABILITIES_FILES_UNDELETE = "files_undelete"; public static final String CAPABILITIES_FILES_VERSIONING = "files_versioning"; - public static final String CAPABILITIES_FILES_APP_PROVIDERS = "files_apps_providers"; public static final String CAPABILITIES_FILES_PRIVATE_LINKS = "files_private_links"; + public static final String CAPABILITIES_APP_PROVIDERS_PREFIX = "app_providers_"; + public static final String CAPABILITIES_SPACES_PREFIX = "spaces_"; // Columns of filelist table public static final String FILE_PARENT = "parent"; diff --git a/owncloudData/src/main/java/com/owncloud/android/data/capabilities/db/OCCapabilityEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/capabilities/db/OCCapabilityEntity.kt index cc23bcaa132..abbacadc3e4 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/capabilities/db/OCCapabilityEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/capabilities/db/OCCapabilityEntity.kt @@ -26,6 +26,7 @@ import androidx.room.Embedded import androidx.room.Entity import androidx.room.PrimaryKey import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_APP_PROVIDERS_PREFIX import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_CORE_POLLINTERVAL import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_DAV_CHUNKING_VERSION import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_FILES_BIGFILECHUNKING @@ -48,6 +49,7 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHA import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_UPLOAD import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_RESHARING import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_USER_PROFILE_PICTURE +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_SPACES_PREFIX import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_EDITION import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAJOR @@ -119,9 +121,9 @@ data class OCCapabilityEntity( val filesVersioning: Int, @ColumnInfo(name = CAPABILITIES_FILES_PRIVATE_LINKS, defaultValue = capabilityBooleanTypeUnknownString) val filesPrivateLinks: Int, - @Embedded + @Embedded(prefix = CAPABILITIES_APP_PROVIDERS_PREFIX) val appProviders: OCCapability.AppProviders?, - @Embedded + @Embedded(prefix = CAPABILITIES_SPACES_PREFIX) val spaces: OCCapability.Spaces?, ) { @PrimaryKey(autoGenerate = true) var id: Int = 0 diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt index 2a255dcd3a0..77b6789e734 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/capabilities/model/OCCapability.kt @@ -61,25 +61,25 @@ data class OCCapability( return filesSharingUserProfilePicture.isTrue || filesSharingUserProfilePicture.isUnknown } - fun isOpenInWebAllowed(): Boolean = filesAppProviders?.openWebUrlAppProviders?.isNotBlank() ?: false + fun isOpenInWebAllowed(): Boolean = filesAppProviders?.openWebUrl?.isNotBlank() ?: false - fun isSpacesAllowed(): Boolean = spaces?.enabledSpaces == true + fun isSpacesAllowed(): Boolean = spaces?.enabled == true - fun isSpacesProjectsAllowed(): Boolean = spaces?.projectsSpaces == true + fun isSpacesProjectsAllowed(): Boolean = spaces?.projects == true data class AppProviders( - val enabledAppProviders: Boolean, - val versionAppProviders: String, - val appsUrlAppProviders: String?, - val openUrlAppProviders: String?, - val openWebUrlAppProviders: String?, - val newUrlAppProviders: String?, + val enabled: Boolean, + val version: String, + val appsUrl: String?, + val openUrl: String?, + val openWebUrl: String?, + val newUrl: String?, ) data class Spaces( - val enabledSpaces: Boolean, - val projectsSpaces: Boolean, - val shareJailSpaces: Boolean + val enabled: Boolean, + val projects: Boolean, + val shareJail: Boolean, ) } From 57bb0e1052a968fa8b60d93944dcd679a77ea893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Wed, 21 Dec 2022 20:08:35 +0100 Subject: [PATCH 010/152] Introduce spaces skeleton (Model, Repository, DataSources..) --- .../LocalDataSourceModule.kt | 3 + .../RemoteDataSourceModule.kt | 3 + .../dependecyinjection/RepositoryModule.kt | 3 + .../dependecyinjection/UseCaseModule.kt | 6 +- .../datasources/LocalSpacesDataSource.kt | 25 +++++ .../datasources/RemoteSpacesDataSource.kt | 25 +++++ .../implementation/OCLocalSpacesDataSource.kt | 30 ++++++ .../OCRemoteSpacesDataSource.kt | 93 +++++++++++++++++++ .../spaces/repository/OCSpacesRepository.kt | 34 +++++++ .../android/domain/spaces/SpacesRepository.kt | 23 +++++ .../android/domain/spaces/model/OCSpace.kt | 91 ++++++++++++++++++ .../RefreshSpacesFromServerAsyncUseCase.kt | 30 ++++++ 12 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/RefreshSpacesFromServerAsyncUseCase.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt index 8654a6a49d0..457731ec2d1 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt @@ -38,6 +38,8 @@ import com.owncloud.android.data.preferences.datasources.SharedPreferencesProvid import com.owncloud.android.data.preferences.datasources.implementation.OCSharedPreferencesProvider import com.owncloud.android.data.sharing.shares.datasources.LocalShareDataSource import com.owncloud.android.data.sharing.shares.datasources.implementation.OCLocalShareDataSource +import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource +import com.owncloud.android.data.spaces.datasources.implementation.OCLocalSpacesDataSource import com.owncloud.android.data.storage.LocalStorageProvider import com.owncloud.android.data.storage.ScopedStorageProvider import com.owncloud.android.data.transfers.datasources.LocalTransferDataSource @@ -67,4 +69,5 @@ val localDataSourceModule = module { factory { OCLocalUserDataSource(get()) } factory { OCFolderBackupLocalDataSource(get()) } factory { OCLocalTransferDataSource(get()) } + factory { OCLocalSpacesDataSource() } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt index dbfdda2ce07..5b945d1f0d6 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt @@ -39,6 +39,8 @@ import com.owncloud.android.data.sharing.sharees.datasources.mapper.RemoteSharee import com.owncloud.android.data.sharing.shares.datasources.RemoteShareDataSource import com.owncloud.android.data.sharing.shares.datasources.implementation.OCRemoteShareDataSource import com.owncloud.android.data.sharing.shares.datasources.mapper.RemoteShareMapper +import com.owncloud.android.data.spaces.datasources.RemoteSpacesDataSource +import com.owncloud.android.data.spaces.datasources.implementation.OCRemoteSpacesDataSource import com.owncloud.android.data.user.datasources.RemoteUserDataSource import com.owncloud.android.data.user.datasources.implementation.OCRemoteUserDataSource import com.owncloud.android.data.webfinger.datasources.WebfingerRemoteDatasource @@ -68,6 +70,7 @@ val remoteDataSourceModule = module { single { OCRemoteServerInfoDataSource(get(), get()) } single { OCRemoteShareDataSource(get(), get()) } single { OCRemoteShareeDataSource(get(), get()) } + single { OCRemoteSpacesDataSource(get()) } single { OCRemoteUserDataSource(get(), androidContext().resources.getDimension(R.dimen.file_avatar_size).toInt()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt index 73f251b80cd..96f7e630718 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt @@ -30,6 +30,7 @@ import com.owncloud.android.data.oauth.repository.OCOAuthRepository import com.owncloud.android.data.server.repository.OCServerInfoRepository import com.owncloud.android.data.sharing.sharees.repository.OCShareeRepository import com.owncloud.android.data.sharing.shares.repository.OCShareRepository +import com.owncloud.android.data.spaces.repository.OCSpacesRepository import com.owncloud.android.data.transfers.repository.OCTransferRepository import com.owncloud.android.data.user.repository.OCUserRepository import com.owncloud.android.data.webfinger.repository.OCWebfingerRepository @@ -41,6 +42,7 @@ import com.owncloud.android.domain.files.FileRepository import com.owncloud.android.domain.server.ServerInfoRepository import com.owncloud.android.domain.sharing.sharees.ShareeRepository import com.owncloud.android.domain.sharing.shares.ShareRepository +import com.owncloud.android.domain.spaces.SpacesRepository import com.owncloud.android.domain.transfers.TransferRepository import com.owncloud.android.domain.user.UserRepository import com.owncloud.android.domain.webfinger.WebfingerRepository @@ -53,6 +55,7 @@ val repositoryModule = module { factory { OCServerInfoRepository(get()) } factory { OCShareRepository(get(), get()) } factory { OCShareeRepository(get()) } + factory { OCSpacesRepository(get(), get()) } factory { OCUserRepository(get(), get()) } factory { OCOAuthRepository(get()) } factory { OCFolderBackupRepository(get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 6caf2ef91da..5a074c5c706 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -77,6 +77,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.EditPublicShareAsyncU import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase +import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase import com.owncloud.android.domain.transfers.usecases.ClearSuccessfulTransfersUseCase import com.owncloud.android.domain.transfers.usecases.GetAllTransfersAsLiveDataUseCase import com.owncloud.android.domain.transfers.usecases.GetAllTransfersUseCase @@ -91,10 +92,10 @@ import com.owncloud.android.usecases.accounts.RemoveAccountUseCase import com.owncloud.android.usecases.synchronization.SynchronizeFileUseCase import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase import com.owncloud.android.usecases.transfers.downloads.CancelDownloadForFileUseCase +import com.owncloud.android.usecases.transfers.downloads.CancelDownloadsRecursivelyUseCase import com.owncloud.android.usecases.transfers.downloads.DownloadFileUseCase import com.owncloud.android.usecases.transfers.downloads.GetLiveDataForDownloadingFileUseCase import com.owncloud.android.usecases.transfers.downloads.GetLiveDataForFinishedDownloadsFromAccountUseCase -import com.owncloud.android.usecases.transfers.downloads.CancelDownloadsRecursivelyUseCase import com.owncloud.android.usecases.transfers.uploads.CancelTransfersFromAccountUseCase import com.owncloud.android.usecases.transfers.uploads.CancelUploadForFileUseCase import com.owncloud.android.usecases.transfers.uploads.CancelUploadUseCase @@ -172,6 +173,9 @@ val useCaseModule = module { factory { GetSharesAsLiveDataUseCase(get()) } factory { RefreshSharesFromServerAsyncUseCase(get()) } + // Spaces + factory { RefreshSpacesFromServerAsyncUseCase(get()) } + // Transfers factory { CancelDownloadForFileUseCase(get()) } factory { CancelDownloadsRecursivelyUseCase(get(), get()) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt new file mode 100644 index 00000000000..ec34eb73b4c --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt @@ -0,0 +1,25 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.datasources + +import com.owncloud.android.domain.spaces.model.OCSpace + +interface LocalSpacesDataSource { + fun saveSpacesForAccount(listOfSpaces: List) +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt new file mode 100644 index 00000000000..5152b53c5b7 --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt @@ -0,0 +1,25 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.datasources + +import com.owncloud.android.domain.spaces.model.OCSpace + +interface RemoteSpacesDataSource { + fun refreshSpacesForAccount(): List +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt new file mode 100644 index 00000000000..7e432419b73 --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -0,0 +1,30 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.datasources.implementation + +import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource +import com.owncloud.android.domain.spaces.model.OCSpace + +class OCLocalSpacesDataSource( + +): LocalSpacesDataSource { + override fun saveSpacesForAccount(listOfSpaces: List) { + TODO("Not yet implemented") + } +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt new file mode 100644 index 00000000000..20e473b9268 --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt @@ -0,0 +1,93 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.datasources.implementation + +import com.owncloud.android.data.executeRemoteOperation +import com.owncloud.android.data.spaces.datasources.RemoteSpacesDataSource +import com.owncloud.android.domain.spaces.model.File +import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.domain.spaces.model.SpaceGrantedTo +import com.owncloud.android.domain.spaces.model.SpaceOwner +import com.owncloud.android.domain.spaces.model.SpacePermission +import com.owncloud.android.domain.spaces.model.SpaceQuota +import com.owncloud.android.domain.spaces.model.SpaceRoot +import com.owncloud.android.domain.spaces.model.SpaceSpecial +import com.owncloud.android.domain.spaces.model.SpaceSpecialFolder +import com.owncloud.android.domain.spaces.model.SpaceUser +import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse +import com.owncloud.android.lib.resources.spaces.services.SpacesService + +class OCRemoteSpacesDataSource( + private val spacesService: SpacesService +) : RemoteSpacesDataSource { + override fun refreshSpacesForAccount(): List { + val spacesResponse = executeRemoteOperation { + spacesService.getSpaces() + } + + return spacesResponse.map { it.toModel() } + } + + private fun SpaceResponse.toModel(): OCSpace = + OCSpace( + driveAlias = driveAlias, + driveType = driveType, + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + owner = SpaceOwner( + user = SpaceUser( + id = owner.user.id + ) + ), + quota = SpaceQuota( + remaining = quota.remaining, + state = quota.state, + total = quota.total, + used = quota.used + ), + root = SpaceRoot( + eTag = root.eTag, + id = root.id, + permissions = root.permissions?.map { permissionsResponse -> + SpacePermission( + grantedTo = permissionsResponse.grantedTo.map { grantedToResponse -> + SpaceGrantedTo(SpaceUser(grantedToResponse.user.id)) + }, + roles = permissionsResponse.roles, + ) + }, + webDavUrl = root.webDavUrl + ), + webUrl = webUrl, + description = description, + special = special?.map { specialResponse -> + SpaceSpecial( + eTag = specialResponse.eTag, + file = File(mimeType = specialResponse.file.mimeType), + id = specialResponse.id, + lastModifiedDateTime = specialResponse.lastModifiedDateTime, + name = specialResponse.name, + size = specialResponse.size, + specialFolder = SpaceSpecialFolder(name = specialResponse.specialFolder.name), + webDavUrl = specialResponse.webDavUrl + ) + } + ) +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt new file mode 100644 index 00000000000..cfe837932ea --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -0,0 +1,34 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.repository + +import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource +import com.owncloud.android.data.spaces.datasources.RemoteSpacesDataSource +import com.owncloud.android.domain.spaces.SpacesRepository + +class OCSpacesRepository( + private val localSpacesDataSource: LocalSpacesDataSource, + private val remoteSpacesDataSource: RemoteSpacesDataSource, +) : SpacesRepository { + override fun refreshSpacesForAccount() { + remoteSpacesDataSource.refreshSpacesForAccount().also { listOfSpaces -> + localSpacesDataSource.saveSpacesForAccount(listOfSpaces) + } + } +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt new file mode 100644 index 00000000000..779d7a815ac --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -0,0 +1,23 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces + +interface SpacesRepository { + fun refreshSpacesForAccount() +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt new file mode 100644 index 00000000000..99f8190269d --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -0,0 +1,91 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.model + +data class OCSpace( + val driveAlias: String, + val driveType: String, + val id: String, + val lastModifiedDateTime: String, + val name: String, + val owner: SpaceOwner, + val quota: SpaceQuota, + val root: SpaceRoot, + val webUrl: String, + val description: String?, + val special: List?, +) { + fun isPersonal() = driveType == DRIVE_TYPE_PERSONAL + fun isProject() = driveType == DRIVE_TYPE_PROJECT + + companion object { + const val DRIVE_TYPE_PERSONAL = "personal" + const val DRIVE_TYPE_PROJECT = "project" + } +} + +data class SpaceOwner( + val user: SpaceUser +) + +data class SpaceQuota( + val remaining: Long, + val state: String, + val total: Int, + val used: Int +) + +data class SpaceRoot( + val eTag: String, + val id: String, + val permissions: List?, + val webDavUrl: String +) + +data class SpaceSpecial( + val eTag: String, + val file: File, + val id: String, + val lastModifiedDateTime: String, + val name: String, + val size: Int, + val specialFolder: SpaceSpecialFolder, + val webDavUrl: String +) + +data class SpaceUser( + val id: String +) + +data class File( + val mimeType: String +) + +data class SpaceGrantedTo( + val user: SpaceUser +) + +data class SpacePermission( + val grantedTo: List, + val roles: List +) + +data class SpaceSpecialFolder( + val name: String +) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/RefreshSpacesFromServerAsyncUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/RefreshSpacesFromServerAsyncUseCase.kt new file mode 100644 index 00000000000..c37d1b7334d --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/RefreshSpacesFromServerAsyncUseCase.kt @@ -0,0 +1,30 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.spaces.SpacesRepository + +class RefreshSpacesFromServerAsyncUseCase( + private val spacesRepository: SpacesRepository +) : BaseUseCaseWithResult() { + + override fun run(params: Unit) = + spacesRepository.refreshSpacesForAccount() +} From f544e30f2d59b8ba4dd997c95e73cdcef94220f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Thu, 22 Dec 2022 08:34:11 +0100 Subject: [PATCH 011/152] Order provider meta alphabetically --- .../owncloud/android/data/ProviderMeta.java | 117 +++++++++--------- .../android/domain/spaces/model/OCSpace.kt | 8 +- 2 files changed, 60 insertions(+), 65 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java index fb491176c90..de0b2d10afa 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java +++ b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java @@ -37,105 +37,100 @@ private ProviderMeta() { } static public class ProviderTableMeta implements BaseColumns { - public static final String OCSHARES_TABLE_NAME = "ocshares"; public static final String CAPABILITIES_TABLE_NAME = "capabilities"; + public static final String FILES_SYNC_TABLE_NAME = "files_sync"; public static final String FILES_TABLE_NAME = "files"; - public static final String USER_QUOTAS_TABLE_NAME = "user_quotas"; public static final String FOLDER_BACKUP_TABLE_NAME = "folder_backup"; + public static final String OCSHARES_TABLE_NAME = "ocshares"; public static final String TRANSFERS_TABLE_NAME = "transfers"; - public static final String FILES_SYNC_TABLE_NAME = "files_sync"; + public static final String USER_QUOTAS_TABLE_NAME = "user_quotas"; // Columns of ocshares table - public static final String OCSHARES_SHARE_TYPE = "share_type"; - public static final String OCSHARES_SHARE_WITH = "share_with"; + public static final String OCSHARES_ACCOUNT_OWNER = "owner_share"; + public static final String OCSHARES_EXPIRATION_DATE = "expiration_date"; + public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared"; + public static final String OCSHARES_IS_DIRECTORY = "is_directory"; + public static final String OCSHARES_NAME = "name"; public static final String OCSHARES_PATH = "path"; public static final String OCSHARES_PERMISSIONS = "permissions"; public static final String OCSHARES_SHARED_DATE = "shared_date"; - public static final String OCSHARES_EXPIRATION_DATE = "expiration_date"; - public static final String OCSHARES_TOKEN = "token"; - public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name"; + public static final String OCSHARES_SHARE_TYPE = "share_type"; + public static final String OCSHARES_SHARE_WITH = "share_with"; public static final String OCSHARES_SHARE_WITH_ADDITIONAL_INFO = "share_with_additional_info"; - public static final String OCSHARES_IS_DIRECTORY = "is_directory"; - public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared"; - public static final String OCSHARES_ACCOUNT_OWNER = "owner_share"; - public static final String OCSHARES_NAME = "name"; + public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name"; + public static final String OCSHARES_TOKEN = "token"; public static final String OCSHARES_URL = "url"; // Columns of capabilities table public static final String CAPABILITIES_ACCOUNT_NAME = "account"; - public static final String LEGACY_CAPABILITIES_VERSION_MAYOR = "version_mayor"; - public static final String CAPABILITIES_VERSION_MAJOR = "version_major"; - public static final String CAPABILITIES_VERSION_MINOR = "version_minor"; - public static final String CAPABILITIES_VERSION_MICRO = "version_micro"; - public static final String CAPABILITIES_VERSION_STRING = "version_string"; - public static final String CAPABILITIES_VERSION_EDITION = "version_edition"; + public static final String CAPABILITIES_APP_PROVIDERS_PREFIX = "app_providers_"; public static final String CAPABILITIES_CORE_POLLINTERVAL = "core_pollinterval"; public static final String CAPABILITIES_DAV_CHUNKING_VERSION = "dav_chunking_version"; + public static final String CAPABILITIES_FILES_APP_PROVIDERS = "files_apps_providers"; + public static final String CAPABILITIES_FILES_BIGFILECHUNKING = "files_bigfilechunking"; + public static final String CAPABILITIES_FILES_PRIVATE_LINKS = "files_private_links"; + public static final String CAPABILITIES_FILES_UNDELETE = "files_undelete"; + public static final String CAPABILITIES_FILES_VERSIONING = "files_versioning"; public static final String CAPABILITIES_SHARING_API_ENABLED = "sharing_api_enabled"; + public static final String CAPABILITIES_SHARING_FEDERATION_INCOMING = "sharing_federation_incoming"; + public static final String CAPABILITIES_SHARING_FEDERATION_OUTGOING = "sharing_federation_outgoing"; public static final String CAPABILITIES_SHARING_PUBLIC_ENABLED = "sharing_public_enabled"; - public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED = "sharing_public_password_enforced"; - public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_READ_ONLY = - "sharing_public_password_enforced_read_only"; - public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_READ_WRITE = - "sharing_public_password_enforced_read_write"; - public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_UPLOAD_ONLY = - "sharing_public_password_enforced_public_only"; - public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENABLED = - "sharing_public_expire_date_enabled"; - public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS = - "sharing_public_expire_date_days"; - public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENFORCED = - "sharing_public_expire_date_enforced"; - public static final String CAPABILITIES_SHARING_PUBLIC_UPLOAD = "sharing_public_upload"; + public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS = "sharing_public_expire_date_days"; + public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENABLED = "sharing_public_expire_date_enabled"; + public static final String CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENFORCED = "sharing_public_expire_date_enforced"; public static final String CAPABILITIES_SHARING_PUBLIC_MULTIPLE = "sharing_public_multiple"; + public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED = "sharing_public_password_enforced"; + public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_READ_ONLY = "sharing_public_password_enforced_read_only"; + public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_READ_WRITE = "sharing_public_password_enforced_read_write"; + public static final String CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_UPLOAD_ONLY = "sharing_public_password_enforced_public_only"; public static final String CAPABILITIES_SHARING_PUBLIC_SUPPORTS_UPLOAD_ONLY = "supports_upload_only"; + public static final String CAPABILITIES_SHARING_PUBLIC_UPLOAD = "sharing_public_upload"; public static final String CAPABILITIES_SHARING_RESHARING = "sharing_resharing"; - public static final String CAPABILITIES_SHARING_FEDERATION_OUTGOING = "sharing_federation_outgoing"; - public static final String CAPABILITIES_SHARING_FEDERATION_INCOMING = "sharing_federation_incoming"; public static final String CAPABILITIES_SHARING_USER_PROFILE_PICTURE = "sharing_user_profile_picture"; - public static final String CAPABILITIES_FILES_BIGFILECHUNKING = "files_bigfilechunking"; - public static final String CAPABILITIES_FILES_UNDELETE = "files_undelete"; - public static final String CAPABILITIES_FILES_VERSIONING = "files_versioning"; - public static final String CAPABILITIES_FILES_PRIVATE_LINKS = "files_private_links"; - public static final String CAPABILITIES_APP_PROVIDERS_PREFIX = "app_providers_"; public static final String CAPABILITIES_SPACES_PREFIX = "spaces_"; + public static final String CAPABILITIES_VERSION_EDITION = "version_edition"; + public static final String CAPABILITIES_VERSION_MAJOR = "version_major"; + public static final String CAPABILITIES_VERSION_MICRO = "version_micro"; + public static final String CAPABILITIES_VERSION_MINOR = "version_minor"; + public static final String CAPABILITIES_VERSION_STRING = "version_string"; + public static final String LEGACY_CAPABILITIES_VERSION_MAYOR = "version_mayor"; // Columns of filelist table - public static final String FILE_PARENT = "parent"; - public static final String FILE_NAME = "filename"; - public static final String FILE_CREATION = "created"; - public static final String FILE_MODIFIED = "modified"; - public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data"; + public static final String FILE_ACCOUNT_OWNER = "file_owner"; public static final String FILE_CONTENT_LENGTH = "content_length"; public static final String FILE_CONTENT_TYPE = "content_type"; - public static final String FILE_STORAGE_PATH = "media_path"; - public static final String FILE_PATH = "path"; - public static final String FILE_ACCOUNT_OWNER = "file_owner"; + public static final String FILE_CREATION = "created"; + public static final String FILE_ETAG = "etag"; + public static final String FILE_ETAG_IN_CONFLICT = "etag_in_conflict"; + public static final String FILE_IS_DOWNLOADING = "is_downloading"; + public static final String FILE_KEEP_IN_SYNC = "keep_in_sync"; public static final String FILE_LAST_SYNC_DATE = "last_sync_date";// _for_properties, but let's keep it as it is public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data"; - public static final String FILE_KEEP_IN_SYNC = "keep_in_sync"; - public static final String FILE_ETAG = "etag"; - public static final String FILE_TREE_ETAG = "tree_etag"; - public static final String FILE_SHARED_VIA_LINK = "share_by_link"; - public static final String FILE_SHARED_WITH_SHAREE = "shared_via_users"; + public static final String FILE_MODIFIED = "modified"; + public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data"; + public static final String FILE_NAME = "filename"; + public static final String FILE_PARENT = "parent"; + public static final String FILE_PATH = "path"; public static final String FILE_PERMISSIONS = "permissions"; + public static final String FILE_PRIVATE_LINK = "private_link"; public static final String FILE_REMOTE_ID = "remote_id"; + public static final String FILE_SHARED_VIA_LINK = "share_by_link"; + public static final String FILE_SHARED_WITH_SHAREE = "shared_via_users"; + public static final String FILE_STORAGE_PATH = "media_path"; + public static final String FILE_TREE_ETAG = "tree_etag"; public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail"; - public static final String FILE_IS_DOWNLOADING = "is_downloading"; - public static final String FILE_ETAG_IN_CONFLICT = "etag_in_conflict"; - public static final String FILE_PRIVATE_LINK = "private_link"; // Columns of list_of_uploads table - public static final String UPLOAD_LOCAL_PATH = "local_path"; - public static final String UPLOAD_REMOTE_PATH = "remote_path"; public static final String UPLOAD_ACCOUNT_NAME = "account_name"; + public static final String UPLOAD_CREATED_BY = "created_by"; public static final String UPLOAD_FILE_SIZE = "file_size"; - public static final String UPLOAD_STATUS = "status"; - public static final String UPLOAD_LOCAL_BEHAVIOUR = "local_behaviour"; public static final String UPLOAD_FORCE_OVERWRITE = "force_overwrite"; - public static final String UPLOAD_UPLOAD_END_TIMESTAMP = "upload_end_timestamp"; public static final String UPLOAD_LAST_RESULT = "last_result"; - public static final String UPLOAD_CREATED_BY = "created_by"; + public static final String UPLOAD_LOCAL_BEHAVIOUR = "local_behaviour"; + public static final String UPLOAD_LOCAL_PATH = "local_path"; + public static final String UPLOAD_REMOTE_PATH = "remote_path"; + public static final String UPLOAD_STATUS = "status"; public static final String UPLOAD_TRANSFER_ID = "transfer_id"; + public static final String UPLOAD_UPLOAD_END_TIMESTAMP = "upload_end_timestamp"; } } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index 99f8190269d..345cdc82dee 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -45,10 +45,10 @@ data class SpaceOwner( ) data class SpaceQuota( - val remaining: Long, - val state: String, - val total: Int, - val used: Int + val remaining: Long?, + val state: String?, + val total: Long, + val used: Long?, ) data class SpaceRoot( From b513e80b28d2a15e312b245dadbb29e0207910b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Fri, 23 Dec 2022 11:51:01 +0100 Subject: [PATCH 012/152] Save spaces into database. --- .../LocalDataSourceModule.kt | 3 +- .../dependecyinjection/ViewModelModule.kt | 22 +- .../40.json | 272 +++++++++++++++--- .../owncloud/android/data/OwncloudDatabase.kt | 10 +- .../owncloud/android/data/ProviderMeta.java | 2 + .../implementation/OCLocalSpacesDataSource.kt | 52 +++- .../OCRemoteSpacesDataSource.kt | 14 +- .../data/spaces/db/SpaceSpecialEntity.kt | 57 ++++ .../android/data/spaces/db/SpacesDao.kt | 33 +++ .../android/data/spaces/db/SpacesEntity.kt | 78 +++++ .../android/domain/spaces/model/OCSpace.kt | 8 +- 11 files changed, 481 insertions(+), 70 deletions(-) create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt create mode 100644 owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt index 457731ec2d1..dabbc59ea40 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt @@ -58,6 +58,7 @@ val localDataSourceModule = module { single { OwncloudDatabase.getDatabase(androidContext()).userDao() } single { OwncloudDatabase.getDatabase(androidContext()).folderBackUpDao() } single { OwncloudDatabase.getDatabase(androidContext()).transferDao() } + single { OwncloudDatabase.getDatabase(androidContext()).spacesDao() } single { OCSharedPreferencesProvider(get()) } single { ScopedStorageProvider(dataFolder, androidContext()) } @@ -69,5 +70,5 @@ val localDataSourceModule = module { factory { OCLocalUserDataSource(get()) } factory { OCFolderBackupLocalDataSource(get()) } factory { OCLocalTransferDataSource(get()) } - factory { OCLocalSpacesDataSource() } + factory { OCLocalSpacesDataSource(get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index 4c6fef9ab4f..fadeb2d4846 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -26,33 +26,33 @@ package com.owncloud.android.dependecyinjection import com.owncloud.android.MainApp import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.domain.files.model.OCFile -import com.owncloud.android.presentation.files.details.FileDetailsViewModel -import com.owncloud.android.presentation.files.filelist.MainFileListViewModel -import com.owncloud.android.presentation.files.operations.FileOperationsViewModel -import com.owncloud.android.presentation.security.passcode.PasscodeAction +import com.owncloud.android.presentation.accounts.AccountsManagementViewModel +import com.owncloud.android.presentation.accounts.RemoveAccountDialogViewModel import com.owncloud.android.presentation.authentication.AuthenticationViewModel +import com.owncloud.android.presentation.authentication.oauth.OAuthViewModel import com.owncloud.android.presentation.capabilities.CapabilityViewModel -import com.owncloud.android.presentation.conflicts.ConflictsResolveViewModel import com.owncloud.android.presentation.common.DrawerViewModel +import com.owncloud.android.presentation.conflicts.ConflictsResolveViewModel +import com.owncloud.android.presentation.files.details.FileDetailsViewModel +import com.owncloud.android.presentation.files.filelist.MainFileListViewModel +import com.owncloud.android.presentation.files.operations.FileOperationsViewModel import com.owncloud.android.presentation.logging.LogListViewModel import com.owncloud.android.presentation.migration.MigrationViewModel -import com.owncloud.android.presentation.authentication.oauth.OAuthViewModel import com.owncloud.android.presentation.releasenotes.ReleaseNotesViewModel import com.owncloud.android.presentation.security.biometric.BiometricViewModel import com.owncloud.android.presentation.security.passcode.PassCodeViewModel +import com.owncloud.android.presentation.security.passcode.PasscodeAction import com.owncloud.android.presentation.security.pattern.PatternViewModel +import com.owncloud.android.presentation.settings.SettingsViewModel import com.owncloud.android.presentation.settings.advanced.SettingsAdvancedViewModel +import com.owncloud.android.presentation.settings.autouploads.SettingsPictureUploadsViewModel +import com.owncloud.android.presentation.settings.autouploads.SettingsVideoUploadsViewModel import com.owncloud.android.presentation.settings.logging.SettingsLogsViewModel import com.owncloud.android.presentation.settings.more.SettingsMoreViewModel -import com.owncloud.android.presentation.settings.autouploads.SettingsPictureUploadsViewModel import com.owncloud.android.presentation.settings.security.SettingsSecurityViewModel -import com.owncloud.android.presentation.settings.autouploads.SettingsVideoUploadsViewModel -import com.owncloud.android.presentation.settings.SettingsViewModel import com.owncloud.android.presentation.sharing.ShareViewModel import com.owncloud.android.presentation.transfers.TransfersViewModel -import com.owncloud.android.presentation.accounts.AccountsManagementViewModel import com.owncloud.android.ui.ReceiveExternalFilesViewModel -import com.owncloud.android.presentation.accounts.RemoveAccountDialogViewModel import com.owncloud.android.ui.preview.PreviewImageViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json index 10a6b97f5d4..62727fe2464 100644 --- a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 40, - "identityHash": "be3530216034282e4a6680bf121e3096", + "identityHash": "91afe77785b47d2d5959b2a98ce4f6fc", "entities": [ { "tableName": "folder_backup", @@ -74,7 +74,7 @@ }, { "tableName": "capabilities", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` TEXT, `version_major` INTEGER NOT NULL, `version_minor` INTEGER NOT NULL, `version_micro` INTEGER NOT NULL, `version_string` TEXT, `version_edition` TEXT, `core_pollinterval` INTEGER NOT NULL, `dav_chunking_version` TEXT NOT NULL, `sharing_api_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_write` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_public_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_days` INTEGER NOT NULL, `sharing_public_expire_date_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_upload` INTEGER NOT NULL DEFAULT -1, `sharing_public_multiple` INTEGER NOT NULL DEFAULT -1, `supports_upload_only` INTEGER NOT NULL DEFAULT -1, `sharing_resharing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_outgoing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_incoming` INTEGER NOT NULL DEFAULT -1, `sharing_user_profile_picture` INTEGER NOT NULL DEFAULT -1, `files_bigfilechunking` INTEGER NOT NULL DEFAULT -1, `files_undelete` INTEGER NOT NULL DEFAULT -1, `files_versioning` INTEGER NOT NULL DEFAULT -1, `files_private_links` INTEGER NOT NULL DEFAULT -1, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `app_providers_enabled` INTEGER, `app_providers_version` TEXT, `app_providers_appsUrl` TEXT, `app_providers_openUrl` TEXT, `app_providers_openWebUrl` TEXT, `app_providers_newUrl` TEXT, `spaces_enabled` INTEGER, `spaces_projects` INTEGER, `spaces_shareJail` INTEGER)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` TEXT, `version_major` INTEGER NOT NULL, `version_minor` INTEGER NOT NULL, `version_micro` INTEGER NOT NULL, `version_string` TEXT, `version_edition` TEXT, `core_pollinterval` INTEGER NOT NULL, `dav_chunking_version` TEXT NOT NULL, `sharing_api_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_write` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_public_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_days` INTEGER NOT NULL, `sharing_public_expire_date_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_upload` INTEGER NOT NULL DEFAULT -1, `sharing_public_multiple` INTEGER NOT NULL DEFAULT -1, `supports_upload_only` INTEGER NOT NULL DEFAULT -1, `sharing_resharing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_outgoing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_incoming` INTEGER NOT NULL DEFAULT -1, `sharing_user_profile_picture` INTEGER NOT NULL DEFAULT -1, `files_bigfilechunking` INTEGER NOT NULL DEFAULT -1, `files_undelete` INTEGER NOT NULL DEFAULT -1, `files_versioning` INTEGER NOT NULL DEFAULT -1, `files_private_links` INTEGER NOT NULL DEFAULT -1, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `enabled` INTEGER, `version` TEXT, `appsUrl` TEXT, `openUrl` TEXT, `openWebUrl` TEXT, `newUrl` TEXT, `enabled` INTEGER, `projects` INTEGER, `shareJail` INTEGER)", "fields": [ { "fieldPath": "accountName", @@ -271,55 +271,55 @@ }, { "fieldPath": "appProviders.enabled", - "columnName": "app_providers_enabled", + "columnName": "enabled", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "appProviders.version", - "columnName": "app_providers_version", + "columnName": "version", "affinity": "TEXT", "notNull": false }, { "fieldPath": "appProviders.appsUrl", - "columnName": "app_providers_appsUrl", + "columnName": "appsUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "appProviders.openUrl", - "columnName": "app_providers_openUrl", + "columnName": "openUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "appProviders.openWebUrl", - "columnName": "app_providers_openWebUrl", + "columnName": "openWebUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "appProviders.newUrl", - "columnName": "app_providers_newUrl", + "columnName": "newUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "spaces.enabled", - "columnName": "spaces_enabled", + "columnName": "enabled", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "spaces.projects", - "columnName": "spaces_projects", + "columnName": "projects", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "spaces.shareJail", - "columnName": "spaces_shareJail", + "columnName": "shareJail", "affinity": "INTEGER", "notNull": false } @@ -651,38 +651,6 @@ "indices": [], "foreignKeys": [] }, - { - "tableName": "user_quotas", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountName` TEXT NOT NULL, `used` INTEGER NOT NULL, `available` INTEGER NOT NULL, PRIMARY KEY(`accountName`))", - "fields": [ - { - "fieldPath": "accountName", - "columnName": "accountName", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "used", - "columnName": "used", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "available", - "columnName": "available", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "accountName" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [] - }, { "tableName": "transfers", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localPath` TEXT NOT NULL, `remotePath` TEXT NOT NULL, `accountName` TEXT NOT NULL, `fileSize` INTEGER NOT NULL, `status` INTEGER NOT NULL, `localBehaviour` INTEGER NOT NULL, `forceOverwrite` INTEGER NOT NULL, `transferEndTimestamp` INTEGER, `lastResult` INTEGER, `createdBy` INTEGER NOT NULL, `transferId` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", @@ -768,12 +736,228 @@ }, "indices": [], "foreignKeys": [] + }, + { + "tableName": "spaces", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `owner_id` TEXT NOT NULL, `rootETag` TEXT NOT NULL, `rootId` TEXT NOT NULL, `rootWebDavUrl` TEXT NOT NULL, `web_url` TEXT NOT NULL, `description` TEXT, `remaining` INTEGER, `state` TEXT, `total` INTEGER, `used` INTEGER, PRIMARY KEY(`space_id`))", + "fields": [ + { + "fieldPath": "driveAlias", + "columnName": "drive_alias", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "driveType", + "columnName": "drive_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "space_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastModifiedDateTime", + "columnName": "last_modified_date_time", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "owner_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rootETag", + "columnName": "rootETag", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rootId", + "columnName": "rootId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rootWebDavUrl", + "columnName": "rootWebDavUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "webUrl", + "columnName": "web_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "quota.remaining", + "columnName": "remaining", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "quota.state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "quota.total", + "columnName": "total", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "quota.used", + "columnName": "used", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "space_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "spaces_special", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`spaces_permission_space_id` TEXT NOT NULL, `eTag` TEXT NOT NULL, `file_mime_type` TEXT NOT NULL, `special_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `size` INTEGER NOT NULL, `specialFolderName` TEXT NOT NULL, `webDavUrl` TEXT NOT NULL, PRIMARY KEY(`special_id`), FOREIGN KEY(`spaces_permission_space_id`) REFERENCES `spaces`(`space_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "spaceId", + "columnName": "spaces_permission_space_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "eTag", + "columnName": "eTag", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fileMymeType", + "columnName": "file_mime_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "special_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastModifiedDateTime", + "columnName": "last_modified_date_time", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "specialFolderName", + "columnName": "specialFolderName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "webDavUrl", + "columnName": "webDavUrl", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "special_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "spaces", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "spaces_permission_space_id" + ], + "referencedColumns": [ + "space_id" + ] + } + ] + }, + { + "tableName": "user_quotas", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountName` TEXT NOT NULL, `used` INTEGER NOT NULL, `available` INTEGER NOT NULL, PRIMARY KEY(`accountName`))", + "fields": [ + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "used", + "columnName": "used", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "available", + "columnName": "available", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "accountName" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'be3530216034282e4a6680bf121e3096')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '91afe77785b47d2d5959b2a98ce4f6fc')" ] } } \ No newline at end of file diff --git a/owncloudData/src/main/java/com/owncloud/android/data/OwncloudDatabase.kt b/owncloudData/src/main/java/com/owncloud/android/data/OwncloudDatabase.kt index 8ab36962772..b2a088d9c6d 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/OwncloudDatabase.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/OwncloudDatabase.kt @@ -48,6 +48,9 @@ import com.owncloud.android.data.migrations.MIGRATION_35_36 import com.owncloud.android.data.migrations.MIGRATION_37_38 import com.owncloud.android.data.sharing.shares.db.OCShareDao import com.owncloud.android.data.sharing.shares.db.OCShareEntity +import com.owncloud.android.data.spaces.db.SpaceSpecialEntity +import com.owncloud.android.data.spaces.db.SpacesDao +import com.owncloud.android.data.spaces.db.SpacesEntity import com.owncloud.android.data.transfers.db.OCTransferEntity import com.owncloud.android.data.transfers.db.TransferDao import com.owncloud.android.data.user.db.UserDao @@ -60,8 +63,10 @@ import com.owncloud.android.data.user.db.UserQuotaEntity OCFileEntity::class, OCFileSyncEntity::class, OCShareEntity::class, - UserQuotaEntity::class, OCTransferEntity::class, + SpacesEntity::class, + SpaceSpecialEntity::class, + UserQuotaEntity::class, ], autoMigrations = [ AutoMigration(from = 36, to = 37), @@ -76,8 +81,9 @@ abstract class OwncloudDatabase : RoomDatabase() { abstract fun fileDao(): FileDao abstract fun folderBackUpDao(): FolderBackupDao abstract fun shareDao(): OCShareDao - abstract fun userDao(): UserDao + abstract fun spacesDao(): SpacesDao abstract fun transferDao(): TransferDao + abstract fun userDao(): UserDao companion object { @Volatile diff --git a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java index de0b2d10afa..525983566d1 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java +++ b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java @@ -42,6 +42,8 @@ static public class ProviderTableMeta implements BaseColumns { public static final String FILES_TABLE_NAME = "files"; public static final String FOLDER_BACKUP_TABLE_NAME = "folder_backup"; public static final String OCSHARES_TABLE_NAME = "ocshares"; + public static final String SPACES_TABLE_NAME = "spaces"; + public static final String SPACES_SPECIAL_TABLE_NAME = "spaces_special"; public static final String TRANSFERS_TABLE_NAME = "transfers"; public static final String USER_QUOTAS_TABLE_NAME = "user_quotas"; diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 7e432419b73..2582f18a4c0 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -19,12 +19,60 @@ package com.owncloud.android.data.spaces.datasources.implementation import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource +import com.owncloud.android.data.spaces.db.SpaceQuotaEntity +import com.owncloud.android.data.spaces.db.SpaceSpecialEntity +import com.owncloud.android.data.spaces.db.SpacesDao +import com.owncloud.android.data.spaces.db.SpacesEntity import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.domain.spaces.model.SpaceSpecial class OCLocalSpacesDataSource( + private val spacesDao: SpacesDao, +) : LocalSpacesDataSource { -): LocalSpacesDataSource { override fun saveSpacesForAccount(listOfSpaces: List) { - TODO("Not yet implemented") + val spaceEntities = mutableListOf() + val spaceSpecialEntities = mutableListOf() + + listOfSpaces.forEach { spaceModel -> + spaceEntities.add(spaceModel.toEntity()) + spaceModel.special?.let { listOfSpacesSpecials -> + spaceSpecialEntities.addAll(listOfSpacesSpecials.map { it.toEntity(spaceModel.id) }) + } + } + + spacesDao.insertOrReplaceSpaces(spaceEntities) + spacesDao.insertOrReplaceSpecials(spaceSpecialEntities) } + + private fun OCSpace.toEntity() = + SpacesEntity( + driveAlias = driveAlias, + driveType = driveType, + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + ownerId = owner.user.id, + quota = quota?.let { quotaModel -> + SpaceQuotaEntity(remaining = quotaModel.remaining, state = quotaModel.state, total = quotaModel.total, used = quotaModel.used) + }, + rootETag = root.eTag, + rootId = root.id, + rootWebDavUrl = root.webDavUrl, + webUrl = webUrl, + description = description, + ) + + private fun SpaceSpecial.toEntity(spaceId: String): SpaceSpecialEntity = + SpaceSpecialEntity( + spaceId = spaceId, + eTag = eTag, + fileMymeType = file.mimeType, + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + size = size, + specialFolderName = specialFolder.name, + webDavUrl = webDavUrl + ) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt index 20e473b9268..a56a2c8a1b9 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt @@ -56,12 +56,14 @@ class OCRemoteSpacesDataSource( id = owner.user.id ) ), - quota = SpaceQuota( - remaining = quota.remaining, - state = quota.state, - total = quota.total, - used = quota.used - ), + quota = quota?.let { quotaResponse -> + SpaceQuota( + remaining = quotaResponse.remaining, + state = quotaResponse.state, + total = quotaResponse.total, + used = quotaResponse.used, + ) + }, root = SpaceRoot( eTag = root.eTag, id = root.id, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt new file mode 100644 index 00000000000..61238c57207 --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt @@ -0,0 +1,57 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.db + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.PrimaryKey +import com.owncloud.android.data.ProviderMeta +import com.owncloud.android.data.spaces.db.SpaceSpecialEntity.Companion.SPACES_SPECIAL_SPACE_ID + +@Entity( + tableName = ProviderMeta.ProviderTableMeta.SPACES_SPECIAL_TABLE_NAME, + foreignKeys = [ForeignKey( + entity = SpacesEntity::class, + parentColumns = arrayOf(SpacesEntity.SPACES_ID), + childColumns = arrayOf(SPACES_SPECIAL_SPACE_ID), + onDelete = ForeignKey.CASCADE + )] +) +data class SpaceSpecialEntity( + @ColumnInfo(name = SPACES_SPECIAL_SPACE_ID) + val spaceId: String, + val eTag: String, + @ColumnInfo(name = SPACES_SPECIAL_FILE_MIME_TYPE) + val fileMymeType: String, + @ColumnInfo(name = SPACES_SPECIAL_ID) @PrimaryKey + val id: String, + @ColumnInfo(name = SpacesEntity.SPACES_LAST_MODIFIED_DATE_TIME) + val lastModifiedDateTime: String, + val name: String, + val size: Int, + val specialFolderName: String, + val webDavUrl: String +) { + companion object { + const val SPACES_SPECIAL_SPACE_ID = "spaces_permission_space_id" + const val SPACES_SPECIAL_FILE_MIME_TYPE = "file_mime_type" + const val SPACES_SPECIAL_ID = "special_id" + } +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt new file mode 100644 index 00000000000..c88a831f4a8 --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -0,0 +1,33 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.db + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy + +@Dao +interface SpacesDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrReplaceSpaces(listOfSpacesEntities: List): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrReplaceSpecials(listOfSpecialEntities: List): List + +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt new file mode 100644 index 00000000000..c45bc741cf1 --- /dev/null +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt @@ -0,0 +1,78 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.db + +import androidx.room.ColumnInfo +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.Relation +import com.owncloud.android.data.ProviderMeta +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID + +data class SpacesWithSpecials( + @Embedded val space: SpacesEntity, + @Relation( + parentColumn = SPACES_ID, + entityColumn = SpaceSpecialEntity.SPACES_SPECIAL_SPACE_ID, + ) + val specials: List +) + +@Entity(tableName = ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME) +data class SpacesEntity( + @ColumnInfo(name = SPACES_DRIVE_ALIAS) + val driveAlias: String, + @ColumnInfo(name = SPACES_DRIVE_TYPE) + val driveType: String, + @PrimaryKey @ColumnInfo(name = SPACES_ID) + val id: String, + @ColumnInfo(name = SPACES_LAST_MODIFIED_DATE_TIME) + val lastModifiedDateTime: String, + val name: String, + @ColumnInfo(name = SPACES_OWNER_ID) + val ownerId: String, + @Embedded + val quota: SpaceQuotaEntity?, + val rootETag: String, + val rootId: String, + val rootWebDavUrl: String, + @ColumnInfo(name = SPACES_WEB_URL) + val webUrl: String, + val description: String?, +) { + + companion object { + const val DRIVE_TYPE_PERSONAL = "personal" + const val DRIVE_TYPE_PROJECT = "project" + const val SPACES_ID = "space_id" + const val SPACES_DRIVE_ALIAS = "drive_alias" + const val SPACES_DRIVE_TYPE = "drive_type" + const val SPACES_LAST_MODIFIED_DATE_TIME = "last_modified_date_time" + const val SPACES_WEB_URL = "web_url" + const val SPACES_OWNER_ID = "owner_id" + } +} + +data class SpaceQuotaEntity( + val remaining: Long, + val state: String, + val total: Int, + val used: Int +) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index 345cdc82dee..ccb4e29fa83 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -25,7 +25,7 @@ data class OCSpace( val lastModifiedDateTime: String, val name: String, val owner: SpaceOwner, - val quota: SpaceQuota, + val quota: SpaceQuota?, val root: SpaceRoot, val webUrl: String, val description: String?, @@ -35,8 +35,8 @@ data class OCSpace( fun isProject() = driveType == DRIVE_TYPE_PROJECT companion object { - const val DRIVE_TYPE_PERSONAL = "personal" - const val DRIVE_TYPE_PROJECT = "project" + private const val DRIVE_TYPE_PERSONAL = "personal" + private const val DRIVE_TYPE_PROJECT = "project" } } @@ -78,7 +78,7 @@ data class File( ) data class SpaceGrantedTo( - val user: SpaceUser + val user: SpaceUser? ) data class SpacePermission( From 3a2947fae6371a2edf5ceb3ad72c590da2915f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Fri, 23 Dec 2022 20:57:27 +0100 Subject: [PATCH 013/152] Add account name to the spaces and save space specials on a different table --- .../40.json | 30 +++++-- .../datasources/RemoteSpacesDataSource.kt | 2 +- .../implementation/OCLocalSpacesDataSource.kt | 9 +- .../OCRemoteSpacesDataSource.kt | 7 +- .../data/spaces/db/SpaceSpecialEntity.kt | 15 ++-- .../android/data/spaces/db/SpacesDao.kt | 84 +++++++++++++++++++ .../android/data/spaces/db/SpacesEntity.kt | 13 ++- .../spaces/repository/OCSpacesRepository.kt | 4 +- .../android/domain/spaces/SpacesRepository.kt | 2 +- .../android/domain/spaces/model/OCSpace.kt | 1 + .../RefreshSpacesFromServerAsyncUseCase.kt | 10 ++- 11 files changed, 150 insertions(+), 27 deletions(-) diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json index 62727fe2464..f16ece5abc1 100644 --- a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -2,7 +2,11 @@ "formatVersion": 1, "database": { "version": 40, +<<<<<<< HEAD "identityHash": "91afe77785b47d2d5959b2a98ce4f6fc", +======= + "identityHash": "3e2b989b89b3c14a02a8c4e259a8c022", +>>>>>>> Add account name to the spaces and save space specials on a different table "entities": [ { "tableName": "folder_backup", @@ -739,8 +743,14 @@ }, { "tableName": "spaces", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `owner_id` TEXT NOT NULL, `rootETag` TEXT NOT NULL, `rootId` TEXT NOT NULL, `rootWebDavUrl` TEXT NOT NULL, `web_url` TEXT NOT NULL, `description` TEXT, `remaining` INTEGER, `state` TEXT, `total` INTEGER, `used` INTEGER, PRIMARY KEY(`space_id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_name` TEXT NOT NULL, `drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `owner_id` TEXT NOT NULL, `rootETag` TEXT NOT NULL, `rootId` TEXT NOT NULL, `rootWebDavUrl` TEXT NOT NULL, `web_url` TEXT NOT NULL, `description` TEXT, `remaining` INTEGER, `state` TEXT, `total` INTEGER, `used` INTEGER, PRIMARY KEY(`account_name`, `space_id`))", "fields": [ + { + "fieldPath": "accountName", + "columnName": "account_name", + "affinity": "TEXT", + "notNull": true + }, { "fieldPath": "driveAlias", "columnName": "drive_alias", @@ -834,6 +844,7 @@ ], "primaryKey": { "columnNames": [ + "account_name", "space_id" ], "autoGenerate": false @@ -843,11 +854,17 @@ }, { "tableName": "spaces_special", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`spaces_permission_space_id` TEXT NOT NULL, `eTag` TEXT NOT NULL, `file_mime_type` TEXT NOT NULL, `special_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `size` INTEGER NOT NULL, `specialFolderName` TEXT NOT NULL, `webDavUrl` TEXT NOT NULL, PRIMARY KEY(`special_id`), FOREIGN KEY(`spaces_permission_space_id`) REFERENCES `spaces`(`space_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`spaces_special_account_name` TEXT NOT NULL, `spaces_special_space_id` TEXT NOT NULL, `eTag` TEXT NOT NULL, `file_mime_type` TEXT NOT NULL, `special_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `size` INTEGER NOT NULL, `specialFolderName` TEXT NOT NULL, `webDavUrl` TEXT NOT NULL, PRIMARY KEY(`spaces_special_space_id`, `special_id`), FOREIGN KEY(`spaces_special_account_name`, `spaces_special_space_id`) REFERENCES `spaces`(`account_name`, `space_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ + { + "fieldPath": "accountName", + "columnName": "spaces_special_account_name", + "affinity": "TEXT", + "notNull": true + }, { "fieldPath": "spaceId", - "columnName": "spaces_permission_space_id", + "columnName": "spaces_special_space_id", "affinity": "TEXT", "notNull": true }, @@ -902,6 +919,7 @@ ], "primaryKey": { "columnNames": [ + "spaces_special_space_id", "special_id" ], "autoGenerate": false @@ -913,9 +931,11 @@ "onDelete": "CASCADE", "onUpdate": "NO ACTION", "columns": [ - "spaces_permission_space_id" + "spaces_special_account_name", + "spaces_special_space_id" ], "referencedColumns": [ + "account_name", "space_id" ] } @@ -960,4 +980,4 @@ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '91afe77785b47d2d5959b2a98ce4f6fc')" ] } -} \ No newline at end of file +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt index 5152b53c5b7..1f06f414551 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt @@ -21,5 +21,5 @@ package com.owncloud.android.data.spaces.datasources import com.owncloud.android.domain.spaces.model.OCSpace interface RemoteSpacesDataSource { - fun refreshSpacesForAccount(): List + fun refreshSpacesForAccount(accountName: String): List } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 2582f18a4c0..cbce0417bf5 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -37,16 +37,16 @@ class OCLocalSpacesDataSource( listOfSpaces.forEach { spaceModel -> spaceEntities.add(spaceModel.toEntity()) spaceModel.special?.let { listOfSpacesSpecials -> - spaceSpecialEntities.addAll(listOfSpacesSpecials.map { it.toEntity(spaceModel.id) }) + spaceSpecialEntities.addAll(listOfSpacesSpecials.map { it.toEntity(spaceModel.accountName, spaceModel.id) }) } } - spacesDao.insertOrReplaceSpaces(spaceEntities) - spacesDao.insertOrReplaceSpecials(spaceSpecialEntities) + spacesDao.upsertOrDeleteSpaces(spaceEntities, spaceSpecialEntities) } private fun OCSpace.toEntity() = SpacesEntity( + accountName = accountName, driveAlias = driveAlias, driveType = driveType, id = id, @@ -63,8 +63,9 @@ class OCLocalSpacesDataSource( description = description, ) - private fun SpaceSpecial.toEntity(spaceId: String): SpaceSpecialEntity = + private fun SpaceSpecial.toEntity(accountName: String, spaceId: String): SpaceSpecialEntity = SpaceSpecialEntity( + accountName = accountName, spaceId = spaceId, eTag = eTag, fileMymeType = file.mimeType, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt index a56a2c8a1b9..f5e31d62153 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt @@ -36,16 +36,17 @@ import com.owncloud.android.lib.resources.spaces.services.SpacesService class OCRemoteSpacesDataSource( private val spacesService: SpacesService ) : RemoteSpacesDataSource { - override fun refreshSpacesForAccount(): List { + override fun refreshSpacesForAccount(accountName: String): List { val spacesResponse = executeRemoteOperation { spacesService.getSpaces() } - return spacesResponse.map { it.toModel() } + return spacesResponse.map { it.toModel(accountName) } } - private fun SpaceResponse.toModel(): OCSpace = + private fun SpaceResponse.toModel(accountName: String): OCSpace = OCSpace( + accountName = accountName, driveAlias = driveAlias, driveType = driveType, id = id, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt index 61238c57207..38349f5c656 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt @@ -21,26 +21,30 @@ package com.owncloud.android.data.spaces.db import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey -import androidx.room.PrimaryKey import com.owncloud.android.data.ProviderMeta +import com.owncloud.android.data.spaces.db.SpaceSpecialEntity.Companion.SPACES_SPECIAL_ACCOUNT_NAME +import com.owncloud.android.data.spaces.db.SpaceSpecialEntity.Companion.SPACES_SPECIAL_ID import com.owncloud.android.data.spaces.db.SpaceSpecialEntity.Companion.SPACES_SPECIAL_SPACE_ID @Entity( tableName = ProviderMeta.ProviderTableMeta.SPACES_SPECIAL_TABLE_NAME, + primaryKeys = [SPACES_SPECIAL_SPACE_ID, SPACES_SPECIAL_ID], foreignKeys = [ForeignKey( entity = SpacesEntity::class, - parentColumns = arrayOf(SpacesEntity.SPACES_ID), - childColumns = arrayOf(SPACES_SPECIAL_SPACE_ID), + parentColumns = arrayOf(SpacesEntity.SPACES_ACCOUNT_NAME, SpacesEntity.SPACES_ID), + childColumns = arrayOf(SPACES_SPECIAL_ACCOUNT_NAME, SPACES_SPECIAL_SPACE_ID), onDelete = ForeignKey.CASCADE )] ) data class SpaceSpecialEntity( + @ColumnInfo(name = SPACES_SPECIAL_ACCOUNT_NAME) + val accountName: String, @ColumnInfo(name = SPACES_SPECIAL_SPACE_ID) val spaceId: String, val eTag: String, @ColumnInfo(name = SPACES_SPECIAL_FILE_MIME_TYPE) val fileMymeType: String, - @ColumnInfo(name = SPACES_SPECIAL_ID) @PrimaryKey + @ColumnInfo(name = SPACES_SPECIAL_ID) val id: String, @ColumnInfo(name = SpacesEntity.SPACES_LAST_MODIFIED_DATE_TIME) val lastModifiedDateTime: String, @@ -50,7 +54,8 @@ data class SpaceSpecialEntity( val webDavUrl: String ) { companion object { - const val SPACES_SPECIAL_SPACE_ID = "spaces_permission_space_id" + const val SPACES_SPECIAL_ACCOUNT_NAME = "spaces_special_account_name" + const val SPACES_SPECIAL_SPACE_ID = "spaces_special_space_id" const val SPACES_SPECIAL_FILE_MIME_TYPE = "file_mime_type" const val SPACES_SPECIAL_ID = "special_id" } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index c88a831f4a8..08f9beab3b4 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -21,13 +21,97 @@ package com.owncloud.android.data.spaces.db import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import com.owncloud.android.data.ProviderMeta +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.DRIVE_TYPE_PERSONAL +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.DRIVE_TYPE_PROJECT +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ACCOUNT_NAME +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_DRIVE_TYPE +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID @Dao interface SpacesDao { + @Transaction + fun upsertOrDeleteSpaces( + listOfSpacesEntities: List, + listOfSpecialEntities: List, + ) { + val currentAccountName = listOfSpacesEntities.first().accountName + val currentSpaces = getAllSpacesForAccount(currentAccountName) + + // Delete spaces that are not attached to the current account anymore + val spacesToDelete = currentSpaces.filterNot { oldSpace -> + listOfSpacesEntities.any { it.id == oldSpace.id } + } + + spacesToDelete.forEach { spaceToDelete -> + deleteSpaceForAccountById(accountName = spaceToDelete.accountName, spaceId = spaceToDelete.id) + } + + // Upsert new spaces + insertOrReplaceSpaces(listOfSpacesEntities) + insertOrReplaceSpecials(listOfSpecialEntities) + } + + // TODO: Use upsert instead of insert and replace @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrReplaceSpaces(listOfSpacesEntities: List): List + // TODO: Use upsert instead of insert and replace @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrReplaceSpecials(listOfSpecialEntities: List): List + @Query(SELECT_ALL_SPACES) + fun getAllSpacesForAccount( + accountName: String, + ): List + + @Query(SELECT_PERSONAL_SPACE) + fun getPersonalSpacesForAccount( + accountName: String, + ): List + + @Query(SELECT_PROJECT_SPACES) + fun getProjectSpacesForAccount( + accountName: String, + ): List + + @Query(DELETE_ALL_SPACES_FOR_ACCOUNT) + fun deleteSpacesForAccount(accountName: String) + + @Query(DELETE_SPACE_FOR_ACCOUNT_BY_ID) + fun deleteSpaceForAccountById(accountName: String, spaceId: String) + + companion object { + private const val SELECT_ALL_SPACES = """ + SELECT * + FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} + WHERE $SPACES_ACCOUNT_NAME = :accountName + """ + + private const val SELECT_PERSONAL_SPACE = """ + SELECT * + FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} + WHERE $SPACES_ACCOUNT_NAME = :accountName AND $SPACES_DRIVE_TYPE LIKE '$DRIVE_TYPE_PERSONAL' + """ + + private const val SELECT_PROJECT_SPACES = """ + SELECT * + FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} + WHERE $SPACES_ACCOUNT_NAME = :accountName AND $SPACES_DRIVE_TYPE LIKE '$DRIVE_TYPE_PROJECT' + """ + + private const val DELETE_ALL_SPACES_FOR_ACCOUNT = """ + DELETE + FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} + WHERE $SPACES_ACCOUNT_NAME = :accountName + """ + + private const val DELETE_SPACE_FOR_ACCOUNT_BY_ID = """ + DELETE + FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} + WHERE $SPACES_ACCOUNT_NAME = :accountName AND $SPACES_ID LIKE :spaceId + """ + } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt index c45bc741cf1..0952e1c0590 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt @@ -21,9 +21,10 @@ package com.owncloud.android.data.spaces.db import androidx.room.ColumnInfo import androidx.room.Embedded import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room.Index import androidx.room.Relation import com.owncloud.android.data.ProviderMeta +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ACCOUNT_NAME import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID data class SpacesWithSpecials( @@ -35,13 +36,18 @@ data class SpacesWithSpecials( val specials: List ) -@Entity(tableName = ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME) +@Entity( + tableName = ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME, + primaryKeys = [SPACES_ACCOUNT_NAME, SPACES_ID], +) data class SpacesEntity( + @ColumnInfo(name = SPACES_ACCOUNT_NAME) + val accountName: String, @ColumnInfo(name = SPACES_DRIVE_ALIAS) val driveAlias: String, @ColumnInfo(name = SPACES_DRIVE_TYPE) val driveType: String, - @PrimaryKey @ColumnInfo(name = SPACES_ID) + @ColumnInfo(name = SPACES_ID) val id: String, @ColumnInfo(name = SPACES_LAST_MODIFIED_DATE_TIME) val lastModifiedDateTime: String, @@ -61,6 +67,7 @@ data class SpacesEntity( companion object { const val DRIVE_TYPE_PERSONAL = "personal" const val DRIVE_TYPE_PROJECT = "project" + const val SPACES_ACCOUNT_NAME = "account_name" const val SPACES_ID = "space_id" const val SPACES_DRIVE_ALIAS = "drive_alias" const val SPACES_DRIVE_TYPE = "drive_type" diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt index cfe837932ea..a444e00ead7 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -26,8 +26,8 @@ class OCSpacesRepository( private val localSpacesDataSource: LocalSpacesDataSource, private val remoteSpacesDataSource: RemoteSpacesDataSource, ) : SpacesRepository { - override fun refreshSpacesForAccount() { - remoteSpacesDataSource.refreshSpacesForAccount().also { listOfSpaces -> + override fun refreshSpacesForAccount(accountName: String) { + remoteSpacesDataSource.refreshSpacesForAccount(accountName).also { listOfSpaces -> localSpacesDataSource.saveSpacesForAccount(listOfSpaces) } } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt index 779d7a815ac..e10e1c1b43d 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -19,5 +19,5 @@ package com.owncloud.android.domain.spaces interface SpacesRepository { - fun refreshSpacesForAccount() + fun refreshSpacesForAccount(accountName: String) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index ccb4e29fa83..6479368c569 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -19,6 +19,7 @@ package com.owncloud.android.domain.spaces.model data class OCSpace( + val accountName: String, val driveAlias: String, val driveType: String, val id: String, diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/RefreshSpacesFromServerAsyncUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/RefreshSpacesFromServerAsyncUseCase.kt index c37d1b7334d..87b666d4569 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/RefreshSpacesFromServerAsyncUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/RefreshSpacesFromServerAsyncUseCase.kt @@ -23,8 +23,12 @@ import com.owncloud.android.domain.spaces.SpacesRepository class RefreshSpacesFromServerAsyncUseCase( private val spacesRepository: SpacesRepository -) : BaseUseCaseWithResult() { +) : BaseUseCaseWithResult() { - override fun run(params: Unit) = - spacesRepository.refreshSpacesForAccount() + override fun run(params: Params) = + spacesRepository.refreshSpacesForAccount(accountName = params.accountName) + + data class Params( + val accountName: String, + ) } From 507ea53297ca1dfe34f9bdd7c9d7f25b2515bf23 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 27 Dec 2022 16:55:48 +0100 Subject: [PATCH 014/152] Some type changes and renamings --- .../40.json | 84 +++++++++---------- .../implementation/OCLocalSpacesDataSource.kt | 7 +- .../OCRemoteSpacesDataSource.kt | 4 +- .../data/spaces/db/SpaceSpecialEntity.kt | 16 +++- .../android/data/spaces/db/SpacesEntity.kt | 58 +++++++++---- .../android/domain/spaces/model/OCSpace.kt | 12 +-- 6 files changed, 109 insertions(+), 72 deletions(-) diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json index f16ece5abc1..a33064a4695 100644 --- a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -2,11 +2,7 @@ "formatVersion": 1, "database": { "version": 40, -<<<<<<< HEAD - "identityHash": "91afe77785b47d2d5959b2a98ce4f6fc", -======= - "identityHash": "3e2b989b89b3c14a02a8c4e259a8c022", ->>>>>>> Add account name to the spaces and save space specials on a different table + "identityHash": "69f96a3d4d578df91be813a866bfbed1", "entities": [ { "tableName": "folder_backup", @@ -78,7 +74,7 @@ }, { "tableName": "capabilities", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` TEXT, `version_major` INTEGER NOT NULL, `version_minor` INTEGER NOT NULL, `version_micro` INTEGER NOT NULL, `version_string` TEXT, `version_edition` TEXT, `core_pollinterval` INTEGER NOT NULL, `dav_chunking_version` TEXT NOT NULL, `sharing_api_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_write` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_public_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_days` INTEGER NOT NULL, `sharing_public_expire_date_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_upload` INTEGER NOT NULL DEFAULT -1, `sharing_public_multiple` INTEGER NOT NULL DEFAULT -1, `supports_upload_only` INTEGER NOT NULL DEFAULT -1, `sharing_resharing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_outgoing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_incoming` INTEGER NOT NULL DEFAULT -1, `sharing_user_profile_picture` INTEGER NOT NULL DEFAULT -1, `files_bigfilechunking` INTEGER NOT NULL DEFAULT -1, `files_undelete` INTEGER NOT NULL DEFAULT -1, `files_versioning` INTEGER NOT NULL DEFAULT -1, `files_private_links` INTEGER NOT NULL DEFAULT -1, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `enabled` INTEGER, `version` TEXT, `appsUrl` TEXT, `openUrl` TEXT, `openWebUrl` TEXT, `newUrl` TEXT, `enabled` INTEGER, `projects` INTEGER, `shareJail` INTEGER)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` TEXT, `version_major` INTEGER NOT NULL, `version_minor` INTEGER NOT NULL, `version_micro` INTEGER NOT NULL, `version_string` TEXT, `version_edition` TEXT, `core_pollinterval` INTEGER NOT NULL, `dav_chunking_version` TEXT NOT NULL, `sharing_api_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_read_write` INTEGER NOT NULL DEFAULT -1, `sharing_public_password_enforced_public_only` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_enabled` INTEGER NOT NULL DEFAULT -1, `sharing_public_expire_date_days` INTEGER NOT NULL, `sharing_public_expire_date_enforced` INTEGER NOT NULL DEFAULT -1, `sharing_public_upload` INTEGER NOT NULL DEFAULT -1, `sharing_public_multiple` INTEGER NOT NULL DEFAULT -1, `supports_upload_only` INTEGER NOT NULL DEFAULT -1, `sharing_resharing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_outgoing` INTEGER NOT NULL DEFAULT -1, `sharing_federation_incoming` INTEGER NOT NULL DEFAULT -1, `sharing_user_profile_picture` INTEGER NOT NULL DEFAULT -1, `files_bigfilechunking` INTEGER NOT NULL DEFAULT -1, `files_undelete` INTEGER NOT NULL DEFAULT -1, `files_versioning` INTEGER NOT NULL DEFAULT -1, `files_private_links` INTEGER NOT NULL DEFAULT -1, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `app_providers_enabled` INTEGER, `app_providers_version` TEXT, `app_providers_appsUrl` TEXT, `app_providers_openUrl` TEXT, `app_providers_openWebUrl` TEXT, `app_providers_newUrl` TEXT, `spaces_enabled` INTEGER, `spaces_projects` INTEGER, `spaces_shareJail` INTEGER)", "fields": [ { "fieldPath": "accountName", @@ -275,55 +271,55 @@ }, { "fieldPath": "appProviders.enabled", - "columnName": "enabled", + "columnName": "app_providers_enabled", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "appProviders.version", - "columnName": "version", + "columnName": "app_providers_version", "affinity": "TEXT", "notNull": false }, { "fieldPath": "appProviders.appsUrl", - "columnName": "appsUrl", + "columnName": "app_providers_appsUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "appProviders.openUrl", - "columnName": "openUrl", + "columnName": "app_providers_openUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "appProviders.openWebUrl", - "columnName": "openWebUrl", + "columnName": "app_providers_openWebUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "appProviders.newUrl", - "columnName": "newUrl", + "columnName": "app_providers_newUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "spaces.enabled", - "columnName": "enabled", + "columnName": "spaces_enabled", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "spaces.projects", - "columnName": "projects", + "columnName": "spaces_projects", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "spaces.shareJail", - "columnName": "shareJail", + "columnName": "spaces_shareJail", "affinity": "INTEGER", "notNull": false } @@ -743,7 +739,7 @@ }, { "tableName": "spaces", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_name` TEXT NOT NULL, `drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `owner_id` TEXT NOT NULL, `rootETag` TEXT NOT NULL, `rootId` TEXT NOT NULL, `rootWebDavUrl` TEXT NOT NULL, `web_url` TEXT NOT NULL, `description` TEXT, `remaining` INTEGER, `state` TEXT, `total` INTEGER, `used` INTEGER, PRIMARY KEY(`account_name`, `space_id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_name` TEXT NOT NULL, `drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `owner_id` TEXT NOT NULL, `web_url` TEXT NOT NULL, `description` TEXT, `quota_remaining` INTEGER, `quota_state` TEXT, `quota_total` INTEGER, `quota_used` INTEGER, `root_etag` TEXT, `root_id` TEXT, `root_web_dav_url` TEXT, PRIMARY KEY(`account_name`, `space_id`))", "fields": [ { "fieldPath": "accountName", @@ -787,24 +783,6 @@ "affinity": "TEXT", "notNull": true }, - { - "fieldPath": "rootETag", - "columnName": "rootETag", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "rootId", - "columnName": "rootId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "rootWebDavUrl", - "columnName": "rootWebDavUrl", - "affinity": "TEXT", - "notNull": true - }, { "fieldPath": "webUrl", "columnName": "web_url", @@ -819,27 +797,45 @@ }, { "fieldPath": "quota.remaining", - "columnName": "remaining", + "columnName": "quota_remaining", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "quota.state", - "columnName": "state", + "columnName": "quota_state", "affinity": "TEXT", "notNull": false }, { "fieldPath": "quota.total", - "columnName": "total", + "columnName": "quota_total", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "quota.used", - "columnName": "used", + "columnName": "quota_used", "affinity": "INTEGER", "notNull": false + }, + { + "fieldPath": "root.eTag", + "columnName": "root_etag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "root.id", + "columnName": "root_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "root.webDavUrl", + "columnName": "root_web_dav_url", + "affinity": "TEXT", + "notNull": false } ], "primaryKey": { @@ -854,7 +850,7 @@ }, { "tableName": "spaces_special", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`spaces_special_account_name` TEXT NOT NULL, `spaces_special_space_id` TEXT NOT NULL, `eTag` TEXT NOT NULL, `file_mime_type` TEXT NOT NULL, `special_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `size` INTEGER NOT NULL, `specialFolderName` TEXT NOT NULL, `webDavUrl` TEXT NOT NULL, PRIMARY KEY(`spaces_special_space_id`, `special_id`), FOREIGN KEY(`spaces_special_account_name`, `spaces_special_space_id`) REFERENCES `spaces`(`account_name`, `space_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`spaces_special_account_name` TEXT NOT NULL, `spaces_special_space_id` TEXT NOT NULL, `spaces_special_etag` TEXT NOT NULL, `file_mime_type` TEXT NOT NULL, `special_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `size` INTEGER NOT NULL, `special_folder_name` TEXT NOT NULL, `special_web_dav_url` TEXT NOT NULL, PRIMARY KEY(`spaces_special_space_id`, `special_id`), FOREIGN KEY(`spaces_special_account_name`, `spaces_special_space_id`) REFERENCES `spaces`(`account_name`, `space_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "accountName", @@ -870,7 +866,7 @@ }, { "fieldPath": "eTag", - "columnName": "eTag", + "columnName": "spaces_special_etag", "affinity": "TEXT", "notNull": true }, @@ -906,13 +902,13 @@ }, { "fieldPath": "specialFolderName", - "columnName": "specialFolderName", + "columnName": "special_folder_name", "affinity": "TEXT", "notNull": true }, { "fieldPath": "webDavUrl", - "columnName": "webDavUrl", + "columnName": "special_web_dav_url", "affinity": "TEXT", "notNull": true } @@ -977,7 +973,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '91afe77785b47d2d5959b2a98ce4f6fc')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '69f96a3d4d578df91be813a866bfbed1')" ] } -} +} \ No newline at end of file diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index cbce0417bf5..7473af56739 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -20,6 +20,7 @@ package com.owncloud.android.data.spaces.datasources.implementation import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource import com.owncloud.android.data.spaces.db.SpaceQuotaEntity +import com.owncloud.android.data.spaces.db.SpaceRootEntity import com.owncloud.android.data.spaces.db.SpaceSpecialEntity import com.owncloud.android.data.spaces.db.SpacesDao import com.owncloud.android.data.spaces.db.SpacesEntity @@ -56,9 +57,9 @@ class OCLocalSpacesDataSource( quota = quota?.let { quotaModel -> SpaceQuotaEntity(remaining = quotaModel.remaining, state = quotaModel.state, total = quotaModel.total, used = quotaModel.used) }, - rootETag = root.eTag, - rootId = root.id, - rootWebDavUrl = root.webDavUrl, + root = root.let { rootModel -> + SpaceRootEntity(eTag = rootModel.eTag, id = rootModel.id, webDavUrl = rootModel.webDavUrl) + }, webUrl = webUrl, description = description, ) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt index f5e31d62153..6a46ae82a9c 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt @@ -20,7 +20,7 @@ package com.owncloud.android.data.spaces.datasources.implementation import com.owncloud.android.data.executeRemoteOperation import com.owncloud.android.data.spaces.datasources.RemoteSpacesDataSource -import com.owncloud.android.domain.spaces.model.File +import com.owncloud.android.domain.spaces.model.SpaceFile import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.SpaceGrantedTo import com.owncloud.android.domain.spaces.model.SpaceOwner @@ -83,7 +83,7 @@ class OCRemoteSpacesDataSource( special = special?.map { specialResponse -> SpaceSpecial( eTag = specialResponse.eTag, - file = File(mimeType = specialResponse.file.mimeType), + file = SpaceFile(mimeType = specialResponse.file.mimeType), id = specialResponse.id, lastModifiedDateTime = specialResponse.lastModifiedDateTime, name = specialResponse.name, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt index 38349f5c656..1b154bcac1b 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt @@ -2,6 +2,8 @@ * ownCloud Android client application * * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify @@ -16,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.data.spaces.db import androidx.room.ColumnInfo @@ -25,13 +28,16 @@ import com.owncloud.android.data.ProviderMeta import com.owncloud.android.data.spaces.db.SpaceSpecialEntity.Companion.SPACES_SPECIAL_ACCOUNT_NAME import com.owncloud.android.data.spaces.db.SpaceSpecialEntity.Companion.SPACES_SPECIAL_ID import com.owncloud.android.data.spaces.db.SpaceSpecialEntity.Companion.SPACES_SPECIAL_SPACE_ID +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ACCOUNT_NAME +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_LAST_MODIFIED_DATE_TIME @Entity( tableName = ProviderMeta.ProviderTableMeta.SPACES_SPECIAL_TABLE_NAME, primaryKeys = [SPACES_SPECIAL_SPACE_ID, SPACES_SPECIAL_ID], foreignKeys = [ForeignKey( entity = SpacesEntity::class, - parentColumns = arrayOf(SpacesEntity.SPACES_ACCOUNT_NAME, SpacesEntity.SPACES_ID), + parentColumns = arrayOf(SPACES_ACCOUNT_NAME, SPACES_ID), childColumns = arrayOf(SPACES_SPECIAL_ACCOUNT_NAME, SPACES_SPECIAL_SPACE_ID), onDelete = ForeignKey.CASCADE )] @@ -41,22 +47,28 @@ data class SpaceSpecialEntity( val accountName: String, @ColumnInfo(name = SPACES_SPECIAL_SPACE_ID) val spaceId: String, + @ColumnInfo(name = SPACES_SPECIAL_ETAG) val eTag: String, @ColumnInfo(name = SPACES_SPECIAL_FILE_MIME_TYPE) val fileMymeType: String, @ColumnInfo(name = SPACES_SPECIAL_ID) val id: String, - @ColumnInfo(name = SpacesEntity.SPACES_LAST_MODIFIED_DATE_TIME) + @ColumnInfo(name = SPACES_LAST_MODIFIED_DATE_TIME) val lastModifiedDateTime: String, val name: String, val size: Int, + @ColumnInfo(name = SPACES_SPECIAL_FOLDER_NAME) val specialFolderName: String, + @ColumnInfo(name = SPACES_SPECIAL_WEB_DAV_URL) val webDavUrl: String ) { companion object { const val SPACES_SPECIAL_ACCOUNT_NAME = "spaces_special_account_name" const val SPACES_SPECIAL_SPACE_ID = "spaces_special_space_id" + const val SPACES_SPECIAL_ETAG = "spaces_special_etag" const val SPACES_SPECIAL_FILE_MIME_TYPE = "file_mime_type" const val SPACES_SPECIAL_ID = "special_id" + const val SPACES_SPECIAL_FOLDER_NAME = "special_folder_name" + const val SPACES_SPECIAL_WEB_DAV_URL = "special_web_dav_url" } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt index 0952e1c0590..9ba18571a6d 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt @@ -2,6 +2,8 @@ * ownCloud Android client application * * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify @@ -16,25 +18,23 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.data.spaces.db import androidx.room.ColumnInfo import androidx.room.Embedded import androidx.room.Entity -import androidx.room.Index import androidx.room.Relation import com.owncloud.android.data.ProviderMeta import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ACCOUNT_NAME import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID - -data class SpacesWithSpecials( - @Embedded val space: SpacesEntity, - @Relation( - parentColumn = SPACES_ID, - entityColumn = SpaceSpecialEntity.SPACES_SPECIAL_SPACE_ID, - ) - val specials: List -) +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_QUOTA_REMAINING +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_QUOTA_STATE +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_QUOTA_TOTAL +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_QUOTA_USED +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ROOT_ETAG +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ROOT_ID +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ROOT_WEB_DAV_URL @Entity( tableName = ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME, @@ -56,9 +56,8 @@ data class SpacesEntity( val ownerId: String, @Embedded val quota: SpaceQuotaEntity?, - val rootETag: String, - val rootId: String, - val rootWebDavUrl: String, + @Embedded + val root: SpaceRootEntity?, @ColumnInfo(name = SPACES_WEB_URL) val webUrl: String, val description: String?, @@ -74,12 +73,41 @@ data class SpacesEntity( const val SPACES_LAST_MODIFIED_DATE_TIME = "last_modified_date_time" const val SPACES_WEB_URL = "web_url" const val SPACES_OWNER_ID = "owner_id" + const val SPACES_QUOTA_REMAINING = "quota_remaining" + const val SPACES_QUOTA_STATE = "quota_state" + const val SPACES_QUOTA_TOTAL = "quota_total" + const val SPACES_QUOTA_USED = "quota_used" + const val SPACES_ROOT_ETAG = "root_etag" + const val SPACES_ROOT_ID = "root_id" + const val SPACES_ROOT_WEB_DAV_URL = "root_web_dav_url" } } data class SpaceQuotaEntity( + @ColumnInfo(name = SPACES_QUOTA_REMAINING) val remaining: Long, + @ColumnInfo(name = SPACES_QUOTA_STATE) val state: String, - val total: Int, - val used: Int + @ColumnInfo(name = SPACES_QUOTA_TOTAL) + val total: Long, + @ColumnInfo(name = SPACES_QUOTA_USED) + val used: Long +) + +data class SpaceRootEntity( + @ColumnInfo(name = SPACES_ROOT_ETAG) + val eTag: String, + @ColumnInfo(name = SPACES_ROOT_ID) + val id: String, + @ColumnInfo(name = SPACES_ROOT_WEB_DAV_URL) + val webDavUrl: String +) + +data class SpacesWithSpecials( + @Embedded val space: SpacesEntity, + @Relation( + parentColumn = SPACES_ID, + entityColumn = SpaceSpecialEntity.SPACES_SPECIAL_SPACE_ID, + ) + val specials: List ) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index 6479368c569..89ad3e9f1f6 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -61,7 +61,7 @@ data class SpaceRoot( data class SpaceSpecial( val eTag: String, - val file: File, + val file: SpaceFile, val id: String, val lastModifiedDateTime: String, val name: String, @@ -74,19 +74,19 @@ data class SpaceUser( val id: String ) -data class File( +data class SpaceFile( val mimeType: String ) -data class SpaceGrantedTo( - val user: SpaceUser? -) - data class SpacePermission( val grantedTo: List, val roles: List ) +data class SpaceGrantedTo( + val user: SpaceUser? +) + data class SpaceSpecialFolder( val name: String ) From 3c66425812c2477f592b8ace9343fc04b87d1d90 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 28 Dec 2022 15:11:21 +0100 Subject: [PATCH 015/152] Added usecase for retrieving project spaces as Flow from DB --- .../dependecyinjection/UseCaseModule.kt | 2 + .../dependecyinjection/ViewModelModule.kt | 2 + .../presentation/spaces/SpacesListFragment.kt | 17 +++++- .../spaces/SpacesListViewModel.kt | 52 ++++++++++++++++++ .../datasources/LocalSpacesDataSource.kt | 5 ++ .../implementation/OCLocalSpacesDataSource.kt | 53 ++++++++++++++++++- .../android/data/spaces/db/SpacesDao.kt | 24 +++++---- .../spaces/repository/OCSpacesRepository.kt | 6 +++ .../android/domain/spaces/SpacesRepository.kt | 7 +++ ...tProjectSpacesForAccountAsStreamUseCase.kt | 37 +++++++++++++ 10 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesForAccountAsStreamUseCase.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 5a074c5c706..896abf3531d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -77,6 +77,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.EditPublicShareAsyncU import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase +import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase import com.owncloud.android.domain.transfers.usecases.ClearSuccessfulTransfersUseCase import com.owncloud.android.domain.transfers.usecases.GetAllTransfersAsLiveDataUseCase @@ -175,6 +176,7 @@ val useCaseModule = module { // Spaces factory { RefreshSpacesFromServerAsyncUseCase(get()) } + factory { GetProjectSpacesForAccountAsStreamUseCase(get()) } // Transfers factory { CancelDownloadForFileUseCase(get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index fadeb2d4846..2ced99e2cdd 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -51,6 +51,7 @@ import com.owncloud.android.presentation.settings.logging.SettingsLogsViewModel import com.owncloud.android.presentation.settings.more.SettingsMoreViewModel import com.owncloud.android.presentation.settings.security.SettingsSecurityViewModel import com.owncloud.android.presentation.sharing.ShareViewModel +import com.owncloud.android.presentation.spaces.SpacesListViewModel import com.owncloud.android.presentation.transfers.TransfersViewModel import com.owncloud.android.ui.ReceiveExternalFilesViewModel import com.owncloud.android.ui.preview.PreviewImageViewModel @@ -112,4 +113,5 @@ val viewModelModule = module { viewModel { (ocFile: OCFile) -> ConflictsResolveViewModel(get(), get(), get(), get(), get(), ocFile) } viewModel { ReceiveExternalFilesViewModel(get(), get()) } viewModel { AccountsManagementViewModel(get()) } + viewModel { SpacesListViewModel(get(), get(), get(), get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index 8f8f7ed28f4..876eda8305a 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -28,14 +28,18 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.owncloud.android.databinding.SpacesListFragmentBinding import com.owncloud.android.domain.files.model.FileListOption +import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.extensions.toDrawableRes import com.owncloud.android.extensions.toSubtitleStringRes import com.owncloud.android.extensions.toTitleStringRes +import org.koin.androidx.viewmodel.ext.android.viewModel class SpacesListFragment : Fragment() { private var _binding: SpacesListFragmentBinding? = null private val binding get() = _binding!! + private val spacesListViewModel: SpacesListViewModel by viewModel() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -46,7 +50,18 @@ class SpacesListFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - showOrHideEmptyView() + initViews() + subscribeToViewModels() + } + + private fun initViews() { + + } + + private fun subscribeToViewModels() { + collectLatestLifecycleFlow(spacesListViewModel.spacesList) { + showOrHideEmptyView() + } } // TODO: Use this method only when necessary, for the moment the empty view is shown always diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt new file mode 100644 index 00000000000..bf2b11ba4c0 --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt @@ -0,0 +1,52 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.presentation.spaces + +import android.accounts.Account +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesForAccountAsStreamUseCase +import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase +import com.owncloud.android.providers.CoroutinesDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class SpacesListViewModel( + private val refreshSpacesFromServerAsyncUseCase: RefreshSpacesFromServerAsyncUseCase, + getProjectSpacesForAccountAsStreamUseCase: GetProjectSpacesForAccountAsStreamUseCase, + coroutinesDispatcherProvider: CoroutinesDispatcherProvider, + private val account: Account, +) : ViewModel() { + val spacesList: MutableStateFlow> = MutableStateFlow(emptyList()) + + init { + viewModelScope.launch(coroutinesDispatcherProvider.io) { + refreshSpacesFromServerAsyncUseCase.execute(RefreshSpacesFromServerAsyncUseCase.Params(account.name)) + spacesList.update { getProjectSpacesForAccountAsStreamUseCase.execute( + GetProjectSpacesForAccountAsStreamUseCase.Params( + accountName = account.name + )).first() } + } + } +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt index ec34eb73b4c..e2e2199da92 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt @@ -2,6 +2,8 @@ * ownCloud Android client application * * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify @@ -16,10 +18,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.data.spaces.datasources import com.owncloud.android.domain.spaces.model.OCSpace +import kotlinx.coroutines.flow.Flow interface LocalSpacesDataSource { fun saveSpacesForAccount(listOfSpaces: List) + fun getProjectSpacesForAccountAsFlow(accountName: String): Flow> } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 7473af56739..8704626a0b6 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -2,6 +2,8 @@ * ownCloud Android client application * * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify @@ -16,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.data.spaces.datasources.implementation import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource @@ -25,7 +28,13 @@ import com.owncloud.android.data.spaces.db.SpaceSpecialEntity import com.owncloud.android.data.spaces.db.SpacesDao import com.owncloud.android.data.spaces.db.SpacesEntity import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.domain.spaces.model.SpaceOwner +import com.owncloud.android.domain.spaces.model.SpaceQuota +import com.owncloud.android.domain.spaces.model.SpaceRoot import com.owncloud.android.domain.spaces.model.SpaceSpecial +import com.owncloud.android.domain.spaces.model.SpaceUser +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map class OCLocalSpacesDataSource( private val spacesDao: SpacesDao, @@ -42,9 +51,51 @@ class OCLocalSpacesDataSource( } } - spacesDao.upsertOrDeleteSpaces(spaceEntities, spaceSpecialEntities) + spacesDao.insertOrDeleteSpaces(spaceEntities, spaceSpecialEntities) } + override fun getProjectSpacesForAccountAsFlow(accountName: String): Flow> { + return spacesDao.getProjectSpacesForAccountAsFlow(accountName).map { spacesEntitiesList -> + spacesEntitiesList.map { spacesEntity -> + spacesEntity.toModel() + } + } + } + + private fun SpacesEntity.toModel() = + OCSpace( + accountName = accountName, + driveAlias = driveAlias, + driveType = driveType, + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + owner = SpaceOwner( + user = SpaceUser( + id = ownerId + ) + ), + quota = quota?.let { + SpaceQuota( + remaining = it.remaining, + state = it.state, + total = it.total, + used = it.used + ) + }, + root = root!!.let { + SpaceRoot( + eTag = it.eTag, + id = it.id, + permissions = null, + webDavUrl = it.webDavUrl + ) + }, + webUrl = webUrl, + description = description, + special = null + ) + private fun OCSpace.toEntity() = SpacesEntity( accountName = accountName, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index 08f9beab3b4..04c239721b9 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -2,6 +2,8 @@ * ownCloud Android client application * * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify @@ -16,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.data.spaces.db import androidx.room.Dao @@ -29,11 +32,12 @@ import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.DRIVE_TYPE_PRO import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ACCOUNT_NAME import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_DRIVE_TYPE import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID +import kotlinx.coroutines.flow.Flow @Dao interface SpacesDao { @Transaction - fun upsertOrDeleteSpaces( + fun insertOrDeleteSpaces( listOfSpacesEntities: List, listOfSpecialEntities: List, ) { @@ -49,7 +53,7 @@ interface SpacesDao { deleteSpaceForAccountById(accountName = spaceToDelete.accountName, spaceId = spaceToDelete.id) } - // Upsert new spaces + // Insert new spaces insertOrReplaceSpaces(listOfSpacesEntities) insertOrReplaceSpecials(listOfSpecialEntities) } @@ -62,20 +66,20 @@ interface SpacesDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrReplaceSpecials(listOfSpecialEntities: List): List - @Query(SELECT_ALL_SPACES) + @Query(SELECT_ALL_SPACES_FOR_ACCOUNT) fun getAllSpacesForAccount( accountName: String, ): List - @Query(SELECT_PERSONAL_SPACE) + @Query(SELECT_PERSONAL_SPACE_FOR_ACCOUNT) fun getPersonalSpacesForAccount( accountName: String, ): List - @Query(SELECT_PROJECT_SPACES) - fun getProjectSpacesForAccount( + @Query(SELECT_PROJECT_SPACES_FOR_ACCOUNT) + fun getProjectSpacesForAccountAsFlow( accountName: String, - ): List + ): Flow> @Query(DELETE_ALL_SPACES_FOR_ACCOUNT) fun deleteSpacesForAccount(accountName: String) @@ -84,19 +88,19 @@ interface SpacesDao { fun deleteSpaceForAccountById(accountName: String, spaceId: String) companion object { - private const val SELECT_ALL_SPACES = """ + private const val SELECT_ALL_SPACES_FOR_ACCOUNT = """ SELECT * FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} WHERE $SPACES_ACCOUNT_NAME = :accountName """ - private const val SELECT_PERSONAL_SPACE = """ + private const val SELECT_PERSONAL_SPACE_FOR_ACCOUNT = """ SELECT * FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} WHERE $SPACES_ACCOUNT_NAME = :accountName AND $SPACES_DRIVE_TYPE LIKE '$DRIVE_TYPE_PERSONAL' """ - private const val SELECT_PROJECT_SPACES = """ + private const val SELECT_PROJECT_SPACES_FOR_ACCOUNT = """ SELECT * FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} WHERE $SPACES_ACCOUNT_NAME = :accountName AND $SPACES_DRIVE_TYPE LIKE '$DRIVE_TYPE_PROJECT' diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt index a444e00ead7..2b37c83170c 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -2,6 +2,8 @@ * ownCloud Android client application * * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify @@ -16,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.data.spaces.repository import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource @@ -31,4 +34,7 @@ class OCSpacesRepository( localSpacesDataSource.saveSpacesForAccount(listOfSpaces) } } + + override fun getProjectSpacesForAccountAsFlow(accountName: String) = + localSpacesDataSource.getProjectSpacesForAccountAsFlow(accountName) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt index e10e1c1b43d..aceac27c056 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -2,6 +2,8 @@ * ownCloud Android client application * * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify @@ -16,8 +18,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.domain.spaces +import com.owncloud.android.domain.spaces.model.OCSpace +import kotlinx.coroutines.flow.Flow + interface SpacesRepository { fun refreshSpacesForAccount(accountName: String) + fun getProjectSpacesForAccountAsFlow(accountName: String): Flow> } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesForAccountAsStreamUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesForAccountAsStreamUseCase.kt new file mode 100644 index 00000000000..fb85bf876ed --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesForAccountAsStreamUseCase.kt @@ -0,0 +1,37 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.usecases + +import com.owncloud.android.domain.BaseUseCase +import com.owncloud.android.domain.spaces.SpacesRepository +import com.owncloud.android.domain.spaces.model.OCSpace +import kotlinx.coroutines.flow.Flow + +class GetProjectSpacesForAccountAsStreamUseCase( + private val spacesRepository: SpacesRepository +) : BaseUseCase>, GetProjectSpacesForAccountAsStreamUseCase.Params>() { + + override fun run(params: Params) = spacesRepository.getProjectSpacesForAccountAsFlow(params.accountName) + + data class Params( + val accountName: String + ) +} From d642253915e516d2f2664f40c50836d1e85b1aad Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 29 Dec 2022 14:36:49 +0100 Subject: [PATCH 016/152] First approach for spaces list --- .../presentation/spaces/SpacesListAdapter.kt | 71 ++++++++++++++++ .../presentation/spaces/SpacesListFragment.kt | 16 +++- .../main/res/layout/spaces_list_fragment.xml | 11 ++- .../src/main/res/layout/spaces_list_item.xml | 81 +++++++++++++++++++ owncloudApp/src/main/res/values/colors.xml | 6 +- .../android/domain/spaces/model/OCSpace.kt | 10 +++ 6 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt create mode 100644 owncloudApp/src/main/res/layout/spaces_list_item.xml diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt new file mode 100644 index 00000000000..acb545c0479 --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -0,0 +1,71 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.presentation.spaces + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.owncloud.android.databinding.SpacesListItemBinding +import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.utils.PreferenceUtils + +class SpacesListAdapter() : RecyclerView.Adapter() { + + private val spacesList = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val binding = SpacesListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + binding.root.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(parent.context) + return SpacesViewHolder(binding) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val spacesViewHolder = holder as SpacesViewHolder + spacesViewHolder.binding.apply { + val space = spacesList[position] + + spacesListItemName.text = space.name + spacesListItemSubtitle.text = space.description + + space.getSpaceImageUrl()?.let { + Glide.with(holder.itemView) + .load(it) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(spacesListItemImage) + } + + } + + } + + fun setData(spaces: List) { + spacesList.clear() + spacesList.addAll(spaces) + } + + override fun getItemCount(): Int = spacesList.size + + fun getItem(position: Int) = spacesList[position] + + class SpacesViewHolder(val binding: SpacesListItemBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index 876eda8305a..85999a39a33 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -26,6 +26,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.GridLayoutManager import com.owncloud.android.databinding.SpacesListFragmentBinding import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.extensions.collectLatestLifecycleFlow @@ -40,6 +41,8 @@ class SpacesListFragment : Fragment() { private val spacesListViewModel: SpacesListViewModel by viewModel() + private lateinit var spacesListAdapter: SpacesListAdapter + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -55,21 +58,28 @@ class SpacesListFragment : Fragment() { } private fun initViews() { + val spacesListLayoutManager = GridLayoutManager(requireContext(), 2) + binding.recyclerSpacesList.layoutManager = spacesListLayoutManager + spacesListAdapter = SpacesListAdapter() + binding.recyclerSpacesList.adapter = spacesListAdapter + + binding.swipeRefreshSpacesList.setOnRefreshListener { + } } private fun subscribeToViewModels() { collectLatestLifecycleFlow(spacesListViewModel.spacesList) { showOrHideEmptyView() + spacesListAdapter.setData(spacesListViewModel.spacesList.value) } } - // TODO: Use this method only when necessary, for the moment the empty view is shown always private fun showOrHideEmptyView() { - binding.recyclerSpacesList.isVisible = false + binding.recyclerSpacesList.isVisible = spacesListViewModel.spacesList.value.isNotEmpty() with(binding.emptyDataParent) { - root.isVisible = true + root.isVisible = spacesListViewModel.spacesList.value.isEmpty() listEmptyDatasetIcon.setImageResource(FileListOption.SPACES_LIST.toDrawableRes()) listEmptyDatasetTitle.setText(FileListOption.SPACES_LIST.toTitleStringRes()) listEmptyDatasetSubTitle.setText(FileListOption.SPACES_LIST.toSubtitleStringRes()) diff --git a/owncloudApp/src/main/res/layout/spaces_list_fragment.xml b/owncloudApp/src/main/res/layout/spaces_list_fragment.xml index 1ee3a007d50..e7b09bc4aa5 100644 --- a/owncloudApp/src/main/res/layout/spaces_list_fragment.xml +++ b/owncloudApp/src/main/res/layout/spaces_list_fragment.xml @@ -1,4 +1,5 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/owncloudApp/src/main/res/values/colors.xml b/owncloudApp/src/main/res/values/colors.xml index 1473e10e5c9..2698497be5b 100644 --- a/owncloudApp/src/main/res/values/colors.xml +++ b/owncloudApp/src/main/res/values/colors.xml @@ -67,4 +67,8 @@ #6E758C #6E758C - \ No newline at end of file + + + #edf3fa + #4c5f79 + diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index 89ad3e9f1f6..4a0e164fd01 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -2,6 +2,8 @@ * ownCloud Android client application * * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify @@ -16,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.domain.spaces.model data class OCSpace( @@ -39,6 +42,13 @@ data class OCSpace( private const val DRIVE_TYPE_PERSONAL = "personal" private const val DRIVE_TYPE_PROJECT = "project" } + + fun getSpaceImageUrl(): String? { + val imageSpecial = special?.filter { + it.specialFolder.name == "image" + } + return imageSpecial?.first()?.webDavUrl + } } data class SpaceOwner( From ebdf554a5c9180bbf7e962619437a61671991b76 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Fri, 30 Dec 2022 10:41:38 +0100 Subject: [PATCH 017/152] Everything ready to load the images in spaces list --- .../dependecyinjection/UseCaseModule.kt | 4 +- .../presentation/spaces/SpacesListAdapter.kt | 13 +---- .../spaces/SpacesListViewModel.kt | 8 +-- .../src/main/res/layout/spaces_list_item.xml | 5 +- .../datasources/LocalSpacesDataSource.kt | 2 +- .../implementation/OCLocalSpacesDataSource.kt | 57 +++++++++++++------ .../data/spaces/db/SpaceSpecialEntity.kt | 2 +- .../android/data/spaces/db/SpacesDao.kt | 4 +- .../spaces/repository/OCSpacesRepository.kt | 4 +- .../android/domain/spaces/SpacesRepository.kt | 2 +- .../android/domain/spaces/model/OCSpace.kt | 4 +- ...sWithSpecialsForAccountAsStreamUseCase.kt} | 6 +- 12 files changed, 64 insertions(+), 47 deletions(-) rename owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/{GetProjectSpacesForAccountAsStreamUseCase.kt => GetProjectSpacesWithSpecialsForAccountAsStreamUseCase.kt} (83%) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 896abf3531d..764aa1982ce 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -77,7 +77,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.EditPublicShareAsyncU import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase -import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesForAccountAsStreamUseCase +import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase import com.owncloud.android.domain.transfers.usecases.ClearSuccessfulTransfersUseCase import com.owncloud.android.domain.transfers.usecases.GetAllTransfersAsLiveDataUseCase @@ -176,7 +176,7 @@ val useCaseModule = module { // Spaces factory { RefreshSpacesFromServerAsyncUseCase(get()) } - factory { GetProjectSpacesForAccountAsStreamUseCase(get()) } + factory { GetProjectSpacesWithSpecialsForAccountAsStreamUseCase(get()) } // Transfers factory { CancelDownloadForFileUseCase(get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt index acb545c0479..5924b59bbea 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -23,13 +23,11 @@ package com.owncloud.android.presentation.spaces import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy import com.owncloud.android.databinding.SpacesListItemBinding import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.utils.PreferenceUtils -class SpacesListAdapter() : RecyclerView.Adapter() { +class SpacesListAdapter : RecyclerView.Adapter() { private val spacesList = mutableListOf() @@ -47,15 +45,10 @@ class SpacesListAdapter() : RecyclerView.Adapter() { spacesListItemName.text = space.name spacesListItemSubtitle.text = space.description - space.getSpaceImageUrl()?.let { - Glide.with(holder.itemView) - .load(it) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .into(spacesListItemImage) - } + space.getSpaceImageWebDavUrl()?.let { + } } - } fun setData(spaces: List) { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt index bf2b11ba4c0..a10e980492e 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt @@ -24,7 +24,7 @@ import android.accounts.Account import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.owncloud.android.domain.spaces.model.OCSpace -import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesForAccountAsStreamUseCase +import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase import com.owncloud.android.providers.CoroutinesDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow @@ -34,7 +34,7 @@ import kotlinx.coroutines.launch class SpacesListViewModel( private val refreshSpacesFromServerAsyncUseCase: RefreshSpacesFromServerAsyncUseCase, - getProjectSpacesForAccountAsStreamUseCase: GetProjectSpacesForAccountAsStreamUseCase, + getProjectSpacesWithSpecialsForAccountAsStreamUseCase: GetProjectSpacesWithSpecialsForAccountAsStreamUseCase, coroutinesDispatcherProvider: CoroutinesDispatcherProvider, private val account: Account, ) : ViewModel() { @@ -43,8 +43,8 @@ class SpacesListViewModel( init { viewModelScope.launch(coroutinesDispatcherProvider.io) { refreshSpacesFromServerAsyncUseCase.execute(RefreshSpacesFromServerAsyncUseCase.Params(account.name)) - spacesList.update { getProjectSpacesForAccountAsStreamUseCase.execute( - GetProjectSpacesForAccountAsStreamUseCase.Params( + spacesList.update { getProjectSpacesWithSpecialsForAccountAsStreamUseCase.execute( + GetProjectSpacesWithSpecialsForAccountAsStreamUseCase.Params( accountName = account.name )).first() } } diff --git a/owncloudApp/src/main/res/layout/spaces_list_item.xml b/owncloudApp/src/main/res/layout/spaces_list_item.xml index 8665bd5393b..855dbd4db9a 100644 --- a/owncloudApp/src/main/res/layout/spaces_list_item.xml +++ b/owncloudApp/src/main/res/layout/spaces_list_item.xml @@ -45,6 +45,9 @@ android:layout_width="match_parent" android:layout_height="150dp" android:src="@drawable/ic_spaces" + android:scaleType="center" + android:scaleX="2" + android:scaleY="2" app:tint="@color/spaces_card_default_image_color" /> ) - fun getProjectSpacesForAccountAsFlow(accountName: String): Flow> + fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 8704626a0b6..ff846f51e90 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -27,11 +27,14 @@ import com.owncloud.android.data.spaces.db.SpaceRootEntity import com.owncloud.android.data.spaces.db.SpaceSpecialEntity import com.owncloud.android.data.spaces.db.SpacesDao import com.owncloud.android.data.spaces.db.SpacesEntity +import com.owncloud.android.data.spaces.db.SpacesWithSpecials import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.domain.spaces.model.SpaceFile import com.owncloud.android.domain.spaces.model.SpaceOwner import com.owncloud.android.domain.spaces.model.SpaceQuota import com.owncloud.android.domain.spaces.model.SpaceRoot import com.owncloud.android.domain.spaces.model.SpaceSpecial +import com.owncloud.android.domain.spaces.model.SpaceSpecialFolder import com.owncloud.android.domain.spaces.model.SpaceUser import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -54,28 +57,28 @@ class OCLocalSpacesDataSource( spacesDao.insertOrDeleteSpaces(spaceEntities, spaceSpecialEntities) } - override fun getProjectSpacesForAccountAsFlow(accountName: String): Flow> { - return spacesDao.getProjectSpacesForAccountAsFlow(accountName).map { spacesEntitiesList -> - spacesEntitiesList.map { spacesEntity -> - spacesEntity.toModel() + override fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> { + return spacesDao.getProjectSpacesWithSpecialsForAccountAsFlow(accountName).map { spacesWithSpecialsEntitiesList -> + spacesWithSpecialsEntitiesList.map { spacesWithSpecialsEntity -> + spacesWithSpecialsEntity.toModel() } } } - private fun SpacesEntity.toModel() = + private fun SpacesWithSpecials.toModel() = OCSpace( - accountName = accountName, - driveAlias = driveAlias, - driveType = driveType, - id = id, - lastModifiedDateTime = lastModifiedDateTime, - name = name, + accountName = space.accountName, + driveAlias = space.driveAlias, + driveType = space.driveType, + id = space.id, + lastModifiedDateTime = space.lastModifiedDateTime, + name = space.name, owner = SpaceOwner( user = SpaceUser( - id = ownerId + id = space.ownerId ) ), - quota = quota?.let { + quota = space.quota?.let { SpaceQuota( remaining = it.remaining, state = it.state, @@ -83,7 +86,7 @@ class OCLocalSpacesDataSource( used = it.used ) }, - root = root!!.let { + root = space.root!!.let { SpaceRoot( eTag = it.eTag, id = it.id, @@ -91,9 +94,27 @@ class OCLocalSpacesDataSource( webDavUrl = it.webDavUrl ) }, - webUrl = webUrl, - description = description, - special = null + webUrl = space.webUrl, + description = space.description, + special = specials.map { + it.toModel() + } + ) + + private fun SpaceSpecialEntity.toModel() = + SpaceSpecial( + eTag = eTag, + file = SpaceFile( + mimeType = fileMimeType + ), + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + size = size, + specialFolder = SpaceSpecialFolder( + name = specialFolderName + ), + webDavUrl = webDavUrl ) private fun OCSpace.toEntity() = @@ -120,7 +141,7 @@ class OCLocalSpacesDataSource( accountName = accountName, spaceId = spaceId, eTag = eTag, - fileMymeType = file.mimeType, + fileMimeType = file.mimeType, id = id, lastModifiedDateTime = lastModifiedDateTime, name = name, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt index 1b154bcac1b..15c38bcac82 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpaceSpecialEntity.kt @@ -50,7 +50,7 @@ data class SpaceSpecialEntity( @ColumnInfo(name = SPACES_SPECIAL_ETAG) val eTag: String, @ColumnInfo(name = SPACES_SPECIAL_FILE_MIME_TYPE) - val fileMymeType: String, + val fileMimeType: String, @ColumnInfo(name = SPACES_SPECIAL_ID) val id: String, @ColumnInfo(name = SPACES_LAST_MODIFIED_DATE_TIME) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index 04c239721b9..cc6ab5f9892 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -77,9 +77,9 @@ interface SpacesDao { ): List @Query(SELECT_PROJECT_SPACES_FOR_ACCOUNT) - fun getProjectSpacesForAccountAsFlow( + fun getProjectSpacesWithSpecialsForAccountAsFlow( accountName: String, - ): Flow> + ): Flow> @Query(DELETE_ALL_SPACES_FOR_ACCOUNT) fun deleteSpacesForAccount(accountName: String) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt index 2b37c83170c..5afd4fe8e52 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -35,6 +35,6 @@ class OCSpacesRepository( } } - override fun getProjectSpacesForAccountAsFlow(accountName: String) = - localSpacesDataSource.getProjectSpacesForAccountAsFlow(accountName) + override fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String) = + localSpacesDataSource.getProjectSpacesWithSpecialsForAccountAsFlow(accountName) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt index aceac27c056..9f9b480cd40 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -26,5 +26,5 @@ import kotlinx.coroutines.flow.Flow interface SpacesRepository { fun refreshSpacesForAccount(accountName: String) - fun getProjectSpacesForAccountAsFlow(accountName: String): Flow> + fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index 4a0e164fd01..2f14c167166 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -43,11 +43,11 @@ data class OCSpace( private const val DRIVE_TYPE_PROJECT = "project" } - fun getSpaceImageUrl(): String? { + fun getSpaceImageWebDavUrl(): String? { val imageSpecial = special?.filter { it.specialFolder.name == "image" } - return imageSpecial?.first()?.webDavUrl + return if (!imageSpecial.isNullOrEmpty()) imageSpecial.first().webDavUrl else null } } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesForAccountAsStreamUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesWithSpecialsForAccountAsStreamUseCase.kt similarity index 83% rename from owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesForAccountAsStreamUseCase.kt rename to owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesWithSpecialsForAccountAsStreamUseCase.kt index fb85bf876ed..92f3db4f63d 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesForAccountAsStreamUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetProjectSpacesWithSpecialsForAccountAsStreamUseCase.kt @@ -25,11 +25,11 @@ import com.owncloud.android.domain.spaces.SpacesRepository import com.owncloud.android.domain.spaces.model.OCSpace import kotlinx.coroutines.flow.Flow -class GetProjectSpacesForAccountAsStreamUseCase( +class GetProjectSpacesWithSpecialsForAccountAsStreamUseCase( private val spacesRepository: SpacesRepository -) : BaseUseCase>, GetProjectSpacesForAccountAsStreamUseCase.Params>() { +) : BaseUseCase>, GetProjectSpacesWithSpecialsForAccountAsStreamUseCase.Params>() { - override fun run(params: Params) = spacesRepository.getProjectSpacesForAccountAsFlow(params.accountName) + override fun run(params: Params) = spacesRepository.getProjectSpacesWithSpecialsForAccountAsFlow(params.accountName) data class Params( val accountName: String From 93ec0c5870061e7f88ddccf9c352f5fafbae2736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 10 Jan 2023 14:12:37 +0100 Subject: [PATCH 018/152] Print the space image --- .../datamodel/ThumbnailsCacheManager.java | 61 +++++++++++++++++++ .../presentation/spaces/SpacesListAdapter.kt | 33 +++++++++- .../src/main/res/layout/spaces_list_item.xml | 5 +- .../android/domain/spaces/model/OCSpace.kt | 4 +- 4 files changed, 96 insertions(+), 7 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index a30646e4352..848cda72494 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -39,6 +39,7 @@ import com.owncloud.android.R; import com.owncloud.android.domain.files.model.OCFile; import com.owncloud.android.domain.files.usecases.DisableThumbnailsForFileUseCase; +import com.owncloud.android.domain.spaces.model.SpaceSpecial; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.SingleSessionManager; @@ -75,6 +76,7 @@ public class ThumbnailsCacheManager { private static OwnCloudClient mClient = null; private static final String PREVIEW_URI = "%s/remote.php/dav/files/%s%s?x=%d&y=%d&c=%s&preview=1"; + private static final String SPACE_SPECIAL_URI = "%s?scalingup=0&a=1&x=%d&y=%d&c=%s&preview=1"; public static Bitmap mDefaultImg = BitmapFactory.decodeResource( @@ -186,6 +188,8 @@ protected Bitmap doInBackground(Object... params) { thumbnail = doOCFileInBackground(); } else if (mFile instanceof File) { thumbnail = doFileInBackground(); + } else if (mFile instanceof SpaceSpecial) { + thumbnail = doSpaceImageInBackground(); //} else { do nothing } @@ -210,6 +214,8 @@ protected void onPostExecute(Bitmap bitmap) { tagId = String.valueOf(((OCFile) mFile).getId()); } else if (mFile instanceof File) { tagId = String.valueOf(mFile.hashCode()); + } else if (mFile instanceof SpaceSpecial) { + tagId = ((SpaceSpecial) mFile).getId(); } if (String.valueOf(imageView.getTag()).equals(tagId)) { imageView.setImageBitmap(bitmap); @@ -349,6 +355,61 @@ private Bitmap doFileInBackground() { return thumbnail; } + private String getSpaceSpecialUri(SpaceSpecial spaceSpecial) { + return String.format(Locale.ROOT, + SPACE_SPECIAL_URI, + spaceSpecial.getWebDavUrl(), + getThumbnailDimension(), + getThumbnailDimension(), + spaceSpecial.getETag()); + } + + private Bitmap doSpaceImageInBackground() { + SpaceSpecial spaceSpecial = (SpaceSpecial) mFile; + + final String imageKey = spaceSpecial.getId(); + + // Check disk cache in background thread + Bitmap thumbnail = getBitmapFromDiskCache(imageKey); + + // Not found in disk cache + if (thumbnail == null) { // TODO: Check if the current thumbnail is outdated + int px = getThumbnailDimension(); + + // Download thumbnail from server + if (mClient != null) { + GetMethod get; + try { + String uri = getSpaceSpecialUri(spaceSpecial); + Timber.d("URI: %s", uri); + get = new GetMethod(new URL(uri)); + int status = mClient.executeHttpMethod(get); + if (status == HttpConstants.HTTP_OK) { + InputStream inputStream = get.getResponseBodyAsStream(); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px); + + // Handle PNG + if (spaceSpecial.getFile().getMimeType().equalsIgnoreCase("image/png")) { + thumbnail = handlePNG(thumbnail, px); + } + + // Add thumbnail to cache + if (thumbnail != null) { + addBitmapToCache(imageKey, thumbnail); + } + } else { + mClient.exhaustResponse(get.getResponseBodyAsStream()); + } + } catch (Exception e) { + Timber.e(e); + } + } + } + + return thumbnail; + + } } public static boolean cancelPotentialThumbnailWork(Object file, ImageView imageView) { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt index 5924b59bbea..b3caba81221 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -22,9 +22,14 @@ package com.owncloud.android.presentation.spaces import android.view.LayoutInflater import android.view.ViewGroup +import android.widget.ImageView +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView +import com.owncloud.android.R import com.owncloud.android.databinding.SpacesListItemBinding +import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.presentation.authentication.AccountUtils import com.owncloud.android.utils.PreferenceUtils class SpacesListAdapter : RecyclerView.Adapter() { @@ -45,8 +50,34 @@ class SpacesListAdapter : RecyclerView.Adapter() { spacesListItemName.text = space.name spacesListItemSubtitle.text = space.description - space.getSpaceImageWebDavUrl()?.let { + val spaceSpecialImage = space.getSpaceSpecialImage() + spacesListItemImage.tag = spaceSpecialImage?.id + if (spaceSpecialImage != null) { + val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(spaceSpecialImage.id) + if (thumbnail != null) { + spacesListItemImage.run { + setImageBitmap(thumbnail) + scaleType = ImageView.ScaleType.CENTER_CROP + } + } + if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(spaceSpecialImage, spacesListItemImage)) { + val account = AccountUtils.getOwnCloudAccountByName(spacesViewHolder.itemView.context, space.accountName) + val task = ThumbnailsCacheManager.ThumbnailGenerationTask(spacesListItemImage, account) + val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(spacesViewHolder.itemView.resources, thumbnail, task) + + // If drawable is not visible, do not update it. + if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) { + spacesListItemImage.run { + spacesListItemImage.setImageDrawable(asyncDrawable) + scaleType = ImageView.ScaleType.CENTER_CROP + } + } + task.execute(spaceSpecialImage) + } + if (spaceSpecialImage.file.mimeType == "image/png") { + spacesListItemImage.setBackgroundColor(ContextCompat.getColor(spacesViewHolder.itemView.context, R.color.background_color)) + } } } } diff --git a/owncloudApp/src/main/res/layout/spaces_list_item.xml b/owncloudApp/src/main/res/layout/spaces_list_item.xml index 855dbd4db9a..1847d8eda5f 100644 --- a/owncloudApp/src/main/res/layout/spaces_list_item.xml +++ b/owncloudApp/src/main/res/layout/spaces_list_item.xml @@ -45,10 +45,7 @@ android:layout_width="match_parent" android:layout_height="150dp" android:src="@drawable/ic_spaces" - android:scaleType="center" - android:scaleX="2" - android:scaleY="2" - app:tint="@color/spaces_card_default_image_color" /> + android:scaleType="center" /> Date: Tue, 10 Jan 2023 14:13:08 +0100 Subject: [PATCH 019/152] Reformat xml --- .../src/main/res/layout/spaces_list_item.xml | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/owncloudApp/src/main/res/layout/spaces_list_item.xml b/owncloudApp/src/main/res/layout/spaces_list_item.xml index 1847d8eda5f..b54538f9fa2 100644 --- a/owncloudApp/src/main/res/layout/spaces_list_item.xml +++ b/owncloudApp/src/main/res/layout/spaces_list_item.xml @@ -1,5 +1,4 @@ - - Found %1$s of data on your external storage. It will be moved to the safe storage on your device. Remaining files will be cleaned from your external storage after the migration to avoid duplicates and vulnerability. From 54d77ab848bb67f9f24c6c22d6f9105dfca551dc Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 10 Jan 2023 17:28:21 +0100 Subject: [PATCH 023/152] Added DiffUtil to spaces list adapter --- .../presentation/spaces/SpacesListAdapter.kt | 8 +-- .../presentation/spaces/SpacesListDiffUtil.kt | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListDiffUtil.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt index beab16ed048..c9ab83bbeba 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -24,6 +24,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.widget.ImageView import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.owncloud.android.R import com.owncloud.android.databinding.SpacesListItemBinding @@ -83,12 +84,13 @@ class SpacesListAdapter : RecyclerView.Adapter() { } fun setData(spaces: List) { - spacesList.clear() - // Let's filter the ones that are disabled for the moment. We may show them as disabled in the future. val onlyEnabledSpaces = spaces.filterNot { it.isDisabled } + val diffCallback = SpacesListDiffUtil(spacesList, onlyEnabledSpaces) + val diffResult = DiffUtil.calculateDiff(diffCallback) + spacesList.clear() spacesList.addAll(onlyEnabledSpaces) - notifyDataSetChanged() + diffResult.dispatchUpdatesTo(this) } override fun getItemCount(): Int = spacesList.size diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListDiffUtil.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListDiffUtil.kt new file mode 100644 index 00000000000..532c59f5a0a --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListDiffUtil.kt @@ -0,0 +1,52 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.presentation.spaces + +import androidx.recyclerview.widget.DiffUtil +import com.owncloud.android.domain.spaces.model.OCSpace + +class SpacesListDiffUtil( + private val oldList: List, + private val newList: List +) : DiffUtil.Callback() { + override fun getOldListSize(): Int = oldList.size + + override fun getNewListSize(): Int = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldList[oldItemPosition] + val newItem = newList[newItemPosition] + + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldList[oldItemPosition] + val newItem = newList[newItemPosition] + + if ((oldItem.name != newItem.name) || (oldItem.description != newItem.description) || + (oldItem.getSpaceSpecialImage()?.id != newItem.getSpaceSpecialImage()?.id)) { + return false + } + + return true + } +} From 16e2accdee3a14675ed58549f180e871fe559b24 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 10 Jan 2023 18:03:55 +0100 Subject: [PATCH 024/152] Spaces are sorted alphabetically now --- .../main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index cc6ab5f9892..8ac89522ae7 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -104,6 +104,7 @@ interface SpacesDao { SELECT * FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} WHERE $SPACES_ACCOUNT_NAME = :accountName AND $SPACES_DRIVE_TYPE LIKE '$DRIVE_TYPE_PROJECT' + ORDER BY name COLLATE NOCASE ASC """ private const val DELETE_ALL_SPACES_FOR_ACCOUNT = """ From edcc7f6b12b8172fae3ac3b8adcc6a4f4afd8113 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 11 Jan 2023 09:50:40 +0100 Subject: [PATCH 025/152] Update spaces icon to be the same as in web --- owncloudApp/src/main/res/drawable/ic_spaces.xml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/owncloudApp/src/main/res/drawable/ic_spaces.xml b/owncloudApp/src/main/res/drawable/ic_spaces.xml index f7893d63d89..1d062030439 100644 --- a/owncloudApp/src/main/res/drawable/ic_spaces.xml +++ b/owncloudApp/src/main/res/drawable/ic_spaces.xml @@ -1,9 +1,4 @@ - - + + From 8c7134e314d5137a2765af1ef7a5b11304cf950e Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 11 Jan 2023 10:13:07 +0100 Subject: [PATCH 026/152] Fix bug with spaces tab when rotating the screen --- .../java/com/owncloud/android/ui/activity/DrawerActivity.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt index 0b910b9e528..d5c8ac6634d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt @@ -223,6 +223,9 @@ abstract class DrawerActivity : ToolbarActivity() { // Allow or disallow touches with other visible windows getBottomNavigationView()?.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) if (account != null) { + capabilitiesViewModel.capabilities.value?.let { + setSpacesVisibilityBottomBar(it.peekContent()) + } capabilitiesViewModel.capabilities.observe(this, Event.EventObserver { uiResult: UIResult -> setSpacesVisibilityBottomBar(uiResult) }) From e3936044e12c44a7fd0abc2123a6c952f91cfabd Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 11 Jan 2023 10:39:56 +0100 Subject: [PATCH 027/152] Make space card background color brandable --- owncloudApp/src/main/res/values/colors.xml | 1 - owncloudApp/src/main/res/values/setup.xml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/owncloudApp/src/main/res/values/colors.xml b/owncloudApp/src/main/res/values/colors.xml index 2698497be5b..9af136159ab 100644 --- a/owncloudApp/src/main/res/values/colors.xml +++ b/owncloudApp/src/main/res/values/colors.xml @@ -69,6 +69,5 @@ #6E758C - #edf3fa #4c5f79 diff --git a/owncloudApp/src/main/res/values/setup.xml b/owncloudApp/src/main/res/values/setup.xml index 16b76ac72af..f9a23b1f8c6 100644 --- a/owncloudApp/src/main/res/values/setup.xml +++ b/owncloudApp/src/main/res/values/setup.xml @@ -45,6 +45,7 @@ #D6D7D7 @color/black @color/owncloud_blue_accent + #edf3fa @color/owncloud_blue From 400366b6f3bacab87bef094909d7c3929bf4dcbb Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 11 Jan 2023 12:16:40 +0100 Subject: [PATCH 028/152] Simplify observation of capabilities from DrawerActivity --- .../com/owncloud/android/ui/activity/DrawerActivity.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt index d5c8ac6634d..7cf0f1f8c91 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt @@ -223,12 +223,9 @@ abstract class DrawerActivity : ToolbarActivity() { // Allow or disallow touches with other visible windows getBottomNavigationView()?.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) if (account != null) { - capabilitiesViewModel.capabilities.value?.let { - setSpacesVisibilityBottomBar(it.peekContent()) + capabilitiesViewModel.capabilities.observe(this) { event: Event> -> + setSpacesVisibilityBottomBar(event.peekContent()) } - capabilitiesViewModel.capabilities.observe(this, Event.EventObserver { uiResult: UIResult -> - setSpacesVisibilityBottomBar(uiResult) - }) } setCheckedItemAtBottomBar(menuItemId) getBottomNavigationView()?.setOnNavigationItemSelectedListener { menuItem: MenuItem -> From d1be4dd762e09b8dfdf6cc17a419b62222b761a6 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 11 Jan 2023 12:35:10 +0100 Subject: [PATCH 029/152] Removed a color that could be reused --- owncloudApp/src/main/res/layout/spaces_list_item.xml | 2 +- owncloudApp/src/main/res/values/colors.xml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/owncloudApp/src/main/res/layout/spaces_list_item.xml b/owncloudApp/src/main/res/layout/spaces_list_item.xml index b54538f9fa2..2c4b518a281 100644 --- a/owncloudApp/src/main/res/layout/spaces_list_item.xml +++ b/owncloudApp/src/main/res/layout/spaces_list_item.xml @@ -49,7 +49,7 @@ + android:background="@color/actionbar_start_color" /> #6E758C #6E758C - - - #4c5f79 From 244effebc6ef4429dcc51c7f08a33053b5e1c6df Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 11 Jan 2023 17:00:47 +0100 Subject: [PATCH 030/152] Added unit test for OCSpace --- .../domain/spaces/model/OCSpaceTest.kt | 83 ++++++++++++ .../com/owncloud/android/testutil/OCSpace.kt | 125 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 owncloudDomain/src/test/java/com/owncloud/android/domain/spaces/model/OCSpaceTest.kt create mode 100644 owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt diff --git a/owncloudDomain/src/test/java/com/owncloud/android/domain/spaces/model/OCSpaceTest.kt b/owncloudDomain/src/test/java/com/owncloud/android/domain/spaces/model/OCSpaceTest.kt new file mode 100644 index 00000000000..a6f54cb2ab9 --- /dev/null +++ b/owncloudDomain/src/test/java/com/owncloud/android/domain/spaces/model/OCSpaceTest.kt @@ -0,0 +1,83 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.model + +import com.owncloud.android.testutil.OC_SPACE_PERSONAL +import com.owncloud.android.testutil.OC_SPACE_PROJECT_DISABLED +import com.owncloud.android.testutil.OC_SPACE_PROJECT_WITHOUT_IMAGE +import com.owncloud.android.testutil.OC_SPACE_PROJECT_WITH_IMAGE +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +class OCSpaceTest { + + @Test + fun `test space is personal - ok - true`() { + val ocSpace = OC_SPACE_PERSONAL + assertTrue(ocSpace.isPersonal) + } + + @Test + fun `test space is personal - ok - false`() { + val ocSpace = OC_SPACE_PROJECT_WITH_IMAGE + assertFalse(ocSpace.isPersonal) + } + + @Test + fun `test space is project - ok - true`() { + val ocSpace = OC_SPACE_PROJECT_WITH_IMAGE + assertTrue(ocSpace.isProject) + } + + @Test + fun `test space is project - ok - false`() { + val ocSpace = OC_SPACE_PERSONAL + assertFalse(ocSpace.isProject) + } + + @Test + fun `test space is disabled - ok - true`() { + val ocSpace = OC_SPACE_PROJECT_DISABLED + assertTrue(ocSpace.isDisabled) + } + + @Test + fun `test space is disabled - ok - false`() { + val ocSpace = OC_SPACE_PROJECT_WITH_IMAGE + assertFalse(ocSpace.isDisabled) + } + + @Test + fun `test get space special image - ok - has image`() { + val ocSpace = OC_SPACE_PROJECT_WITH_IMAGE + assertNotNull(ocSpace.getSpaceSpecialImage()) + } + + @Test + fun `test get space special image - ok - does not have image`() { + val ocSpace = OC_SPACE_PROJECT_WITHOUT_IMAGE + assertNull(ocSpace.getSpaceSpecialImage()) + } + +} diff --git a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt new file mode 100644 index 00000000000..4dd210033eb --- /dev/null +++ b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt @@ -0,0 +1,125 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.testutil + +import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.domain.spaces.model.SpaceDeleted +import com.owncloud.android.domain.spaces.model.SpaceFile +import com.owncloud.android.domain.spaces.model.SpaceOwner +import com.owncloud.android.domain.spaces.model.SpaceQuota +import com.owncloud.android.domain.spaces.model.SpaceRoot +import com.owncloud.android.domain.spaces.model.SpaceSpecial +import com.owncloud.android.domain.spaces.model.SpaceSpecialFolder +import com.owncloud.android.domain.spaces.model.SpaceUser + +val OC_SPACE_SPECIAL_README = SpaceSpecial( + eTag = "71f78349c3598c9e431a67de5a283fc0", + file = SpaceFile( + mimeType = "text/markdown" + ), + id = "8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199!1c7bbc13-469f-482c-8f13-55ae1402b4c3", + lastModifiedDateTime = "2023-01-01T00:00:00.00000000Z", + name = "readme.md", + size = 50, + specialFolder = SpaceSpecialFolder( + name = "readme" + ), + webDavUrl = "https://server.com/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199%210aa0e03c-ec36-498c-bb9f-857315568199/.space/readme.md" +) + +val OC_SPACE_SPECIAL_IMAGE = SpaceSpecial( + eTag = "26ad7e0b49f9c0f163a6f227af3f130a", + file = SpaceFile( + mimeType = "image/jpeg" + ), + id = "8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199!2597f35a-350f-4cf0-ace1-54b0e6bc377c", + lastModifiedDateTime = "2023-01-01T00:00:00.00000000Z", + name = "image.jpg", + size = 50000, + specialFolder = SpaceSpecialFolder( + name = "image" + ), + webDavUrl = "https://server.com/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199%210aa0e03c-ec36-498c-bb9f-857315568199/.space/image.jpg" +) + +val OC_SPACE_PROJECT_WITH_IMAGE = OCSpace( + accountName = OC_ACCOUNT_NAME, + driveAlias = "project/space", + driveType = "project", + id = "8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199", + lastModifiedDateTime = "2023-01-01T00:00:00.00000000Z", + name = "Space", + owner = SpaceOwner( + user = SpaceUser( + id = "0aa0e03c-ec36-498c-bb9f-857315568199" + ) + ), + quota = SpaceQuota( + remaining = 999999995, + state = "normal", + total = 1000000000, + used = 5 + ), + root = SpaceRoot( + eTag = "989c7968dbbbde8c5fd9849b9123c384", + id = "8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199", + permissions = null, + webDavUrl = "https://server.com/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199", + deleted = null + ), + webUrl = "https://server.com/f/8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199", + description = "This is the description of the space", + special = listOf( + OC_SPACE_SPECIAL_IMAGE, + OC_SPACE_SPECIAL_README + ) +) + +val OC_SPACE_PROJECT_WITHOUT_IMAGE = OC_SPACE_PROJECT_WITH_IMAGE.copy( + special = listOf(OC_SPACE_SPECIAL_README) +) + +val OC_SPACE_PERSONAL = OC_SPACE_PROJECT_WITH_IMAGE.copy( + driveAlias = "personal/admin", + driveType = "personal", + name = "Admin", + description = null, + special = null +) + +val OC_SPACE_PROJECT_DISABLED = OC_SPACE_PROJECT_WITH_IMAGE.copy( + quota = SpaceQuota( + remaining = null, + state = null, + total = 1000000000, + used = null + ), + root = SpaceRoot( + eTag = "989c7968dbbbde8c5fd9849b9123c384", + id = "8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199", + permissions = null, + webDavUrl = "https://server.com/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568199", + deleted = SpaceDeleted( + state = "trashed" + ) + ), + special = null +) From 68710ffe719ea94e31793d75668054f1a68bac0a Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 12 Jan 2023 11:01:15 +0100 Subject: [PATCH 031/152] Added instrumented tests for SpacesDao --- .../android/data/spaces/db/SpacesDaoTest.kt | 163 +++++++++++++++++ .../implementation/OCLocalSpacesDataSource.kt | 171 +++++++++--------- .../android/data/spaces/db/SpacesDao.kt | 5 + 3 files changed, 257 insertions(+), 82 deletions(-) create mode 100644 owncloudData/src/androidTest/java/com/owncloud/android/data/spaces/db/SpacesDaoTest.kt diff --git a/owncloudData/src/androidTest/java/com/owncloud/android/data/spaces/db/SpacesDaoTest.kt b/owncloudData/src/androidTest/java/com/owncloud/android/data/spaces/db/SpacesDaoTest.kt new file mode 100644 index 00000000000..243c178d56c --- /dev/null +++ b/owncloudData/src/androidTest/java/com/owncloud/android/data/spaces/db/SpacesDaoTest.kt @@ -0,0 +1,163 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.data.spaces.db + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.test.filters.MediumTest +import androidx.test.platform.app.InstrumentationRegistry +import com.owncloud.android.data.OwncloudDatabase +import com.owncloud.android.data.spaces.datasources.implementation.OCLocalSpacesDataSource.Companion.toEntity +import com.owncloud.android.data.spaces.datasources.implementation.OCLocalSpacesDataSource.Companion.toModel +import com.owncloud.android.testutil.OC_SPACE_PROJECT_WITHOUT_IMAGE +import com.owncloud.android.testutil.OC_SPACE_PROJECT_WITH_IMAGE +import com.owncloud.android.testutil.OC_SPACE_SPECIAL_IMAGE +import com.owncloud.android.testutil.OC_SPACE_SPECIAL_README +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +@MediumTest +class SpacesDaoTest { + @Rule + @JvmField + val instantExecutorRule = InstantTaskExecutorRule() + + private lateinit var spacesDao: SpacesDao + + @Before + fun setUp() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + OwncloudDatabase.switchToInMemory(context) + val db: OwncloudDatabase = OwncloudDatabase.getDatabase(context) + spacesDao = db.spacesDao() + } + + @Test + fun insertOrDeleteSpacesWithEmptyDatabase() { + val accountName = OC_SPACE_PROJECT_WITHOUT_IMAGE.accountName + + val specialsToInsert = listOf( + OC_SPACE_SPECIAL_README.toEntity(accountName, OC_SPACE_PROJECT_WITHOUT_IMAGE.id) + ) + + val spacesToInsertModel = listOf( + OC_SPACE_PROJECT_WITHOUT_IMAGE + ) + + val spacesToInsert = spacesToInsertModel.map { it.toEntity() } + + spacesDao.insertOrDeleteSpaces(spacesToInsert, specialsToInsert) + + val spacesInDatabase = spacesDao.getProjectSpacesWithSpecialsForAccount(accountName).map { it.toModel() } + + assertNotNull(spacesInDatabase) + assertEquals(1, spacesInDatabase.size) + assertEquals(spacesToInsertModel, spacesInDatabase) + } + + @Test + fun insertOrDeleteSpacesWithSpacesAlreadyInDatabaseNotAttachedToAccountAnymore() { + val accountName = OC_SPACE_PROJECT_WITHOUT_IMAGE.accountName + + val specialsAlreadyInDatabaseToInsert = listOf( + OC_SPACE_SPECIAL_IMAGE.toEntity(accountName, OC_SPACE_PROJECT_WITH_IMAGE.id), + OC_SPACE_SPECIAL_README.toEntity(accountName, OC_SPACE_PROJECT_WITH_IMAGE.id) + ) + + val spacesAlreadyInDatabaseToInsert = listOf( + OC_SPACE_PROJECT_WITH_IMAGE.toEntity() + ) + + spacesDao.insertOrDeleteSpaces(spacesAlreadyInDatabaseToInsert, specialsAlreadyInDatabaseToInsert) + + val spacesAlreadyInDatabase = spacesDao.getProjectSpacesWithSpecialsForAccount(accountName) + + assertEquals(1, spacesAlreadyInDatabase.size) + assertEquals(2, spacesAlreadyInDatabase[0].specials.size) + + val newSpecialsToInsert = listOf( + OC_SPACE_SPECIAL_README.toEntity(accountName, OC_SPACE_PROJECT_WITHOUT_IMAGE.id), + ) + + val newSpacesToInsertModel = listOf( + OC_SPACE_PROJECT_WITHOUT_IMAGE + ) + + val newSpacesToInsert = newSpacesToInsertModel.map { it.toEntity() } + + spacesDao.insertOrDeleteSpaces(newSpacesToInsert, newSpecialsToInsert) + + val spacesInDatabaseEntity = spacesDao.getProjectSpacesWithSpecialsForAccount(accountName) + val spacesInDatabase = spacesInDatabaseEntity.map { it.toModel() } + val specialsInDatabase = spacesInDatabaseEntity.flatMap { it.specials } + + assertNotNull(spacesInDatabase) + assertEquals(1, spacesInDatabase.size) + assertEquals(1, specialsInDatabase.size) + assertEquals(newSpacesToInsertModel, spacesInDatabase) + } + + @Test + fun insertOrDeleteSpacesWithSpacesAlreadyInDatabaseStillAttachedToAccount() { + val accountName = OC_SPACE_PROJECT_WITHOUT_IMAGE.accountName + + val specialsAlreadyInDatabaseToInsert = listOf( + OC_SPACE_SPECIAL_IMAGE.toEntity(accountName, OC_SPACE_PROJECT_WITH_IMAGE.id), + OC_SPACE_SPECIAL_README.toEntity(accountName, OC_SPACE_PROJECT_WITH_IMAGE.id) + ) + + val spacesAlreadyInDatabaseToInsert = listOf( + OC_SPACE_PROJECT_WITH_IMAGE.toEntity() + ) + + spacesDao.insertOrDeleteSpaces(spacesAlreadyInDatabaseToInsert, specialsAlreadyInDatabaseToInsert) + + val spacesAlreadyInDatabase = spacesDao.getProjectSpacesWithSpecialsForAccount(accountName) + + assertEquals(1, spacesAlreadyInDatabase.size) + assertEquals(2, spacesAlreadyInDatabase[0].specials.size) + + val anotherSpaceId = "8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-857315568190" + + val newSpecialsToInsert = listOf( + OC_SPACE_SPECIAL_README.copy( + id = "$anotherSpaceId!1c7bbc13-469f-482c-8f13-55ae1402b4c4" + ).toEntity(accountName, OC_SPACE_PROJECT_WITHOUT_IMAGE.id), + ) + specialsAlreadyInDatabaseToInsert + + val newSpacesToInsert = listOf( + OC_SPACE_PROJECT_WITHOUT_IMAGE.copy( + id = anotherSpaceId + ).toEntity() + ) + spacesAlreadyInDatabaseToInsert + + spacesDao.insertOrDeleteSpaces(newSpacesToInsert, newSpecialsToInsert) + + val spacesInDatabase = spacesDao.getProjectSpacesWithSpecialsForAccount(accountName) + val specialsInDatabase = spacesInDatabase.flatMap { it.specials } + + assertNotNull(spacesInDatabase) + assertEquals(2, spacesInDatabase.size) + assertEquals(3, specialsInDatabase.size) + } +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 3d36f9fe3f6..38aa32d50d9 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -21,6 +21,7 @@ package com.owncloud.android.data.spaces.datasources.implementation +import androidx.annotation.VisibleForTesting import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource import com.owncloud.android.data.spaces.db.SpaceQuotaEntity import com.owncloud.android.data.spaces.db.SpaceRootEntity @@ -66,89 +67,95 @@ class OCLocalSpacesDataSource( } } - private fun SpacesWithSpecials.toModel() = - OCSpace( - accountName = space.accountName, - driveAlias = space.driveAlias, - driveType = space.driveType, - id = space.id, - lastModifiedDateTime = space.lastModifiedDateTime, - name = space.name, - owner = SpaceOwner( - user = SpaceUser( - id = space.ownerId - ) - ), - quota = space.quota?.let { spaceQuotaEntity -> - SpaceQuota( - remaining = spaceQuotaEntity.remaining, - state = spaceQuotaEntity.state, - total = spaceQuotaEntity.total, - used = spaceQuotaEntity.used - ) - }, - root = space.root!!.let { spaceRootEntity -> - SpaceRoot( - eTag = spaceRootEntity.eTag, - id = spaceRootEntity.id, - permissions = null, - webDavUrl = spaceRootEntity.webDavUrl, - deleted = spaceRootEntity.deleteState?.let { SpaceDeleted(state = it) } - ) - }, - webUrl = space.webUrl, - description = space.description, - special = specials.map { - it.toModel() - } - ) + companion object { + @VisibleForTesting + fun SpacesWithSpecials.toModel() = + OCSpace( + accountName = space.accountName, + driveAlias = space.driveAlias, + driveType = space.driveType, + id = space.id, + lastModifiedDateTime = space.lastModifiedDateTime, + name = space.name, + owner = SpaceOwner( + user = SpaceUser( + id = space.ownerId + ) + ), + quota = space.quota?.let { spaceQuotaEntity -> + SpaceQuota( + remaining = spaceQuotaEntity.remaining, + state = spaceQuotaEntity.state, + total = spaceQuotaEntity.total, + used = spaceQuotaEntity.used + ) + }, + root = space.root!!.let { spaceRootEntity -> + SpaceRoot( + eTag = spaceRootEntity.eTag, + id = spaceRootEntity.id, + permissions = null, + webDavUrl = spaceRootEntity.webDavUrl, + deleted = spaceRootEntity.deleteState?.let { SpaceDeleted(state = it) } + ) + }, + webUrl = space.webUrl, + description = space.description, + special = specials.map { + it.toModel() + } + ) - private fun SpaceSpecialEntity.toModel() = - SpaceSpecial( - eTag = eTag, - file = SpaceFile( - mimeType = fileMimeType - ), - id = id, - lastModifiedDateTime = lastModifiedDateTime, - name = name, - size = size, - specialFolder = SpaceSpecialFolder( - name = specialFolderName - ), - webDavUrl = webDavUrl - ) + @VisibleForTesting + fun SpaceSpecialEntity.toModel() = + SpaceSpecial( + eTag = eTag, + file = SpaceFile( + mimeType = fileMimeType + ), + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + size = size, + specialFolder = SpaceSpecialFolder( + name = specialFolderName + ), + webDavUrl = webDavUrl + ) - private fun OCSpace.toEntity() = - SpacesEntity( - accountName = accountName, - driveAlias = driveAlias, - driveType = driveType, - id = id, - lastModifiedDateTime = lastModifiedDateTime, - name = name, - ownerId = owner.user.id, - quota = quota?.let { quotaModel -> - SpaceQuotaEntity(remaining = quotaModel.remaining, state = quotaModel.state, total = quotaModel.total, used = quotaModel.used) - }, - root = root.let { rootModel -> - SpaceRootEntity(eTag = rootModel.eTag, id = rootModel.id, webDavUrl = rootModel.webDavUrl, deleteState = rootModel.deleted?.state) - }, - webUrl = webUrl, - description = description, - ) + @VisibleForTesting + fun OCSpace.toEntity() = + SpacesEntity( + accountName = accountName, + driveAlias = driveAlias, + driveType = driveType, + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + ownerId = owner.user.id, + quota = quota?.let { quotaModel -> + SpaceQuotaEntity(remaining = quotaModel.remaining, state = quotaModel.state, total = quotaModel.total, used = quotaModel.used) + }, + root = root.let { rootModel -> + SpaceRootEntity(eTag = rootModel.eTag, id = rootModel.id, webDavUrl = rootModel.webDavUrl, deleteState = rootModel.deleted?.state) + }, + webUrl = webUrl, + description = description, + ) - private fun SpaceSpecial.toEntity(accountName: String, spaceId: String): SpaceSpecialEntity = - SpaceSpecialEntity( - accountName = accountName, - spaceId = spaceId, - eTag = eTag, - fileMimeType = file.mimeType, - id = id, - lastModifiedDateTime = lastModifiedDateTime, - name = name, - size = size, - specialFolderName = specialFolder.name, - webDavUrl = webDavUrl - ) + @VisibleForTesting + fun SpaceSpecial.toEntity(accountName: String, spaceId: String): SpaceSpecialEntity = + SpaceSpecialEntity( + accountName = accountName, + spaceId = spaceId, + eTag = eTag, + fileMimeType = file.mimeType, + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + size = size, + specialFolderName = specialFolder.name, + webDavUrl = webDavUrl + ) + } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index 8ac89522ae7..c9ebf2909d7 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -76,6 +76,11 @@ interface SpacesDao { accountName: String, ): List + @Query(SELECT_PROJECT_SPACES_FOR_ACCOUNT) + fun getProjectSpacesWithSpecialsForAccount( + accountName: String, + ): List + @Query(SELECT_PROJECT_SPACES_FOR_ACCOUNT) fun getProjectSpacesWithSpecialsForAccountAsFlow( accountName: String, From a9e2ceb5055749b581887e947015b94e507f0b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Fri, 13 Jan 2023 08:53:33 +0100 Subject: [PATCH 032/152] Support shares space --- .../com.owncloud.android.data.OwncloudDatabase/40.json | 10 +++++----- .../implementation/OCLocalSpacesDataSource.kt | 10 +++++----- .../implementation/OCRemoteSpacesDataSource.kt | 10 ++++++---- .../owncloud/android/data/spaces/db/SpacesEntity.kt | 6 +++--- .../owncloud/android/domain/spaces/model/OCSpace.kt | 6 +++--- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json index 822ae83b93c..b311de07711 100644 --- a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 40, - "identityHash": "50fbdf2d302ff496a8c428b44ea22665", + "identityHash": "fecbaa0d5e6dd4dad5f253c7785747b3", "entities": [ { "tableName": "folder_backup", @@ -739,7 +739,7 @@ }, { "tableName": "spaces", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_name` TEXT NOT NULL, `drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT NOT NULL, `name` TEXT NOT NULL, `owner_id` TEXT NOT NULL, `web_url` TEXT NOT NULL, `description` TEXT, `quota_remaining` INTEGER, `quota_state` TEXT, `quota_total` INTEGER, `quota_used` INTEGER, `root_etag` TEXT, `root_id` TEXT, `root_web_dav_url` TEXT, `root_deleted_state` TEXT, PRIMARY KEY(`account_name`, `space_id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_name` TEXT NOT NULL, `drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT, `name` TEXT NOT NULL, `owner_id` TEXT, `web_url` TEXT NOT NULL, `description` TEXT, `quota_remaining` INTEGER, `quota_state` TEXT, `quota_total` INTEGER, `quota_used` INTEGER, `root_etag` TEXT, `root_id` TEXT, `root_web_dav_url` TEXT, `root_deleted_state` TEXT, PRIMARY KEY(`account_name`, `space_id`))", "fields": [ { "fieldPath": "accountName", @@ -769,7 +769,7 @@ "fieldPath": "lastModifiedDateTime", "columnName": "last_modified_date_time", "affinity": "TEXT", - "notNull": true + "notNull": false }, { "fieldPath": "name", @@ -781,7 +781,7 @@ "fieldPath": "ownerId", "columnName": "owner_id", "affinity": "TEXT", - "notNull": true + "notNull": false }, { "fieldPath": "webUrl", @@ -979,7 +979,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '50fbdf2d302ff496a8c428b44ea22665')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fecbaa0d5e6dd4dad5f253c7785747b3')" ] } } \ No newline at end of file diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 38aa32d50d9..c9b680b63f5 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -77,11 +77,11 @@ class OCLocalSpacesDataSource( id = space.id, lastModifiedDateTime = space.lastModifiedDateTime, name = space.name, - owner = SpaceOwner( - user = SpaceUser( - id = space.ownerId + owner = space.ownerId?.let { spaceOwnerIdEntity -> + SpaceOwner( + user = SpaceUser(id = spaceOwnerIdEntity) ) - ), + }, quota = space.quota?.let { spaceQuotaEntity -> SpaceQuota( remaining = spaceQuotaEntity.remaining, @@ -132,7 +132,7 @@ class OCLocalSpacesDataSource( id = id, lastModifiedDateTime = lastModifiedDateTime, name = name, - ownerId = owner.user.id, + ownerId = owner?.user?.id, quota = quota?.let { quotaModel -> SpaceQuotaEntity(remaining = quotaModel.remaining, state = quotaModel.state, total = quotaModel.total, used = quotaModel.used) }, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt index 3e4f7e28aec..a786d0df4bd 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt @@ -53,11 +53,13 @@ class OCRemoteSpacesDataSource( id = id, lastModifiedDateTime = lastModifiedDateTime, name = name, - owner = SpaceOwner( - user = SpaceUser( - id = owner.user.id + owner = owner?.let { ownerResponse -> + SpaceOwner( + user = SpaceUser( + id = ownerResponse.user.id + ) ) - ), + }, quota = quota?.let { quotaResponse -> SpaceQuota( remaining = quotaResponse.remaining, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt index d89d9e88bd1..b01a279c948 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt @@ -51,10 +51,10 @@ data class SpacesEntity( @ColumnInfo(name = SPACES_ID) val id: String, @ColumnInfo(name = SPACES_LAST_MODIFIED_DATE_TIME) - val lastModifiedDateTime: String, + val lastModifiedDateTime: String?, val name: String, @ColumnInfo(name = SPACES_OWNER_ID) - val ownerId: String, + val ownerId: String?, @Embedded val quota: SpaceQuotaEntity?, @Embedded @@ -98,7 +98,7 @@ data class SpaceQuotaEntity( data class SpaceRootEntity( @ColumnInfo(name = SPACES_ROOT_ETAG) - val eTag: String, + val eTag: String?, @ColumnInfo(name = SPACES_ROOT_ID) val id: String, @ColumnInfo(name = SPACES_ROOT_WEB_DAV_URL) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index b895e61d9dd..9f0e9ce03d8 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -26,9 +26,9 @@ data class OCSpace( val driveAlias: String, val driveType: String, val id: String, - val lastModifiedDateTime: String, + val lastModifiedDateTime: String?, val name: String, - val owner: SpaceOwner, + val owner: SpaceOwner?, val quota: SpaceQuota?, val root: SpaceRoot, val webUrl: String, @@ -66,7 +66,7 @@ data class SpaceQuota( ) data class SpaceRoot( - val eTag: String, + val eTag: String?, val id: String, val permissions: List?, val webDavUrl: String, From cdd4ca786455ca344fe0d447d2b1ddaf9fd59a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Fri, 13 Jan 2023 11:29:36 +0100 Subject: [PATCH 033/152] Update permissions parsing to latest api changes --- .../implementation/OCRemoteSpacesDataSource.kt | 18 +++++++++++++++--- .../android/domain/spaces/model/OCSpace.kt | 12 +++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt index a786d0df4bd..8f2636a5a0e 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt @@ -23,14 +23,16 @@ import com.owncloud.android.data.spaces.datasources.RemoteSpacesDataSource import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.SpaceDeleted import com.owncloud.android.domain.spaces.model.SpaceFile -import com.owncloud.android.domain.spaces.model.SpaceGrantedTo +import com.owncloud.android.domain.spaces.model.SpaceGrantedToIdentities import com.owncloud.android.domain.spaces.model.SpaceOwner import com.owncloud.android.domain.spaces.model.SpacePermission +import com.owncloud.android.domain.spaces.model.SpacePermissionIdentity import com.owncloud.android.domain.spaces.model.SpaceQuota import com.owncloud.android.domain.spaces.model.SpaceRoot import com.owncloud.android.domain.spaces.model.SpaceSpecial import com.owncloud.android.domain.spaces.model.SpaceSpecialFolder import com.owncloud.android.domain.spaces.model.SpaceUser +import com.owncloud.android.lib.resources.spaces.responses.IdentityPermissionResponse import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse import com.owncloud.android.lib.resources.spaces.services.SpacesService @@ -73,8 +75,11 @@ class OCRemoteSpacesDataSource( id = root.id, permissions = root.permissions?.map { permissionsResponse -> SpacePermission( - grantedTo = permissionsResponse.grantedTo.map { grantedToResponse -> - SpaceGrantedTo(SpaceUser(grantedToResponse.user.id)) + grantedToIdentities = permissionsResponse.getGrantedToIdentitiesResponse().map { grantedToResponse -> + SpaceGrantedToIdentities( + user = grantedToResponse.user?.toModel(), + group = grantedToResponse.group?.toModel(), + ) }, roles = permissionsResponse.roles, ) @@ -97,4 +102,11 @@ class OCRemoteSpacesDataSource( ) } ) + + private fun IdentityPermissionResponse.toModel() = + SpacePermissionIdentity( + id = this.id, + displayName = this.displayName, + ) + } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index 9f0e9ce03d8..3fdd97a68ff 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -97,12 +97,18 @@ data class SpaceFile( ) data class SpacePermission( - val grantedTo: List, + val grantedToIdentities: List, val roles: List ) -data class SpaceGrantedTo( - val user: SpaceUser? +data class SpaceGrantedToIdentities( + val user: SpacePermissionIdentity?, + val group: SpacePermissionIdentity?, +) + +data class SpacePermissionIdentity( + val id: String, + val displayName: String?, ) data class SpaceSpecialFolder( From 66a18e2368bbf69ee8c2d878255478a28b0cf7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Fri, 13 Jan 2023 11:36:11 +0100 Subject: [PATCH 034/152] Added some trailing commas --- .../datasources/implementation/OCLocalSpacesDataSource.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index c9b680b63f5..756451888d1 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -79,7 +79,9 @@ class OCLocalSpacesDataSource( name = space.name, owner = space.ownerId?.let { spaceOwnerIdEntity -> SpaceOwner( - user = SpaceUser(id = spaceOwnerIdEntity) + user = SpaceUser( + id = spaceOwnerIdEntity + ) ) }, quota = space.quota?.let { spaceQuotaEntity -> @@ -87,7 +89,7 @@ class OCLocalSpacesDataSource( remaining = spaceQuotaEntity.remaining, state = spaceQuotaEntity.state, total = spaceQuotaEntity.total, - used = spaceQuotaEntity.used + used = spaceQuotaEntity.used, ) }, root = space.root!!.let { spaceRootEntity -> @@ -96,7 +98,7 @@ class OCLocalSpacesDataSource( id = spaceRootEntity.id, permissions = null, webDavUrl = spaceRootEntity.webDavUrl, - deleted = spaceRootEntity.deleteState?.let { SpaceDeleted(state = it) } + deleted = spaceRootEntity.deleteState?.let { SpaceDeleted(state = it) }, ) }, webUrl = space.webUrl, From 8561990859f3d5ec2b7f170a2d9c9e250a76d324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Thu, 12 Jan 2023 08:58:21 +0100 Subject: [PATCH 035/152] Fix empty view not showed when all spaces are disabled --- .../android/presentation/spaces/SpacesListAdapter.kt | 6 ++---- .../android/presentation/spaces/SpacesListFragment.kt | 7 +++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt index c9ab83bbeba..c1c57cc6577 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -84,12 +84,10 @@ class SpacesListAdapter : RecyclerView.Adapter() { } fun setData(spaces: List) { - // Let's filter the ones that are disabled for the moment. We may show them as disabled in the future. - val onlyEnabledSpaces = spaces.filterNot { it.isDisabled } - val diffCallback = SpacesListDiffUtil(spacesList, onlyEnabledSpaces) + val diffCallback = SpacesListDiffUtil(spacesList, spaces) val diffResult = DiffUtil.calculateDiff(diffCallback) spacesList.clear() - spacesList.addAll(onlyEnabledSpaces) + spacesList.addAll(spaces) diffResult.dispatchUpdatesTo(this) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index d2140a26f31..05b5d461f63 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -73,8 +73,11 @@ class SpacesListFragment : Fragment() { private fun subscribeToViewModels() { collectLatestLifecycleFlow(spacesListViewModel.spacesList) { uiState -> - showOrHideEmptyView(uiState.spaces) - spacesListAdapter.setData(uiState.spaces) + // Let's filter the ones that are disabled for the moment. We may show them as disabled in the future. + val onlyEnabledSpaces = uiState.spaces.filterNot { it.isDisabled } + + showOrHideEmptyView(onlyEnabledSpaces) + spacesListAdapter.setData(onlyEnabledSpaces) binding.swipeRefreshSpacesList.isRefreshing = uiState.refreshing uiState.error?.let { showErrorInSnackbar(R.string.spaces_sync_failed, it) } } From da60bc61f6dbc2b77ee356a6e9445f2a6dae0551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Thu, 12 Jan 2023 13:48:34 +0100 Subject: [PATCH 036/152] Increase spaces thumbnails to support different screen sizes --- .../android/datamodel/ThumbnailsCacheManager.java | 9 ++++++--- owncloudApp/src/main/res/layout/spaces_list_item.xml | 2 +- owncloudApp/src/main/res/values-h640dp/dims.xml | 5 ++++- owncloudApp/src/main/res/values-h740dp/dims.xml | 5 ++++- owncloudApp/src/main/res/values/dims.xml | 6 ++++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 848cda72494..da94588625f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -356,11 +356,14 @@ private Bitmap doFileInBackground() { } private String getSpaceSpecialUri(SpaceSpecial spaceSpecial) { + // Converts dp to pixel + Resources r = MainApp.Companion.getAppContext().getResources(); + Integer spacesThumbnailSize = Math.round(r.getDimension(R.dimen.spaces_thumbnail_height)) * 2; return String.format(Locale.ROOT, SPACE_SPECIAL_URI, spaceSpecial.getWebDavUrl(), - getThumbnailDimension(), - getThumbnailDimension(), + spacesThumbnailSize, + spacesThumbnailSize, spaceSpecial.getETag()); } @@ -373,7 +376,7 @@ private Bitmap doSpaceImageInBackground() { Bitmap thumbnail = getBitmapFromDiskCache(imageKey); // Not found in disk cache - if (thumbnail == null) { // TODO: Check if the current thumbnail is outdated + if (thumbnail == null) { int px = getThumbnailDimension(); // Download thumbnail from server diff --git a/owncloudApp/src/main/res/layout/spaces_list_item.xml b/owncloudApp/src/main/res/layout/spaces_list_item.xml index 2c4b518a281..b91d2b55ca8 100644 --- a/owncloudApp/src/main/res/layout/spaces_list_item.xml +++ b/owncloudApp/src/main/res/layout/spaces_list_item.xml @@ -42,7 +42,7 @@ diff --git a/owncloudApp/src/main/res/values-h640dp/dims.xml b/owncloudApp/src/main/res/values-h640dp/dims.xml index 75deca8312d..cc53084f8a4 100644 --- a/owncloudApp/src/main/res/values-h640dp/dims.xml +++ b/owncloudApp/src/main/res/values-h640dp/dims.xml @@ -23,4 +23,7 @@ 50dp 16dp 8dp - \ No newline at end of file + + + 192dp + diff --git a/owncloudApp/src/main/res/values-h740dp/dims.xml b/owncloudApp/src/main/res/values-h740dp/dims.xml index 745408ea4de..d4973189f1d 100644 --- a/owncloudApp/src/main/res/values-h740dp/dims.xml +++ b/owncloudApp/src/main/res/values-h740dp/dims.xml @@ -23,4 +23,7 @@ 100dp 32dp 8dp - \ No newline at end of file + + + 256dp + diff --git a/owncloudApp/src/main/res/values/dims.xml b/owncloudApp/src/main/res/values/dims.xml index 02b3a7e560a..563b43811a4 100644 --- a/owncloudApp/src/main/res/values/dims.xml +++ b/owncloudApp/src/main/res/values/dims.xml @@ -1,5 +1,4 @@ - - + 156dp From 87c49532ce4b6e02b34f1a816d47fe238c7edbfd Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Fri, 13 Jan 2023 14:52:25 +0100 Subject: [PATCH 037/152] Remove spaces attached to an account when removing that account --- .../owncloud/android/dependecyinjection/UseCaseModule.kt | 2 +- .../android/usecases/accounts/RemoveAccountUseCase.kt | 7 ++++++- .../data/spaces/datasources/LocalSpacesDataSource.kt | 1 + .../datasources/implementation/OCLocalSpacesDataSource.kt | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 764aa1982ce..a55a861fe4b 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -227,5 +227,5 @@ val useCaseModule = module { factory { GetUrlToOpenInWebUseCase(get()) } // Accounts - factory { RemoveAccountUseCase(get(), get(), get(), get(), get(), get(), get(), get()) } + factory { RemoveAccountUseCase(get(), get(), get(), get(), get(), get(), get(), get(), get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/usecases/accounts/RemoveAccountUseCase.kt b/owncloudApp/src/main/java/com/owncloud/android/usecases/accounts/RemoveAccountUseCase.kt index a650218721a..2e458733fd0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/usecases/accounts/RemoveAccountUseCase.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/usecases/accounts/RemoveAccountUseCase.kt @@ -23,6 +23,7 @@ package com.owncloud.android.usecases.accounts import com.owncloud.android.data.capabilities.datasources.LocalCapabilitiesDataSource import com.owncloud.android.data.files.datasources.LocalFileDataSource import com.owncloud.android.data.sharing.shares.datasources.LocalShareDataSource +import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource import com.owncloud.android.data.user.datasources.LocalUserDataSource import com.owncloud.android.domain.BaseUseCase import com.owncloud.android.domain.camerauploads.usecases.GetCameraUploadsConfigurationUseCase @@ -43,7 +44,8 @@ class RemoveAccountUseCase( private val localFileDataSource: LocalFileDataSource, private val localCapabilitiesDataSource: LocalCapabilitiesDataSource, private val localShareDataSource: LocalShareDataSource, - private val localUserDataSource: LocalUserDataSource + private val localUserDataSource: LocalUserDataSource, + private val localSpacesDataSource: LocalSpacesDataSource, ) : BaseUseCase() { override fun run(params: Params) { @@ -72,6 +74,9 @@ class RemoveAccountUseCase( // Delete quota for the removed account in database localUserDataSource.deleteQuotaForAccount(params.accountName) + + // Delete spaces for the removed account in database + localSpacesDataSource.deleteSpacesForAccount(params.accountName) } data class Params( diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt index c5b470f72a6..98cfdbd71c9 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt @@ -27,4 +27,5 @@ import kotlinx.coroutines.flow.Flow interface LocalSpacesDataSource { fun saveSpacesForAccount(listOfSpaces: List) fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> + fun deleteSpacesForAccount(accountName: String) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 756451888d1..5be4cb5c86d 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -67,6 +67,10 @@ class OCLocalSpacesDataSource( } } + override fun deleteSpacesForAccount(accountName: String) { + spacesDao.deleteSpacesForAccount(accountName) + } + companion object { @VisibleForTesting fun SpacesWithSpecials.toModel() = From c42c5da851ec913684d0f0a071b65e85a8250802 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Mon, 16 Jan 2023 16:42:16 +0100 Subject: [PATCH 038/152] Add "spaceId" foreign key to files database table --- .../40.json | 28 ++++++++++++++++--- .../implementation/OCLocalFileDataSource.kt | 3 +- .../android/data/files/db/OCFileEntity.kt | 21 ++++++++++++-- .../android/domain/files/model/OCFile.kt | 8 ++++-- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json index b311de07711..56625698e0f 100644 --- a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 40, - "identityHash": "fecbaa0d5e6dd4dad5f253c7785747b3", + "identityHash": "72151f55aa7990b47067c2ac269e3cd9", "entities": [ { "tableName": "folder_backup", @@ -335,7 +335,7 @@ }, { "tableName": "files", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`parentId` INTEGER, `owner` TEXT NOT NULL, `remotePath` TEXT NOT NULL, `remoteId` TEXT, `length` INTEGER NOT NULL, `creationTimestamp` INTEGER, `modificationTimestamp` INTEGER NOT NULL, `mimeType` TEXT NOT NULL, `etag` TEXT, `permissions` TEXT, `privateLink` TEXT, `storagePath` TEXT, `name` TEXT, `treeEtag` TEXT, `keepInSync` INTEGER, `lastSyncDateForData` INTEGER, `fileShareViaLink` INTEGER, `lastSyncDateForProperties` INTEGER, `needsToUpdateThumbnail` INTEGER NOT NULL, `modifiedAtLastSyncForData` INTEGER, `etagInConflict` TEXT, `fileIsDownloading` INTEGER, `sharedWithSharee` INTEGER, `sharedByLink` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`parentId` INTEGER, `owner` TEXT NOT NULL, `remotePath` TEXT NOT NULL, `remoteId` TEXT, `length` INTEGER NOT NULL, `creationTimestamp` INTEGER, `modificationTimestamp` INTEGER NOT NULL, `mimeType` TEXT NOT NULL, `etag` TEXT, `permissions` TEXT, `privateLink` TEXT, `storagePath` TEXT, `name` TEXT, `treeEtag` TEXT, `keepInSync` INTEGER, `lastSyncDateForData` INTEGER, `fileShareViaLink` INTEGER, `lastSyncDateForProperties` INTEGER, `needsToUpdateThumbnail` INTEGER NOT NULL, `modifiedAtLastSyncForData` INTEGER, `etagInConflict` TEXT, `fileIsDownloading` INTEGER, `sharedWithSharee` INTEGER, `sharedByLink` INTEGER NOT NULL, `spaceId` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`owner`, `spaceId`) REFERENCES `spaces`(`account_name`, `space_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "parentId", @@ -481,6 +481,12 @@ "affinity": "INTEGER", "notNull": true }, + { + "fieldPath": "spaceId", + "columnName": "spaceId", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "id", "columnName": "id", @@ -495,7 +501,21 @@ "autoGenerate": true }, "indices": [], - "foreignKeys": [] + "foreignKeys": [ + { + "table": "spaces", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "owner", + "spaceId" + ], + "referencedColumns": [ + "account_name", + "space_id" + ] + } + ] }, { "tableName": "files_sync", @@ -979,7 +999,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fecbaa0d5e6dd4dad5f253c7785747b3')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '72151f55aa7990b47067c2ac269e3cd9')" ] } } \ No newline at end of file diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt index 0a32110e4a7..cf8a484a364 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt @@ -251,7 +251,8 @@ class OCLocalFileDataSource( modifiedAtLastSyncForData = modifiedAtLastSyncForData, etagInConflict = etagInConflict, treeEtag = treeEtag, - name = fileName + name = fileName, + spaceId = spaceId, ).apply { this@toEntity.id?.let { modelId -> this.id = modelId } } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt index 271282686a7..9e366fb905e 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt @@ -21,6 +21,7 @@ package com.owncloud.android.data.files.db import android.database.Cursor import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.ForeignKey import androidx.room.PrimaryKey import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_ACCOUNT_OWNER @@ -47,12 +48,23 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_STORAGE_PAT import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_TREE_ETAG import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_UPDATE_THUMBNAIL import com.owncloud.android.data.ProviderMeta.ProviderTableMeta._ID +import com.owncloud.android.data.files.db.OCFileEntity.Companion.FILE_OWNER +import com.owncloud.android.data.files.db.OCFileEntity.Companion.FILE_SPACE_ID +import com.owncloud.android.data.spaces.db.SpacesEntity +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ACCOUNT_NAME +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID import com.owncloud.android.domain.extensions.isOneOf import com.owncloud.android.domain.files.model.MIME_DIR import com.owncloud.android.domain.files.model.MIME_DIR_UNIX @Entity( - tableName = FILES_TABLE_NAME + tableName = FILES_TABLE_NAME, + foreignKeys = [ForeignKey( + entity = SpacesEntity::class, + parentColumns = arrayOf(SPACES_ACCOUNT_NAME, SPACES_ID), + childColumns = arrayOf(FILE_OWNER, FILE_SPACE_ID), + onDelete = ForeignKey.CASCADE + )] ) data class OCFileEntity( var parentId: Long? = null, @@ -69,7 +81,6 @@ data class OCFileEntity( val storagePath: String? = null, var name: String? = null, val treeEtag: String? = null, - @ColumnInfo(name = "keepInSync") var availableOfflineStatus: Int? = null, val lastSyncDateForData: Long? = null, @@ -80,7 +91,8 @@ data class OCFileEntity( val etagInConflict: String? = null, val fileIsDownloading: Boolean? = null, val sharedWithSharee: Boolean? = false, - var sharedByLink: Boolean = false + var sharedByLink: Boolean = false, + val spaceId: String? = null, ) { @PrimaryKey(autoGenerate = true) var id: Long = 0 @@ -127,5 +139,8 @@ data class OCFileEntity( private fun Cursor.getStringFromColumnOrEmpty( columnName: String ): String = getColumnIndex(columnName).takeUnless { it < 0 }?.let { getString(it) }.orEmpty() + + const val FILE_OWNER = "owner" + const val FILE_SPACE_ID = "spaceId" } } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt index 2319bd31d74..1b51a1aba9d 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt @@ -3,7 +3,9 @@ * * @author David González Verdugo * @author Abel García de Prada - * Copyright (C) 2020 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -46,7 +48,6 @@ data class OCFile( val privateLink: String? = "", var storagePath: String? = null, var treeEtag: String? = "", - var availableOfflineStatus: AvailableOfflineStatus? = null, var lastSyncDateForData: Long? = 0, var lastSyncDateForProperties: Long? = 0, @@ -55,7 +56,8 @@ data class OCFile( var etagInConflict: String? = null, val fileIsDownloading: Boolean? = false, var sharedWithSharee: Boolean? = false, - var sharedByLink: Boolean = false + var sharedByLink: Boolean = false, + val spaceId: String? = null, ) : Parcelable { val fileName: String From 0488c95e798e2c7021f793b5acb6ead075172f7d Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Mon, 16 Jan 2023 17:56:53 +0100 Subject: [PATCH 039/152] Retrieve web dav URL for space --- .../data/files/repository/OCFileRepository.kt | 4 +++- .../spaces/datasources/LocalSpacesDataSource.kt | 3 ++- .../implementation/OCLocalSpacesDataSource.kt | 6 +++++- .../owncloud/android/data/spaces/db/SpacesDao.kt | 16 +++++++++++++++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index d35738f639b..f966204e72c 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -5,7 +5,7 @@ * @author Christian Schabesberger * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -223,6 +223,8 @@ class OCFileRepository( override fun refreshFolder(remotePath: String, accountName: String): List { val currentSyncTime = System.currentTimeMillis() + val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(spaceId, accountName) + // Retrieve remote folder data val fetchFolderResult = remoteFileDataSource.refreshFolder(remotePath, accountName) val remoteFolder = fetchFolderResult.first() diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt index 98cfdbd71c9..146eeccfaa4 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt @@ -4,7 +4,7 @@ * @author Abel García de Prada * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -27,5 +27,6 @@ import kotlinx.coroutines.flow.Flow interface LocalSpacesDataSource { fun saveSpacesForAccount(listOfSpaces: List) fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> + fun getWebDavUrlForSpace(spaceId: String?, accountName: String): String? fun deleteSpacesForAccount(accountName: String) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 5be4cb5c86d..5ce837bc352 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -4,7 +4,7 @@ * @author Abel García de Prada * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -67,6 +67,10 @@ class OCLocalSpacesDataSource( } } + override fun getWebDavUrlForSpace(spaceId: String?, accountName: String): String? { + return spacesDao.getWebDavUrlForSpace(spaceId, accountName) + } + override fun deleteSpacesForAccount(accountName: String) { spacesDao.deleteSpacesForAccount(accountName) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index c9ebf2909d7..45e76bf8bbb 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -4,7 +4,7 @@ * @author Abel García de Prada * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -32,6 +32,7 @@ import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.DRIVE_TYPE_PRO import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ACCOUNT_NAME import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_DRIVE_TYPE import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID +import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ROOT_WEB_DAV_URL import kotlinx.coroutines.flow.Flow @Dao @@ -86,6 +87,12 @@ interface SpacesDao { accountName: String, ): Flow> + @Query(SELECT_WEB_DAV_URL_FOR_SPACE) + fun getWebDavUrlForSpace( + spaceId: String?, + accountName: String, + ): String? + @Query(DELETE_ALL_SPACES_FOR_ACCOUNT) fun deleteSpacesForAccount(accountName: String) @@ -112,6 +119,13 @@ interface SpacesDao { ORDER BY name COLLATE NOCASE ASC """ + // TODO: Use it for personal space too (remove last AND condition) + private const val SELECT_WEB_DAV_URL_FOR_SPACE = """ + SELECT $SPACES_ROOT_WEB_DAV_URL + FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} + WHERE $SPACES_ID = :spaceId AND $SPACES_ACCOUNT_NAME = :accountName AND $SPACES_DRIVE_TYPE NOT LIKE '$DRIVE_TYPE_PERSONAL' + """ + private const val DELETE_ALL_SPACES_FOR_ACCOUNT = """ DELETE FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} From 1942d817796e1d9919edeff1fb653d6dfacd7e81 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 17 Jan 2023 08:49:34 +0100 Subject: [PATCH 040/152] Fix unit tests --- .../android/data/file/repository/OCFileRepositoryTest.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/owncloudData/src/test/java/com/owncloud/android/data/file/repository/OCFileRepositoryTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/file/repository/OCFileRepositoryTest.kt index 70733da902b..a0cf7b12126 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/file/repository/OCFileRepositoryTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/file/repository/OCFileRepositoryTest.kt @@ -22,6 +22,7 @@ package com.owncloud.android.data.file.repository import com.owncloud.android.data.files.datasources.LocalFileDataSource import com.owncloud.android.data.files.datasources.RemoteFileDataSource import com.owncloud.android.data.files.repository.OCFileRepository +import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource import com.owncloud.android.data.storage.LocalStorageProvider import com.owncloud.android.domain.exceptions.FileNotFoundException import com.owncloud.android.domain.exceptions.NoConnectionWithServerException @@ -41,8 +42,9 @@ class OCFileRepositoryTest { private val remoteFileDataSource = mockk(relaxed = true) private val localFileDataSource = mockk(relaxed = true) + private val localSpacesDataSource = mockk(relaxed = true) private val localStorageProvider = mockk() - private val ocFileRepository: OCFileRepository = OCFileRepository(localFileDataSource, remoteFileDataSource, localStorageProvider) + private val ocFileRepository: OCFileRepository = OCFileRepository(localFileDataSource, remoteFileDataSource, localSpacesDataSource, localStorageProvider) private val folderToFetch = OC_FOLDER private val listOfFilesRetrieved = listOf( From 652ff9947d46f52444870fb1a77f7b64244ea36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 17 Jan 2023 13:01:11 +0100 Subject: [PATCH 041/152] Added space id as parameter to refresh folder operation so we can retrieve the webdav url we need to ask --- .../documentsprovider/DocumentsStorageProvider.kt | 3 ++- .../presentation/files/filelist/MainFileListViewModel.kt | 1 + .../files/operations/FileOperationsViewModel.kt | 2 ++ .../com/owncloud/android/syncadapter/FileSyncAdapter.java | 5 ++++- .../owncloud/android/ui/ReceiveExternalFilesViewModel.kt | 1 + .../owncloud/android/ui/helpers/FileOperationsHelper.java | 1 + .../usecases/synchronization/SynchronizeFolderUseCase.kt | 8 +++++++- .../android/workers/AvailableOfflinePeriodicWorker.kt | 2 ++ .../datasources/implementation/OCLocalFileDataSource.kt | 3 ++- 9 files changed, 22 insertions(+), 4 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index 5007a40fe66..83f03f680fd 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -404,7 +404,8 @@ class DocumentsStorageProvider : DocumentsProvider() { val synchronizeFolderUseCaseParams = SynchronizeFolderUseCase.Params( remotePath = folderToSync.remotePath, accountName = folderToSync.owner, - SynchronizeFolderUseCase.SyncFolderMode.REFRESH_FOLDER, + spaceId = folderToSync.spaceId, + syncMode = SynchronizeFolderUseCase.SyncFolderMode.REFRESH_FOLDER, ) CoroutineScope(Dispatchers.IO).launch { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt index b74845bd5a8..ab315355414 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt @@ -112,6 +112,7 @@ class MainFileListViewModel( SynchronizeFolderUseCase.Params( remotePath = initialFolderToDisplay.remotePath, accountName = initialFolderToDisplay.owner, + spaceId = initialFolderToDisplay.spaceId, syncMode = SYNC_CONTENTS, ) ) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/operations/FileOperationsViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/operations/FileOperationsViewModel.kt index 2f201d5a57d..00f61552071 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/operations/FileOperationsViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/operations/FileOperationsViewModel.kt @@ -162,6 +162,7 @@ class FileOperationsViewModel( useCaseParams = SynchronizeFolderUseCase.Params( remotePath = fileOperation.folderToSync.remotePath, accountName = fileOperation.folderToSync.owner, + spaceId = fileOperation.folderToSync.spaceId, syncMode = SynchronizeFolderUseCase.SyncFolderMode.SYNC_FOLDER_RECURSIVELY ) ) @@ -176,6 +177,7 @@ class FileOperationsViewModel( useCaseParams = SynchronizeFolderUseCase.Params( remotePath = fileOperation.folderToRefresh.remotePath, accountName = fileOperation.folderToRefresh.owner, + spaceId = fileOperation.folderToRefresh.spaceId, syncMode = if (fileOperation.shouldSyncContents) SynchronizeFolderUseCase.SyncFolderMode.SYNC_CONTENTS else SynchronizeFolderUseCase.SyncFolderMode.REFRESH_FOLDER ) ) diff --git a/owncloudApp/src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java b/owncloudApp/src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java index ec78ec1475d..dd32b8c04cb 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/owncloudApp/src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -227,7 +227,10 @@ private void synchronizeFolder(OCFile folder) { // Discover full account @NotNull Lazy synchronizeFolderUseCase = inject(SynchronizeFolderUseCase.class); - SynchronizeFolderUseCase.Params params = new SynchronizeFolderUseCase.Params(folder.getRemotePath(), folder.getOwner(), + SynchronizeFolderUseCase.Params params = new SynchronizeFolderUseCase.Params( + folder.getRemotePath(), + folder.getOwner(), + folder.getSpaceId(), SynchronizeFolderUseCase.SyncFolderMode.REFRESH_FOLDER_RECURSIVELY); UseCaseResult useCaseResult; diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/ReceiveExternalFilesViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/ReceiveExternalFilesViewModel.kt index 523bd404570..24c62e70368 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/ReceiveExternalFilesViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/ReceiveExternalFilesViewModel.kt @@ -45,6 +45,7 @@ class ReceiveExternalFilesViewModel( useCaseParams = SynchronizeFolderUseCase.Params( accountName = folderToSync.owner, remotePath = folderToSync.remotePath, + spaceId = folderToSync.spaceId, syncMode = SynchronizeFolderUseCase.SyncFolderMode.REFRESH_FOLDER ) ) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 98e3977d1d3..45ee51ad9b6 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -190,6 +190,7 @@ public void syncFile(OCFile file) { new SynchronizeFolderUseCase.Params( file.getRemotePath(), file.getOwner(), + file.getSpaceId(), SynchronizeFolderUseCase.SyncFolderMode.SYNC_FOLDER_RECURSIVELY) ); } diff --git a/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFolderUseCase.kt b/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFolderUseCase.kt index efe1f430a72..bdb5ac8f4ab 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFolderUseCase.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFolderUseCase.kt @@ -36,7 +36,11 @@ class SynchronizeFolderUseCase( val remotePath = params.remotePath val accountName = params.accountName - val folderContent = fileRepository.refreshFolder(remotePath, accountName) + val folderContent = fileRepository.refreshFolder( + remotePath = remotePath, + accountName = accountName, + spaceId = params.spaceId + ) folderContent.forEach { ocFile -> if (ocFile.isFolder) { @@ -45,6 +49,7 @@ class SynchronizeFolderUseCase( Params( remotePath = ocFile.remotePath, accountName = accountName, + spaceId = ocFile.spaceId, syncMode = params.syncMode, ) ) @@ -68,6 +73,7 @@ class SynchronizeFolderUseCase( data class Params( val remotePath: String, val accountName: String, + val spaceId: String? = null, val syncMode: SyncFolderMode, ) diff --git a/owncloudApp/src/main/java/com/owncloud/android/workers/AvailableOfflinePeriodicWorker.kt b/owncloudApp/src/main/java/com/owncloud/android/workers/AvailableOfflinePeriodicWorker.kt index c000173cf2e..6844d110bc2 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/workers/AvailableOfflinePeriodicWorker.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/workers/AvailableOfflinePeriodicWorker.kt @@ -63,6 +63,7 @@ class AvailableOfflinePeriodicWorker( SynchronizeFolderUseCase.Params( remotePath = it.remotePath, accountName = it.owner, + spaceId = it.spaceId, syncMode = SynchronizeFolderUseCase.SyncFolderMode.SYNC_FOLDER_RECURSIVELY ) ) @@ -71,6 +72,7 @@ class AvailableOfflinePeriodicWorker( } } } + companion object { const val AVAILABLE_OFFLINE_PERIODIC_WORKER = "AVAILABLE_OFFLINE_PERIODIC_WORKER" const val repeatInterval: Long = 15L diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt index cf8a484a364..f43992c12ec 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt @@ -223,7 +223,8 @@ class OCLocalFileDataSource( lastSyncDateForProperties = lastSyncDateForProperties, modifiedAtLastSyncForData = modifiedAtLastSyncForData, etagInConflict = etagInConflict, - treeEtag = treeEtag + treeEtag = treeEtag, + spaceId = spaceId, ) @VisibleForTesting From 7964c3bb287e365eef405175005b4f282bbcb64c Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 17 Jan 2023 16:22:37 +0100 Subject: [PATCH 042/152] Include navigation from space card to MainFileListFragment --- .../android/presentation/spaces/SpacesListAdapter.kt | 8 +++++++- .../android/presentation/spaces/SpacesListFragment.kt | 8 +++++++- .../owncloud/android/ui/activity/FileDisplayActivity.kt | 2 +- owncloudApp/src/main/res/layout/spaces_list_item.xml | 4 +++- owncloudApp/src/main/res/values-h640dp/dims.xml | 2 +- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt index c1c57cc6577..bdcd263ec09 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -33,7 +33,9 @@ import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.presentation.authentication.AccountUtils import com.owncloud.android.utils.PreferenceUtils -class SpacesListAdapter : RecyclerView.Adapter() { +class SpacesListAdapter( + val browseSpace: () -> Unit, +) : RecyclerView.Adapter() { private val spacesList = mutableListOf() @@ -48,6 +50,10 @@ class SpacesListAdapter : RecyclerView.Adapter() { spacesViewHolder.binding.apply { val space = spacesList[position] + spacesListItemCard.setOnClickListener { + browseSpace() + } + spacesListItemName.text = space.name spacesListItemSubtitle.text = space.description diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index 05b5d461f63..27b840bdc43 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -36,6 +36,7 @@ import com.owncloud.android.extensions.showErrorInSnackbar import com.owncloud.android.extensions.toDrawableRes import com.owncloud.android.extensions.toSubtitleStringRes import com.owncloud.android.extensions.toTitleStringRes +import com.owncloud.android.ui.activity.FileDisplayActivity import org.koin.androidx.viewmodel.ext.android.viewModel class SpacesListFragment : Fragment() { @@ -63,7 +64,12 @@ class SpacesListFragment : Fragment() { private fun initViews() { val spacesListLayoutManager = GridLayoutManager(requireContext(), 2) binding.recyclerSpacesList.layoutManager = spacesListLayoutManager - spacesListAdapter = SpacesListAdapter() + spacesListAdapter = SpacesListAdapter( + browseSpace = { + val parentActivity = requireActivity() as FileDisplayActivity + parentActivity.initAndShowListOfFiles() + } + ) binding.recyclerSpacesList.adapter = spacesListAdapter binding.swipeRefreshSpacesList.setOnRefreshListener { diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 0e9c26ba59f..ea123dd86d4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -326,7 +326,7 @@ class FileDisplayActivity : FileActivity(), } } - private fun initAndShowListOfFiles(fileListOption: FileListOption = FileListOption.ALL_FILES) { + fun initAndShowListOfFiles(fileListOption: FileListOption = FileListOption.ALL_FILES) { val mainListOfFiles = MainFileListFragment.newInstance( accountName = account.name, initialFolderToDisplay = file, diff --git a/owncloudApp/src/main/res/layout/spaces_list_item.xml b/owncloudApp/src/main/res/layout/spaces_list_item.xml index b91d2b55ca8..da2f565dbfc 100644 --- a/owncloudApp/src/main/res/layout/spaces_list_item.xml +++ b/owncloudApp/src/main/res/layout/spaces_list_item.xml @@ -2,7 +2,7 @@ ~ ownCloud Android client application ~ ~ @author Juan Carlos Garrote Gascón - ~ Copyright (C) 2022 ownCloud GmbH. + ~ Copyright (C) 2023 ownCloud GmbH. ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License version 2, @@ -28,8 +28,10 @@ android:orientation="vertical"> diff --git a/owncloudApp/src/main/res/values-h640dp/dims.xml b/owncloudApp/src/main/res/values-h640dp/dims.xml index cc53084f8a4..810feae2f21 100644 --- a/owncloudApp/src/main/res/values-h640dp/dims.xml +++ b/owncloudApp/src/main/res/values-h640dp/dims.xml @@ -25,5 +25,5 @@ 8dp - 192dp + 172dp From eef7c6b41c4112fe450dc7b41c23f01096495b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 17 Jan 2023 16:26:00 +0100 Subject: [PATCH 043/152] Fix remote path. Not it is parsed properly to support spaces --- .../android/data/files/repository/OCFileRepository.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index f966204e72c..838f0202a4f 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -226,7 +226,10 @@ class OCFileRepository( val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(spaceId, accountName) // Retrieve remote folder data - val fetchFolderResult = remoteFileDataSource.refreshFolder(remotePath, accountName) + + val fetchFolderResult = remoteFileDataSource.refreshFolder(remotePath, accountName, spaceWebDavUrl).map { + it.copy(spaceId = spaceId) + } val remoteFolder = fetchFolderResult.first() val remoteFolderContent = fetchFolderResult.drop(1) From 12921ee90774c44d8c819e3055389b55300cca43 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 17 Jan 2023 18:17:14 +0100 Subject: [PATCH 044/152] Added UI for space header in file list --- .../res/layout/main_file_list_fragment.xml | 18 ++++- .../src/main/res/layout/space_header.xml | 78 +++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 owncloudApp/src/main/res/layout/space_header.xml diff --git a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml index d109707025b..08dbe5cf13f 100644 --- a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml +++ b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml @@ -1,8 +1,11 @@ - + + + + + + + + + + + From 6e8d66955540f007d795a90c4f7d1a665be2d107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 17 Jan 2023 18:56:33 +0100 Subject: [PATCH 045/152] Add support to reading specific files from a space --- .../usecases/synchronization/SynchronizeFileUseCase.kt | 6 +++++- .../data/files/datasources/RemoteFileDataSource.kt | 1 + .../datasources/implementation/OCRemoteFileDataSource.kt | 4 +++- .../android/data/files/repository/OCFileRepository.kt | 8 +++++--- .../com/owncloud/android/domain/files/FileRepository.kt | 4 ++-- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFileUseCase.kt b/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFileUseCase.kt index 12623c94604..65c3fa0bf7f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFileUseCase.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFileUseCase.kt @@ -45,7 +45,11 @@ class SynchronizeFileUseCase( CoroutineScope(Dispatchers.IO).run { // 1. Perform a propfind to check if the file still exists in remote val serverFile = try { - fileRepository.readFile(fileToSynchronize.remotePath, fileToSynchronize.owner) + fileRepository.readFile( + remotePath = fileToSynchronize.remotePath, + accountName = fileToSynchronize.owner, + spaceId = fileToSynchronize.spaceId + ) } catch (exception: FileNotFoundException) { // 1.1 File not exists anymore -> remove file locally (DB and Storage) fileRepository.deleteFiles(listOf(fileToSynchronize), false) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index 93a4023fde8..b8a550457bc 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -57,6 +57,7 @@ interface RemoteFileDataSource { fun readFile( remotePath: String, accountName: String, + spaceWebDavUrl: String? = null, ): OCFile fun refreshFolder( diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index e6d6a50192f..5eabd3da44f 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -121,9 +121,11 @@ class OCRemoteFileDataSource( override fun readFile( remotePath: String, accountName: String, + spaceWebDavUrl: String?, ): OCFile = executeRemoteOperation { clientManager.getFileService(accountName).readFile( - remotePath = remotePath + remotePath = remotePath, + spaceWebDavUrl = spaceWebDavUrl, ) }.toModel() diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 838f0202a4f..67cd4fd2214 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -216,11 +216,13 @@ class OCFileRepository( } } - override fun readFile(remotePath: String, accountName: String): OCFile { - return remoteFileDataSource.readFile(remotePath, accountName) + override fun readFile(remotePath: String, accountName: String, spaceId: String?): OCFile { + val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(spaceId, accountName) + + return remoteFileDataSource.readFile(remotePath, accountName, spaceWebDavUrl).copy(spaceId = spaceId) } - override fun refreshFolder(remotePath: String, accountName: String): List { + override fun refreshFolder(remotePath: String, accountName: String, spaceId: String?): List { val currentSyncTime = System.currentTimeMillis() val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(spaceId, accountName) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt index cba04af59c7..945b590a2e4 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt @@ -45,8 +45,8 @@ interface FileRepository { fun getFilesAvailableOfflineFromAccount(owner: String): List fun getFilesAvailableOfflineFromEveryAccount(): List fun moveFile(listOfFilesToMove: List, targetFile: OCFile) - fun readFile(remotePath: String, accountName: String): OCFile - fun refreshFolder(remotePath: String, accountName: String): List + fun readFile(remotePath: String, accountName: String, spaceId: String? = null): OCFile + fun refreshFolder(remotePath: String, accountName: String, spaceId: String? = null): List fun deleteFiles(listOfFilesToDelete: List, removeOnlyLocalCopy: Boolean) fun renameFile(ocFile: OCFile, newName: String) fun saveFile(file: OCFile) From 10d6931c9ed62f47ac14ad4e592b1dffe0148f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 17 Jan 2023 19:08:42 +0100 Subject: [PATCH 046/152] Create folder operation support for specific spaces --- .../data/files/datasources/RemoteFileDataSource.kt | 1 + .../datasources/implementation/OCRemoteFileDataSource.kt | 4 +++- .../android/data/files/repository/OCFileRepository.kt | 8 ++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index b8a550457bc..5a002cd7511 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -41,6 +41,7 @@ interface RemoteFileDataSource { createFullPath: Boolean, isChunksFolder: Boolean, accountName: String, + spaceWebDavUrl: String?, ) fun getAvailableRemotePath( diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index 5eabd3da44f..11493a674ed 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -57,11 +57,13 @@ class OCRemoteFileDataSource( createFullPath: Boolean, isChunksFolder: Boolean, accountName: String, + spaceWebDavUrl: String?, ) = executeRemoteOperation { clientManager.getFileService(accountName).createFolder( remotePath = remotePath, createFullPath = createFullPath, - isChunkFolder = isChunksFolder + isChunkFolder = isChunksFolder, + spaceWebDavUrl = spaceWebDavUrl, ) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 67cd4fd2214..0f506200f0e 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -52,13 +52,16 @@ class OCFileRepository( override fun createFolder( remotePath: String, - parentFolder: OCFile + parentFolder: OCFile, ) { + val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(parentFolder.spaceId, parentFolder.owner) + remoteFileDataSource.createFolder( remotePath = remotePath, createFullPath = false, isChunksFolder = false, accountName = parentFolder.owner, + spaceWebDavUrl = spaceWebDavUrl, ).also { localFileDataSource.saveFilesInFolderAndReturnThem( folder = parentFolder, @@ -68,7 +71,8 @@ class OCFileRepository( owner = parentFolder.owner, modificationTimestamp = System.currentTimeMillis(), length = 0, - mimeType = MIME_DIR + mimeType = MIME_DIR, + spaceId = parentFolder.spaceId, ) ) ) From f3a5918b1629e927e36a2d54315ddf030ff926fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Wed, 18 Jan 2023 13:15:56 +0100 Subject: [PATCH 047/152] Support removal of files from specific spaces --- .../data/files/datasources/RemoteFileDataSource.kt | 1 + .../implementation/OCRemoteFileDataSource.kt | 4 +++- .../android/data/files/repository/OCFileRepository.kt | 11 ++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index 5a002cd7511..71e16cf2d03 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -69,6 +69,7 @@ interface RemoteFileDataSource { fun deleteFile( remotePath: String, accountName: String, + spaceWebDavUrl: String? = null, ) fun renameFile( diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index 11493a674ed..48419964973 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -147,9 +147,11 @@ class OCRemoteFileDataSource( override fun deleteFile( remotePath: String, accountName: String, + spaceWebDavUrl: String?, ) = executeRemoteOperation { clientManager.getFileService(accountName).removeFile( - remotePath = remotePath + remotePath = remotePath, + spaceWebDavUrl = spaceWebDavUrl, ) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 0f506200f0e..3c02c70e16c 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -321,10 +321,19 @@ class OCFileRepository( } override fun deleteFiles(listOfFilesToDelete: List, removeOnlyLocalCopy: Boolean) { + val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace( + spaceId = listOfFilesToDelete.first().spaceId, + accountName = listOfFilesToDelete.first().owner, + ) + listOfFilesToDelete.forEach { ocFile -> if (!removeOnlyLocalCopy) { try { - remoteFileDataSource.deleteFile(remotePath = ocFile.remotePath, accountName = ocFile.owner) + remoteFileDataSource.deleteFile( + remotePath = ocFile.remotePath, + accountName = ocFile.owner, + spaceWebDavUrl = spaceWebDavUrl, + ) } catch (fileNotFoundException: FileNotFoundException) { Timber.i("File ${ocFile.fileName} was not found in server. Let's remove it from local storage") } From 260d1bbdf2a24a6f0be38784d87fdc8b53599aff Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 18 Jan 2023 14:55:13 +0100 Subject: [PATCH 048/152] Added space to the UI state of the main file list --- .../dependecyinjection/UseCaseModule.kt | 4 +- .../dependecyinjection/ViewModelModule.kt | 6 +-- .../files/filelist/MainFileListFragment.kt | 14 +++-- .../files/filelist/MainFileListViewModel.kt | 52 +++++++++++++------ .../ui/activity/FileDisplayActivity.kt | 1 - .../ui/activity/FolderPickerActivity.java | 2 +- .../res/layout/main_file_list_fragment.xml | 33 +++++++----- .../datasources/LocalSpacesDataSource.kt | 1 + .../implementation/OCLocalSpacesDataSource.kt | 4 ++ .../android/data/spaces/db/SpacesDao.kt | 12 +++++ .../spaces/repository/OCSpacesRepository.kt | 5 +- .../android/domain/spaces/SpacesRepository.kt | 3 +- ...tSpaceWithSpecialsByIdForAccountUseCase.kt | 40 ++++++++++++++ 13 files changed, 136 insertions(+), 41 deletions(-) create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetSpaceWithSpecialsByIdForAccountUseCase.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index a55a861fe4b..6027303b166 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -5,7 +5,7 @@ * @author Abel García de Prada * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -78,6 +78,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUse import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase +import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAccountUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase import com.owncloud.android.domain.transfers.usecases.ClearSuccessfulTransfersUseCase import com.owncloud.android.domain.transfers.usecases.GetAllTransfersAsLiveDataUseCase @@ -177,6 +178,7 @@ val useCaseModule = module { // Spaces factory { RefreshSpacesFromServerAsyncUseCase(get()) } factory { GetProjectSpacesWithSpecialsForAccountAsStreamUseCase(get()) } + factory { GetSpaceWithSpecialsByIdForAccountUseCase(get()) } // Transfers factory { CancelDownloadForFileUseCase(get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index 2ced99e2cdd..0af292d10da 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -6,7 +6,7 @@ * @author Juan Carlos Garrote Gascón * @author David Crespo Ríos * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -93,7 +93,7 @@ val viewModelModule = module { viewModel { PreviewImageViewModel(get(), get(), get()) } viewModel { FileOperationsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } - viewModel { (accountName: String, initialFolderToDisplay: OCFile, fileListOption: FileListOption) -> + viewModel { (initialFolderToDisplay: OCFile, fileListOption: FileListOption) -> MainFileListViewModel( get(), get(), @@ -104,7 +104,7 @@ val viewModelModule = module { get(), get(), get(), - accountName, + get(), initialFolderToDisplay, fileListOption, ) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index fd73df396ac..c2684b1d87b 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -3,7 +3,9 @@ * * @author Fernando Sanz Velasco * @author Jose Antonio Barros Ramos - * Copyright (C) 2022 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -92,7 +94,6 @@ class MainFileListFragment : Fragment(), private val mainFileListViewModel by viewModel { parametersOf( - requireArguments().getString(ARG_ACCOUNT_NAME), requireArguments().getParcelable(ARG_INITIAL_FOLDER_TO_DISPLAY), requireArguments().getParcelable(ARG_FILE_LIST_OPTION), ) @@ -226,6 +227,12 @@ class MainFileListFragment : Fragment(), fileListAdapter.updateFileList(filesToAdd = fileListUiState.folderContent, fileListOption = fileListUiState.fileListOption) showOrHideEmptyView(fileListUiState) + fileListUiState.space?.let { + binding.spaceHeader.spaceHeaderName.text = it.name + binding.spaceHeader.spaceHeaderSubtitle.text = it.description + } + + actionMode?.invalidate() } @@ -751,7 +758,6 @@ class MainFileListFragment : Fragment(), companion object { val ARG_PICKING_A_FOLDER = "${MainFileListFragment::class.java.canonicalName}.ARG_PICKING_A_FOLDER}" - val ARG_ACCOUNT_NAME = "${MainFileListFragment::class.java.canonicalName}.ARG_ACCOUNT_NAME}" val ARG_INITIAL_FOLDER_TO_DISPLAY = "${MainFileListFragment::class.java.canonicalName}.ARG_INITIAL_FOLDER_TO_DISPLAY}" val ARG_FILE_LIST_OPTION = "${MainFileListFragment::class.java.canonicalName}.FILE_LIST_OPTION}" @@ -759,13 +765,11 @@ class MainFileListFragment : Fragment(), @JvmStatic fun newInstance( - accountName: String, initialFolderToDisplay: OCFile, pickingAFolder: Boolean = false, fileListOption: FileListOption = FileListOption.ALL_FILES, ): MainFileListFragment { val args = Bundle() - args.putString(ARG_ACCOUNT_NAME, accountName) args.putParcelable(ARG_INITIAL_FOLDER_TO_DISPLAY, initialFolderToDisplay) args.putBoolean(ARG_PICKING_A_FOLDER, pickingAFolder) args.putParcelable(ARG_FILE_LIST_OPTION, fileListOption) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt index ab315355414..55cc2117e45 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt @@ -3,7 +3,9 @@ * * @author Fernando Sanz Velasco * @author Jose Antonio Barros Ramos - * Copyright (C) 2021 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -35,6 +37,8 @@ import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase import com.owncloud.android.domain.files.usecases.GetFolderContentAsStreamUseCase import com.owncloud.android.domain.files.usecases.GetSharedByLinkForAccountAsStreamUseCase import com.owncloud.android.domain.files.usecases.SortFilesWithSyncInfoUseCase +import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAccountUseCase import com.owncloud.android.presentation.files.SortOrder import com.owncloud.android.presentation.files.SortOrder.Companion.PREF_FILE_LIST_SORT_ORDER import com.owncloud.android.presentation.files.SortType @@ -61,38 +65,38 @@ class MainFileListViewModel( private val getFilesAvailableOfflineFromAccountAsStreamUseCase: GetFilesAvailableOfflineFromAccountAsStreamUseCase, private val getFileByIdUseCase: GetFileByIdUseCase, private val getFileByRemotePathUseCase: GetFileByRemotePathUseCase, + private val getSpaceWithSpecialsByIdForAccountUseCase: GetSpaceWithSpecialsByIdForAccountUseCase, private val sortFilesWithSyncInfoUseCase: SortFilesWithSyncInfoUseCase, private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, private val sharedPreferencesProvider: SharedPreferencesProvider, private val synchronizeFolderUseCase: SynchronizeFolderUseCase, - accountNameParam: String, initialFolderToDisplay: OCFile, fileListOptionParam: FileListOption, ) : ViewModel() { private val showHiddenFiles: Boolean = sharedPreferencesProvider.getBoolean(PREF_SHOW_HIDDEN_FILES, false) - private val accountName: MutableStateFlow = MutableStateFlow(accountNameParam) val currentFolderDisplayed: MutableStateFlow = MutableStateFlow(initialFolderToDisplay) val fileListOption: MutableStateFlow = MutableStateFlow(fileListOptionParam) private val searchFilter: MutableStateFlow = MutableStateFlow("") private val sortTypeAndOrder = MutableStateFlow(Pair(SortType.SORT_TYPE_BY_NAME, SortOrder.SORT_ORDER_ASCENDING)) + private val space: MutableStateFlow = MutableStateFlow(null) /** File list ui state combines the other fields and generate a new state whenever any of them changes */ val fileListUiState: StateFlow = combine( currentFolderDisplayed, - accountName, fileListOption, searchFilter, sortTypeAndOrder, - ) { currentFolderDisplayed, accountName, fileListOption, searchFilter, sortTypeAndOrder -> + space, + ) { currentFolderDisplayed, fileListOption, searchFilter, sortTypeAndOrder, space -> composeFileListUiStateForThisParams( currentFolderDisplayed = currentFolderDisplayed, - accountName = accountName, fileListOption = fileListOption, searchFilter = searchFilter, sortTypeAndOrder = sortTypeAndOrder, + space = space, ) } .flatMapLatest { it } @@ -107,6 +111,7 @@ class MainFileListViewModel( val sortOrderSelected = SortOrder.values()[sharedPreferencesProvider.getInt(PREF_FILE_LIST_SORT_ORDER, SortOrder.SORT_ORDER_ASCENDING.ordinal)] sortTypeAndOrder.update { Pair(sortTypeSelected, sortOrderSelected) } + updateSpace() viewModelScope.launch(coroutinesDispatcherProvider.io) { synchronizeFolderUseCase.execute( SynchronizeFolderUseCase.Params( @@ -208,6 +213,7 @@ class MainFileListViewModel( fun updateFolderToDisplay(newFolderToDisplay: OCFile) { currentFolderDisplayed.update { newFolderToDisplay } searchFilter.update { "" } + updateSpace() } fun updateSearchFilter(newSearchFilter: String) { @@ -224,24 +230,40 @@ class MainFileListViewModel( sortTypeAndOrder.update { Pair(sortType, sortOrder) } } + private fun updateSpace() { + val folderToDisplay = currentFolderDisplayed.value + viewModelScope.launch(coroutinesDispatcherProvider.io) { + if (folderToDisplay.remotePath == ROOT_PATH) { + val currentSpace = getSpaceWithSpecialsByIdForAccountUseCase.execute( + GetSpaceWithSpecialsByIdForAccountUseCase.Params( + spaceId = folderToDisplay.spaceId, + accountName = folderToDisplay.owner, + ) + ) + space.update { currentSpace } + } + } + + } + private fun composeFileListUiStateForThisParams( currentFolderDisplayed: OCFile, - accountName: String, fileListOption: FileListOption, searchFilter: String?, - sortTypeAndOrder: Pair + sortTypeAndOrder: Pair, + space: OCSpace?, ): Flow = when (fileListOption) { - FileListOption.ALL_FILES -> retrieveFlowForAllFiles(currentFolderDisplayed, accountName) - FileListOption.SHARED_BY_LINK -> retrieveFlowForShareByLink(currentFolderDisplayed, accountName) - FileListOption.AV_OFFLINE -> retrieveFlowForAvailableOffline(currentFolderDisplayed, accountName) + FileListOption.ALL_FILES -> retrieveFlowForAllFiles(currentFolderDisplayed, currentFolderDisplayed.owner) + FileListOption.SHARED_BY_LINK -> retrieveFlowForShareByLink(currentFolderDisplayed, currentFolderDisplayed.owner) + FileListOption.AV_OFFLINE -> retrieveFlowForAvailableOffline(currentFolderDisplayed, currentFolderDisplayed.owner) FileListOption.SPACES_LIST -> TODO() }.toFileListUiState( currentFolderDisplayed, - accountName, fileListOption, searchFilter, sortTypeAndOrder, + space, ) private fun retrieveFlowForAllFiles( @@ -285,13 +307,12 @@ class MainFileListViewModel( private fun Flow>.toFileListUiState( currentFolderDisplayed: OCFile, - accountName: String, fileListOption: FileListOption, searchFilter: String?, sortTypeAndOrder: Pair, + space: OCSpace?, ) = this.map { folderContent -> FileListUiState.Success( - accountName = accountName, folderToDisplay = currentFolderDisplayed, folderContent = folderContent.filter { fileWithSyncInfo -> fileWithSyncInfo.file.fileName.contains( @@ -301,17 +322,18 @@ class MainFileListViewModel( }.let { sortList(it, sortTypeAndOrder) }, fileListOption = fileListOption, searchFilter = searchFilter, + space = space, ) } sealed interface FileListUiState { object Loading : FileListUiState data class Success( - val accountName: String, val folderToDisplay: OCFile?, val folderContent: List, val fileListOption: FileListOption, val searchFilter: String?, + val space: OCSpace?, ) : FileListUiState } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index ea123dd86d4..ad670f918d4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -328,7 +328,6 @@ class FileDisplayActivity : FileActivity(), fun initAndShowListOfFiles(fileListOption: FileListOption = FileListOption.ALL_FILES) { val mainListOfFiles = MainFileListFragment.newInstance( - accountName = account.name, initialFolderToDisplay = file, fileListOption = fileListOption, ).apply { diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java index c245fc873d0..78695cec862 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java @@ -127,7 +127,7 @@ private void initAndShowListOfFilesFragment() { safeInitialFolder = getFile(); } - MainFileListFragment mainListOfFiles = MainFileListFragment.newInstance(getAccount().name, safeInitialFolder, true, FileListOption.ALL_FILES); + MainFileListFragment mainListOfFiles = MainFileListFragment.newInstance(safeInitialFolder, true, FileListOption.ALL_FILES); mainListOfFiles.setFileActions(this); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.add(R.id.fragment_container, mainListOfFiles, TAG_LIST_OF_FOLDERS); diff --git a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml index 08dbe5cf13f..545998127b1 100644 --- a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml +++ b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml @@ -62,14 +62,6 @@ android:background="@drawable/shadow_gradient" fab:layout_constraintTop_toBottomOf="@id/options_layout" /> - - + app:layout_constraintTop_toBottomOf="@id/shadow_view"> - + android:orientation="vertical"> + + + + + + diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt index 146eeccfaa4..7fdc5bc31f3 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.Flow interface LocalSpacesDataSource { fun saveSpacesForAccount(listOfSpaces: List) fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> + fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace fun getWebDavUrlForSpace(spaceId: String?, accountName: String): String? fun deleteSpacesForAccount(accountName: String) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 5ce837bc352..f7131a90c51 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -67,6 +67,10 @@ class OCLocalSpacesDataSource( } } + override fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace { + return spacesDao.getSpaceWithSpecialsByIdForAccount(spaceId, accountName).toModel() + } + override fun getWebDavUrlForSpace(spaceId: String?, accountName: String): String? { return spacesDao.getWebDavUrlForSpace(spaceId, accountName) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index 45e76bf8bbb..661a9856318 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -87,6 +87,12 @@ interface SpacesDao { accountName: String, ): Flow> + @Query(SELECT_SPACE_BY_ID_FOR_ACCOUNT) + fun getSpaceWithSpecialsByIdForAccount( + spaceId: String?, + accountName: String, + ): SpacesWithSpecials + @Query(SELECT_WEB_DAV_URL_FOR_SPACE) fun getWebDavUrlForSpace( spaceId: String?, @@ -119,6 +125,12 @@ interface SpacesDao { ORDER BY name COLLATE NOCASE ASC """ + private const val SELECT_SPACE_BY_ID_FOR_ACCOUNT = """ + SELECT * + FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} + WHERE $SPACES_ID = :spaceId AND $SPACES_ACCOUNT_NAME = :accountName + """ + // TODO: Use it for personal space too (remove last AND condition) private const val SELECT_WEB_DAV_URL_FOR_SPACE = """ SELECT $SPACES_ROOT_WEB_DAV_URL diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt index 5afd4fe8e52..de77a3bd20e 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -4,7 +4,7 @@ * @author Abel García de Prada * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -37,4 +37,7 @@ class OCSpacesRepository( override fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String) = localSpacesDataSource.getProjectSpacesWithSpecialsForAccountAsFlow(accountName) + + override fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String) = + localSpacesDataSource.getSpaceWithSpecialsByIdForAccount(spaceId, accountName) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt index 9f9b480cd40..2596ef22394 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -4,7 +4,7 @@ * @author Abel García de Prada * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -27,4 +27,5 @@ import kotlinx.coroutines.flow.Flow interface SpacesRepository { fun refreshSpacesForAccount(accountName: String) fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> + fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetSpaceWithSpecialsByIdForAccountUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetSpaceWithSpecialsByIdForAccountUseCase.kt new file mode 100644 index 00000000000..c0a4e0dd398 --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetSpaceWithSpecialsByIdForAccountUseCase.kt @@ -0,0 +1,40 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.usecases + +import com.owncloud.android.domain.BaseUseCase +import com.owncloud.android.domain.spaces.SpacesRepository +import com.owncloud.android.domain.spaces.model.OCSpace + +class GetSpaceWithSpecialsByIdForAccountUseCase( + private val spacesRepository: SpacesRepository +) : BaseUseCase() { + + override fun run(params: Params): OCSpace? { + if (params.spaceId == null) return null + return spacesRepository.getSpaceWithSpecialsByIdForAccount(params.spaceId, params.accountName) + } + + data class Params( + val spaceId: String?, + val accountName: String, + ) +} From 257e3a016660f21c620617673faba0620568e75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Wed, 18 Jan 2023 18:34:11 +0100 Subject: [PATCH 049/152] Navigate to space folder root when clicking on a space --- .../dependecyinjection/ViewModelModule.kt | 2 +- .../presentation/spaces/SpacesListAdapter.kt | 8 ++++++-- .../presentation/spaces/SpacesListFragment.kt | 19 +++++++++++------- .../spaces/SpacesListViewModel.kt | 20 +++++++++++++++++++ .../files/datasources/LocalFileDataSource.kt | 2 +- .../implementation/OCLocalFileDataSource.kt | 7 ++++--- .../owncloud/android/data/files/db/FileDao.kt | 8 +++++--- .../data/files/repository/OCFileRepository.kt | 8 ++++---- .../android/domain/files/FileRepository.kt | 2 +- .../usecases/GetFileByRemotePathUseCase.kt | 6 ++++-- 10 files changed, 58 insertions(+), 24 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index 0af292d10da..e084fdc30d0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -113,5 +113,5 @@ val viewModelModule = module { viewModel { (ocFile: OCFile) -> ConflictsResolveViewModel(get(), get(), get(), get(), get(), ocFile) } viewModel { ReceiveExternalFilesViewModel(get(), get()) } viewModel { AccountsManagementViewModel(get()) } - viewModel { SpacesListViewModel(get(), get(), get(), get()) } + viewModel { SpacesListViewModel(get(), get(), get(), get(), get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt index bdcd263ec09..43260858d26 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -34,7 +34,7 @@ import com.owncloud.android.presentation.authentication.AccountUtils import com.owncloud.android.utils.PreferenceUtils class SpacesListAdapter( - val browseSpace: () -> Unit, + private val listener: SpacesListAdapterListener, ) : RecyclerView.Adapter() { private val spacesList = mutableListOf() @@ -51,7 +51,7 @@ class SpacesListAdapter( val space = spacesList[position] spacesListItemCard.setOnClickListener { - browseSpace() + listener.onItemClick(space) } spacesListItemName.text = space.name @@ -101,5 +101,9 @@ class SpacesListAdapter( fun getItem(position: Int) = spacesList[position] + interface SpacesListAdapterListener { + fun onItemClick(ocSpace: OCSpace) + } + class SpacesViewHolder(val binding: SpacesListItemBinding) : RecyclerView.ViewHolder(binding.root) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index 27b840bdc43..5a6b5959e7f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -39,7 +39,7 @@ import com.owncloud.android.extensions.toTitleStringRes import com.owncloud.android.ui.activity.FileDisplayActivity import org.koin.androidx.viewmodel.ext.android.viewModel -class SpacesListFragment : Fragment() { +class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment() { private var _binding: SpacesListFragmentBinding? = null private val binding get() = _binding!! @@ -64,12 +64,7 @@ class SpacesListFragment : Fragment() { private fun initViews() { val spacesListLayoutManager = GridLayoutManager(requireContext(), 2) binding.recyclerSpacesList.layoutManager = spacesListLayoutManager - spacesListAdapter = SpacesListAdapter( - browseSpace = { - val parentActivity = requireActivity() as FileDisplayActivity - parentActivity.initAndShowListOfFiles() - } - ) + spacesListAdapter = SpacesListAdapter(this) binding.recyclerSpacesList.adapter = spacesListAdapter binding.swipeRefreshSpacesList.setOnRefreshListener { @@ -86,6 +81,12 @@ class SpacesListFragment : Fragment() { spacesListAdapter.setData(onlyEnabledSpaces) binding.swipeRefreshSpacesList.isRefreshing = uiState.refreshing uiState.error?.let { showErrorInSnackbar(R.string.spaces_sync_failed, it) } + + uiState.rootFolderFromSelectedSpace?.let { + val parentActivity = requireActivity() as FileDisplayActivity + parentActivity.file = it + parentActivity.initAndShowListOfFiles() + } } } @@ -99,4 +100,8 @@ class SpacesListFragment : Fragment() { listEmptyDatasetSubTitle.setText(FileListOption.SPACES_LIST.toSubtitleStringRes()) } } + + override fun onItemClick(ocSpace: OCSpace) { + spacesListViewModel.getRootFileForSpace(ocSpace) + } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt index d2741445611..4d1a611d3e0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt @@ -24,6 +24,9 @@ import android.accounts.Account import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.owncloud.android.domain.UseCaseResult +import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH +import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase @@ -36,6 +39,7 @@ import kotlinx.coroutines.launch class SpacesListViewModel( private val refreshSpacesFromServerAsyncUseCase: RefreshSpacesFromServerAsyncUseCase, private val getProjectSpacesWithSpecialsForAccountAsStreamUseCase: GetProjectSpacesWithSpecialsForAccountAsStreamUseCase, + private val getFileByRemotePathUseCase: GetFileByRemotePathUseCase, private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, private val account: Account, ) : ViewModel() { @@ -65,8 +69,24 @@ class SpacesListViewModel( } } + fun getRootFileForSpace(ocSpace: OCSpace) { + viewModelScope.launch(coroutinesDispatcherProvider.io) { + val result = getFileByRemotePathUseCase.execute( + GetFileByRemotePathUseCase.Params( + owner = ocSpace.accountName, + remotePath = ROOT_PATH, + spaceId = ocSpace.id + ) + ) + result.getDataOrNull()?.let { rootFolderFromSpace -> + _spacesList.update { it.copy(rootFolderFromSelectedSpace = rootFolderFromSpace) } + } + } + } + data class SpacesListUiState( val spaces: List, + val rootFolderFromSelectedSpace: OCFile? = null, val refreshing: Boolean, val error: Throwable?, ) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt index a33e713411b..fb594467853 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt @@ -32,7 +32,7 @@ interface LocalFileDataSource { fun copyFile(sourceFile: OCFile, targetFolder: OCFile, finalRemotePath: String, remoteId: String) fun getFileById(fileId: Long): OCFile? fun getFileByIdAsFlow(fileId: Long): Flow - fun getFileByRemotePath(remotePath: String, owner: String): OCFile? + fun getFileByRemotePath(remotePath: String, owner: String, spaceId: String?): OCFile? fun getFileByRemoteId(remoteId: String): OCFile? fun getFolderContent(folderId: Long): List fun getSearchFolderContent(folderId: Long, search: String): List diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt index f43992c12ec..15d1a7a1af1 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt @@ -55,8 +55,8 @@ class OCLocalFileDataSource( override fun getFileByIdAsFlow(fileId: Long): Flow = fileDao.getFileByIdAsFlow(fileId).map { it?.toModel() } - override fun getFileByRemotePath(remotePath: String, owner: String): OCFile? { - fileDao.getFileByOwnerAndRemotePath(owner, remotePath)?.let { return it.toModel() } + override fun getFileByRemotePath(remotePath: String, owner: String, spaceId: String?): OCFile? { + fileDao.getFileByOwnerAndRemotePath(owner, remotePath, spaceId)?.let { return it.toModel() } // If root folder do not exists, create and return it. if (remotePath == ROOT_PATH) { @@ -66,7 +66,8 @@ class OCLocalFileDataSource( remotePath = ROOT_PATH, length = 0, mimeType = MIME_DIR, - modificationTimestamp = 0 + modificationTimestamp = 0, + spaceId = spaceId, ) fileDao.mergeRemoteAndLocalFile(rootFolder.toEntity()).also { return getFileById(it) } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt index 1d30f7c1d1b..76777ba29c8 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt @@ -59,7 +59,8 @@ interface FileDao { @Query(SELECT_FILE_FROM_OWNER_WITH_REMOTE_PATH) fun getFileByOwnerAndRemotePath( owner: String, - remotePath: String + remotePath: String, + spaceId: String?, ): OCFileEntity? @Query(SELECT_FILE_WITH_REMOTE_ID) @@ -215,7 +216,8 @@ interface FileDao { ): Long { val localFile: OCFileEntity? = getFileByOwnerAndRemotePath( owner = ocFileEntity.owner, - remotePath = ocFileEntity.remotePath + remotePath = ocFileEntity.remotePath, + ocFileEntity.spaceId, ) return if (localFile == null) { insertOrIgnore(ocFileEntity) @@ -468,7 +470,7 @@ interface FileDao { private const val SELECT_FILE_FROM_OWNER_WITH_REMOTE_PATH = """ SELECT * FROM ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME} - WHERE owner = :owner AND remotePath = :remotePath + WHERE owner = :owner AND remotePath = :remotePath AND spaceId IS :spaceId """ private const val DELETE_FILE_WITH_ID = """ diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 3c02c70e16c..68214595cdd 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -131,8 +131,8 @@ class OCFileRepository( override fun getFileByIdAsFlow(fileId: Long): Flow = localFileDataSource.getFileByIdAsFlow(fileId) - override fun getFileByRemotePath(remotePath: String, owner: String): OCFile? = - localFileDataSource.getFileByRemotePath(remotePath, owner) + override fun getFileByRemotePath(remotePath: String, owner: String, spaceId: String?): OCFile? = + localFileDataSource.getFileByRemotePath(remotePath, owner, spaceId) override fun getSearchFolderContent(fileListOption: FileListOption, folderId: Long, search: String): List = when (fileListOption) { @@ -244,7 +244,7 @@ class OCFileRepository( // Check if the folder already exists in database. val localFolderByRemotePath: OCFile? = - localFileDataSource.getFileByRemotePath(remotePath = remoteFolder.remotePath, owner = remoteFolder.owner) + localFileDataSource.getFileByRemotePath(remotePath = remoteFolder.remotePath, owner = remoteFolder.owner, spaceId = spaceId) // If folder doesn't exists in database, insert everything. Easy path if (localFolderByRemotePath == null) { @@ -358,7 +358,7 @@ class OCFileRepository( ) // 2. Check if file already exists in database - if (localFileDataSource.getFileByRemotePath(newRemotePath, ocFile.owner) != null) { + if (localFileDataSource.getFileByRemotePath(newRemotePath, ocFile.owner, ocFile.spaceId) != null) { throw FileAlreadyExistsException() } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt index 945b590a2e4..fa9e88e75ad 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt @@ -35,7 +35,7 @@ interface FileRepository { fun copyFile(listOfFilesToCopy: List, targetFolder: OCFile) fun getFileById(fileId: Long): OCFile? fun getFileByIdAsFlow(fileId: Long): Flow - fun getFileByRemotePath(remotePath: String, owner: String): OCFile? + fun getFileByRemotePath(remotePath: String, owner: String, spaceId: String? = null): OCFile? fun getSearchFolderContent(fileListOption: FileListOption, folderId: Long, search: String): List fun getFolderContent(folderId: Long): List fun getFolderContentWithSyncInfoAsFlow(folderId: Long): Flow> diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetFileByRemotePathUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetFileByRemotePathUseCase.kt index 88bef6729ac..6c614f84005 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetFileByRemotePathUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetFileByRemotePathUseCase.kt @@ -29,12 +29,14 @@ class GetFileByRemotePathUseCase( override fun run(params: Params): OCFile? = fileRepository.getFileByRemotePath( params.remotePath, - params.owner + params.owner, + params.spaceId, ) data class Params( val owner: String, - val remotePath: String + val remotePath: String, + val spaceId: String? = null, ) } From e7e924cd53e2961d742211842ced6ef2ed4c1ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Wed, 18 Jan 2023 18:39:26 +0100 Subject: [PATCH 050/152] Show space header on file list only when root folder for specific space is displayed --- .../android/presentation/files/filelist/MainFileListFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index c2684b1d87b..dfdb0d1ec92 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -48,6 +48,7 @@ import com.owncloud.android.R import com.owncloud.android.databinding.MainFileListFragmentBinding import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH import com.owncloud.android.domain.files.model.OCFileSyncInfo import com.owncloud.android.domain.files.model.OCFileWithSyncInfo import com.owncloud.android.domain.utils.Event @@ -227,6 +228,7 @@ class MainFileListFragment : Fragment(), fileListAdapter.updateFileList(filesToAdd = fileListUiState.folderContent, fileListOption = fileListUiState.fileListOption) showOrHideEmptyView(fileListUiState) + binding.spaceHeader.root.isVisible = fileListUiState.space != null && fileListUiState.folderToDisplay?.remotePath == ROOT_PATH fileListUiState.space?.let { binding.spaceHeader.spaceHeaderName.text = it.name binding.spaceHeader.spaceHeaderSubtitle.text = it.description From 794a890ec5c7b21f1123bbc50c9cb2fc25b2183e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Thu, 19 Jan 2023 08:21:28 +0100 Subject: [PATCH 051/152] Renaming files should work with spaces now --- .../dependecyinjection/RepositoryModule.kt | 2 +- .../files/datasources/RemoteFileDataSource.kt | 1 + .../implementation/OCRemoteFileDataSource.kt | 4 +++- .../data/files/repository/OCFileRepository.kt | 15 ++++++++++++--- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt index 96f7e630718..e5ebc726cd3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt @@ -51,7 +51,7 @@ import org.koin.dsl.module val repositoryModule = module { factory { OCAuthenticationRepository(get(), get()) } factory { OCCapabilityRepository(get(), get()) } - factory { OCFileRepository(get(), get(), get()) } + factory { OCFileRepository(get(), get(), get(), get()) } factory { OCServerInfoRepository(get()) } factory { OCShareRepository(get(), get()) } factory { OCShareeRepository(get()) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index 71e16cf2d03..71d523f5817 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -78,5 +78,6 @@ interface RemoteFileDataSource { newName: String, isFolder: Boolean, accountName: String, + spaceWebDavUrl: String? = null, ) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index 48419964973..652f166f619 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -161,12 +161,14 @@ class OCRemoteFileDataSource( newName: String, isFolder: Boolean, accountName: String, + spaceWebDavUrl: String?, ) = executeRemoteOperation { clientManager.getFileService(accountName).renameFile( oldName = oldName, oldRemotePath = oldRemotePath, newName = newName, - isFolder = isFolder + isFolder = isFolder, + spaceWebDavUrl = spaceWebDavUrl, ) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 68214595cdd..2b1b5094ed5 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -24,6 +24,7 @@ package com.owncloud.android.data.files.repository import com.owncloud.android.data.files.datasources.LocalFileDataSource import com.owncloud.android.data.files.datasources.RemoteFileDataSource +import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource import com.owncloud.android.data.storage.LocalStorageProvider import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT @@ -44,6 +45,7 @@ import java.util.UUID class OCFileRepository( private val localFileDataSource: LocalFileDataSource, private val remoteFileDataSource: RemoteFileDataSource, + private val localSpacesDataSource: LocalSpacesDataSource, private val localStorageProvider: LocalStorageProvider ) : FileRepository { @@ -362,23 +364,30 @@ class OCFileRepository( throw FileAlreadyExistsException() } - // 3. Perform remote operation + // 3. Retrieve the specific web dav url in case there is one. + val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace( + spaceId = ocFile.spaceId, + accountName = ocFile.owner, + ) + + // 4. Perform remote operation remoteFileDataSource.renameFile( oldName = ocFile.fileName, oldRemotePath = ocFile.remotePath, newName = newName, isFolder = ocFile.isFolder, accountName = ocFile.owner, + spaceWebDavUrl = spaceWebDavUrl, ) - // 4. Save new remote path in the local database + // 5. Save new remote path in the local database localFileDataSource.renameFile( fileToRename = ocFile, finalRemotePath = newRemotePath, finalStoragePath = localStorageProvider.getDefaultSavePathFor(ocFile.owner, newRemotePath) ) - // 5. Update local storage + // 6. Update local storage localStorageProvider.moveLocalFile( ocFile = ocFile, finalStoragePath = localStorageProvider.getDefaultSavePathFor(ocFile.owner, newRemotePath) From c9ff6a4d98f4e594572b04280b7b4e7479529e0f Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 19 Jan 2023 11:11:01 +0100 Subject: [PATCH 052/152] Some improvements in the space header --- .../files/filelist/MainFileListFragment.kt | 19 ++++++++- .../res/layout/main_file_list_fragment.xml | 37 +++++++++-------- .../src/main/res/layout/space_header.xml | 41 +++++++++++-------- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index dfdb0d1ec92..a29126a5e07 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -32,6 +32,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode @@ -46,6 +47,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.owncloud.android.R import com.owncloud.android.databinding.MainFileListFragmentBinding +import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH @@ -228,13 +230,26 @@ class MainFileListFragment : Fragment(), fileListAdapter.updateFileList(filesToAdd = fileListUiState.folderContent, fileListOption = fileListUiState.fileListOption) showOrHideEmptyView(fileListUiState) - binding.spaceHeader.root.isVisible = fileListUiState.space != null && fileListUiState.folderToDisplay?.remotePath == ROOT_PATH + binding.spaceHeader.root.isVisible = fileListUiState.space?.isProject == true && fileListUiState.folderToDisplay?.remotePath == ROOT_PATH fileListUiState.space?.let { + val spaceSpecialImage = it.getSpaceSpecialImage() + if (spaceSpecialImage != null) { + binding.spaceHeader.spaceHeaderImage.tag = spaceSpecialImage.id + val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(spaceSpecialImage.id) + if (thumbnail != null) { + binding.spaceHeader.spaceHeaderImage.run { + setImageBitmap(thumbnail) + scaleType = ImageView.ScaleType.CENTER_CROP + } + if (spaceSpecialImage.file.mimeType == "image/png") { + binding.spaceHeader.spaceHeaderImage.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.background_color)) + } + } + } binding.spaceHeader.spaceHeaderName.text = it.name binding.spaceHeader.spaceHeaderSubtitle.text = it.description } - actionMode?.invalidate() } diff --git a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml index 545998127b1..cdb9e10e382 100644 --- a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml +++ b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml @@ -66,13 +66,12 @@ android:id="@+id/swipeRefresh_main_file_list" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginTop="@dimen/file_list_progress_bar_height" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/shadow_view"> + app:layout_constraintTop_toBottomOf="@id/syncProgressBar"> - @@ -82,30 +81,32 @@ layout="@layout/space_header" android:layout_width="match_parent" android:layout_height="wrap_content" - android:visibility="gone" - app:layout_constraintTop_toBottomOf="@id/shadow_view" /> + android:visibility="visible" + app:layout_constraintTop_toTopOf="parent" /> + tools:listitem="@layout/item_file_list" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/space_header"/> + + - + - - + android:paddingHorizontal="@dimen/standard_padding" + android:paddingVertical="@dimen/standard_half_padding" + android:background="@color/spaces_card_background_color"> - + app:layout_constraintTop_toTopOf="parent"> + + + + @@ -64,15 +76,10 @@ android:ellipsize="end" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" - app:layout_constraintStart_toEndOf="@+id/space_header_image" + app:layout_constraintStart_toEndOf="@+id/space_header_card" app:layout_constraintTop_toBottomOf="@id/space_header_name" tools:text="Space subtitle"/> - - + + From f691940b0e1692bc3a7f118970c70eb3567df3f3 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 19 Jan 2023 14:55:51 +0100 Subject: [PATCH 053/152] Use upsert with spaces instead of insert or replace --- .../com/owncloud/android/data/UpsertHelper.kt | 21 +++++++++- .../android/data/spaces/db/SpacesDao.kt | 39 ++++++++++++++----- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/UpsertHelper.kt b/owncloudData/src/main/java/com/owncloud/android/data/UpsertHelper.kt index 5ad0126a154..29de583deb8 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/UpsertHelper.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/UpsertHelper.kt @@ -3,7 +3,7 @@ * * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -36,3 +36,22 @@ fun upsert( val insertResult = insert(item) if (insertResult == -1L) update(item) } + +/** + * Performs an upsert by first attempting to insert [items] using [insertMany] with the the result + * of the inserts returned. + * + * If it was not inserted due to conflicts, it is updated using [updateMany] + */ +fun upsert( + items: List, + insertMany: (List) -> List, + updateMany: (List) -> Unit, +) { + val insertResults = insertMany(items) + val updateList = items.zip(insertResults) + .mapNotNull { (item, insertResult) -> + if (insertResult == -1L) item else null + } + if (updateList.isNotEmpty()) updateMany(updateList) +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index 661a9856318..3bb8b34f8a7 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -26,6 +26,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction +import androidx.room.Update import com.owncloud.android.data.ProviderMeta import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.DRIVE_TYPE_PERSONAL import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.DRIVE_TYPE_PROJECT @@ -54,18 +55,36 @@ interface SpacesDao { deleteSpaceForAccountById(accountName = spaceToDelete.accountName, spaceId = spaceToDelete.id) } - // Insert new spaces - insertOrReplaceSpaces(listOfSpacesEntities) - insertOrReplaceSpecials(listOfSpecialEntities) + // Upsert new spaces + upsertSpaces(listOfSpacesEntities) + upsertSpecials(listOfSpecialEntities) } - // TODO: Use upsert instead of insert and replace - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertOrReplaceSpaces(listOfSpacesEntities: List): List + @Transaction + fun upsertSpaces(listOfSpacesEntities: List) = com.owncloud.android.data.upsert( + items = listOfSpacesEntities, + insertMany = ::insertOrIgnoreSpaces, + updateMany = ::updateSpaces + ) + + @Transaction + fun upsertSpecials(listOfSpecialEntities: List) = com.owncloud.android.data.upsert( + items = listOfSpecialEntities, + insertMany = ::insertOrIgnoreSpecials, + updateMany = ::updateSpecials + ) + + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insertOrIgnoreSpaces(listOfSpacesEntities: List): List + + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insertOrIgnoreSpecials(listOfSpecialEntities: List): List + + @Update + fun updateSpaces(listOfSpacesEntities: List) - // TODO: Use upsert instead of insert and replace - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertOrReplaceSpecials(listOfSpecialEntities: List): List + @Update + fun updateSpecials(listOfSpecialEntities: List) @Query(SELECT_ALL_SPACES_FOR_ACCOUNT) fun getAllSpacesForAccount( @@ -73,7 +92,7 @@ interface SpacesDao { ): List @Query(SELECT_PERSONAL_SPACE_FOR_ACCOUNT) - fun getPersonalSpacesForAccount( + fun getPersonalSpaceForAccount( accountName: String, ): List From 5955f8c9fe9df327320611069a8e49ee320f54f8 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 24 Jan 2023 09:35:58 +0100 Subject: [PATCH 054/152] Update toolbar properly with spaces --- .../files/filelist/MainFileListFragment.kt | 12 ++++++---- .../files/filelist/MainFileListViewModel.kt | 2 +- .../ui/activity/FileDisplayActivity.kt | 22 +++++++++++-------- .../ui/activity/FolderPickerActivity.java | 4 +++- .../res/layout/main_file_list_fragment.xml | 5 +++-- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index a29126a5e07..213a0c587b3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -53,6 +53,7 @@ import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH import com.owncloud.android.domain.files.model.OCFileSyncInfo import com.owncloud.android.domain.files.model.OCFileWithSyncInfo +import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.extensions.parseError @@ -210,9 +211,11 @@ class MainFileListFragment : Fragment(), } private fun subscribeToViewModels() { - // Observe the current folder displayed and notify the listener + // Observe the current folder displayed collectLatestLifecycleFlow(mainFileListViewModel.currentFolderDisplayed) { - fileActions?.onCurrentFolderUpdated(it) + if (mainFileListViewModel.space.value == null) { + fileActions?.onCurrentFolderUpdated(it) + } if (mainFileListViewModel.fileListOption.value.isAllFiles()) { fileOperationsViewModel.performOperation( FileOperation.RefreshFolderOperation( @@ -223,7 +226,7 @@ class MainFileListFragment : Fragment(), } } - // Observe the file list ui state. + // Observe the file list ui state collectLatestLifecycleFlow(mainFileListViewModel.fileListUiState) { fileListUiState -> if (fileListUiState !is MainFileListViewModel.FileListUiState.Success) return@collectLatestLifecycleFlow @@ -248,6 +251,7 @@ class MainFileListFragment : Fragment(), } binding.spaceHeader.spaceHeaderName.text = it.name binding.spaceHeader.spaceHeaderSubtitle.text = it.description + fileActions?.onCurrentFolderUpdated(fileListUiState.folderToDisplay!!, it) } actionMode?.invalidate() @@ -756,7 +760,7 @@ class MainFileListFragment : Fragment(), } interface FileActions { - fun onCurrentFolderUpdated(newCurrentFolder: OCFile) + fun onCurrentFolderUpdated(newCurrentFolder: OCFile, currentSpace: OCSpace? = null) fun onFileClicked(file: OCFile) fun onShareFileClicked(file: OCFile) fun initDownloadForSending(file: OCFile) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt index 55cc2117e45..f6df0cc7e45 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt @@ -80,7 +80,7 @@ class MainFileListViewModel( val fileListOption: MutableStateFlow = MutableStateFlow(fileListOptionParam) private val searchFilter: MutableStateFlow = MutableStateFlow("") private val sortTypeAndOrder = MutableStateFlow(Pair(SortType.SORT_TYPE_BY_NAME, SortOrder.SORT_ORDER_ASCENDING)) - private val space: MutableStateFlow = MutableStateFlow(null) + val space: MutableStateFlow = MutableStateFlow(null) /** File list ui state combines the other fields and generate a new state whenever any of them changes */ val fileListUiState: StateFlow = diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index ad670f918d4..bbf17dc7dc5 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -10,20 +10,17 @@ * @author Juan Carlos Garrote Gascón * * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2022 ownCloud GmbH. - * + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation. * - * * 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 //www.gnu.org/licenses/>. */ @@ -58,6 +55,7 @@ import com.owncloud.android.domain.exceptions.SSLRecoverablePeerUnverifiedExcept import com.owncloud.android.domain.exceptions.UnauthorizedException import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.checkPasscodeEnforced import com.owncloud.android.extensions.isDownloadPending @@ -335,6 +333,7 @@ class FileDisplayActivity : FileActivity(), uploadActions = this@FileDisplayActivity setSearchListener(findViewById(R.id.root_toolbar_search_view)) } + this.fileListOption = fileListOption val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.left_fragment_container, mainListOfFiles, TAG_LIST_OF_FILES) transaction.commit() @@ -669,7 +668,9 @@ class FileDisplayActivity : FileActivity(), updateToolbar(listMainFileFragment?.getCurrentFile()) } else { val currentDirDisplayed = listMainFileFragment?.getCurrentFile() - if (currentDirDisplayed == null || currentDirDisplayed.parentId == FileDataStorageManager.ROOT_PARENT_ID.toLong()) { + // if space.isProject and currentFolderDisplayed.parentId == root_parent_id -> go to spaces list + // else + if (currentDirDisplayed == null || currentDirDisplayed.parentId == FileDataStorageManager.ROOT_PARENT_ID.toLong()) { // and if we are in spaces list finish() return } else { @@ -852,10 +853,10 @@ class FileDisplayActivity : FileActivity(), sendDownloadedFilesByShareSheet(listOf(file)) } - private fun updateToolbar(chosenFileFromParam: OCFile?) { + private fun updateToolbar(chosenFileFromParam: OCFile?, space: OCSpace? = null) { val chosenFile = chosenFileFromParam ?: file // If no file is passed, current file decides - if (chosenFile == null || chosenFile.remotePath == OCFile.ROOT_PATH) { + if (chosenFile == null || (chosenFile.remotePath == OCFile.ROOT_PATH && (space == null || !space.isProject))) { val title = when (fileListOption) { FileListOption.AV_OFFLINE -> getString(R.string.drawer_item_only_available_offline) @@ -865,6 +866,8 @@ class FileDisplayActivity : FileActivity(), } setupRootToolbar(title, isSearchEnabled = fileListOption != FileListOption.SPACES_LIST) listMainFileFragment?.setSearchListener(findViewById(R.id.root_toolbar_search_view)) + } else if (space?.isProject == true && chosenFile.remotePath == OCFile.ROOT_PATH) { + updateStandardToolbar(title = space.name, displayHomeAsUpEnabled = true, homeButtonEnabled = true) } else { updateStandardToolbar(title = chosenFile.fileName, displayHomeAsUpEnabled = true, homeButtonEnabled = true) } @@ -1346,6 +1349,7 @@ class FileDisplayActivity : FileActivity(), if (fileListOption != newFileListOption) { if (newFileListOption == FileListOption.SPACES_LIST) { fileListOption = FileListOption.SPACES_LIST + file = null initAndShowListOfSpaces() updateToolbar(null) } else if (listMainFileFragment != null) { @@ -1405,8 +1409,8 @@ class FileDisplayActivity : FileActivity(), }) } - override fun onCurrentFolderUpdated(newCurrentFolder: OCFile) { - updateToolbar(newCurrentFolder) + override fun onCurrentFolderUpdated(newCurrentFolder: OCFile, currentSpace: OCSpace?) { + updateToolbar(newCurrentFolder, currentSpace) file = newCurrentFolder } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java index 78695cec862..387315ba726 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java @@ -34,12 +34,14 @@ import android.widget.LinearLayout; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.domain.files.model.FileListOption; import com.owncloud.android.domain.files.model.OCFile; +import com.owncloud.android.domain.spaces.model.OCSpace; import com.owncloud.android.presentation.files.filelist.MainFileListFragment; import com.owncloud.android.ui.fragment.FileFragment; import com.owncloud.android.utils.PreferenceUtils; @@ -249,7 +251,7 @@ public void onClick(View v) { } @Override - public void onCurrentFolderUpdated(@NonNull OCFile newCurrentFolder) { + public void onCurrentFolderUpdated(@NonNull OCFile newCurrentFolder, @Nullable OCSpace currentSpace) { updateNavigationElementsInActionBar(); setFile(newCurrentFolder); } diff --git a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml index cdb9e10e382..2c734a7affd 100644 --- a/owncloudApp/src/main/res/layout/main_file_list_fragment.xml +++ b/owncloudApp/src/main/res/layout/main_file_list_fragment.xml @@ -81,8 +81,9 @@ layout="@layout/space_header" android:layout_width="match_parent" android:layout_height="wrap_content" - android:visibility="visible" - app:layout_constraintTop_toTopOf="parent" /> + android:visibility="gone" + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible"/> Date: Tue, 24 Jan 2023 12:34:30 +0100 Subject: [PATCH 055/152] Navigate properly between spaces list and files list --- .../files/filelist/MainFileListFragment.kt | 6 +++- .../files/filelist/MainFileListViewModel.kt | 7 ++-- .../ui/activity/FileDisplayActivity.kt | 32 +++++++++---------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index 213a0c587b3..90084730fed 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -145,7 +145,7 @@ class MainFileListFragment : Fragment(), updateActionModeAfterTogglingSelected() true } - if (isPickingAFolder()) { + if (isPickingAFolder() || getCurrentSpace() != null) { menu.removeItem(menu.findItem(R.id.action_share_current_folder).itemId) } else { menu.findItem(R.id.action_share_current_folder)?.setOnMenuItemClickListener { @@ -460,6 +460,10 @@ class MainFileListFragment : Fragment(), return mainFileListViewModel.getFile() } + fun getCurrentSpace(): OCSpace? { + return mainFileListViewModel.getSpace() + } + private fun setDrawerStatus(enabled: Boolean) { (activity as FileActivity).setDrawerLockMode(if (enabled) DrawerLayout.LOCK_MODE_UNLOCKED else DrawerLayout.LOCK_MODE_LOCKED_CLOSED) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt index f6df0cc7e45..817372af04c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt @@ -137,6 +137,10 @@ class MainFileListViewModel( return currentFolderDisplayed.value } + fun getSpace(): OCSpace? { + return space.value + } + fun setGridModeAsPreferred() { savePreferredLayoutManager(true) } @@ -188,8 +192,7 @@ class MainFileListViewModel( } else fileById } FileListOption.SPACES_LIST -> { - // TODO: Spaces is not applicable here at the moment - parentDir = TODO() + parentDir = TODO("Move it to usecase if possible") } } } else if (parentId == ROOT_PARENT_ID.toLong()) { diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index bbf17dc7dc5..60e01e374ca 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -498,22 +498,9 @@ class FileDisplayActivity : FileActivity(), } override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { android.R.id.home -> { - val second = secondFragment - val currentDir = listMainFileFragment?.getCurrentFile() - - val inRootFolder = currentDir != null && currentDir.parentId == 0L - val fileFragmentVisible = second != null && second.file != null - - if (!inRootFolder || fileFragmentVisible) { - onBackPressed() - } else if (isDrawerOpen()) { - closeDrawer() - } else { - openDrawer() - } + onBackPressed() } } @@ -668,11 +655,22 @@ class FileDisplayActivity : FileActivity(), updateToolbar(listMainFileFragment?.getCurrentFile()) } else { val currentDirDisplayed = listMainFileFragment?.getCurrentFile() - // if space.isProject and currentFolderDisplayed.parentId == root_parent_id -> go to spaces list - // else - if (currentDirDisplayed == null || currentDirDisplayed.parentId == FileDataStorageManager.ROOT_PARENT_ID.toLong()) { // and if we are in spaces list + // If current file is null (we are in the spaces list, for example), close the app + if (currentDirDisplayed == null) { finish() return + } + // If current file is root folder + else if (currentDirDisplayed.parentId == FileDataStorageManager.ROOT_PARENT_ID.toLong()) { + // If current space is a project space (not personal, not shares), navigate back to the spaces list + if (listMainFileFragment?.getCurrentSpace()?.isProject == true) { + navigateTo(FileListOption.SPACES_LIST) + } + // If current space is not a project space (personal or shares) or it is null ("Files" in oC10), close the app + else { + finish() + return + } } else { listMainFileFragment?.onBrowseUp() } From e41e6f075e08890bc4d2573f9ec9d96ece7f6510 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 24 Jan 2023 16:49:18 +0100 Subject: [PATCH 056/152] Fixed rotating screen in spaces file list --- .../datamodel/FileDataStorageManager.kt | 6 +-- .../android/syncadapter/FileSyncAdapter.java | 2 +- .../android/ui/activity/FileActivity.java | 4 +- .../ui/activity/FileDisplayActivity.kt | 11 +++-- .../ui/activity/FolderPickerActivity.java | 4 +- .../ReceiveExternalFilesActivity.java | 8 ++-- .../ui/activity/UploadPathActivity.java | 4 +- .../ui/preview/PreviewTextFragment.java | 2 +- .../ui/preview/PreviewVideoFragment.java | 2 +- .../res/layout/list_fragment_expandable.xml | 43 ------------------- 10 files changed, 23 insertions(+), 63 deletions(-) delete mode 100755 owncloudApp/src/main/res/layout/list_fragment_expandable.xml diff --git a/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt b/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt index 2f4ce6636eb..ac6a4fdd9ec 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt @@ -43,13 +43,13 @@ class FileDataStorageManager( val account: Account, ) : KoinComponent { - fun getFileByPath(remotePath: String): OCFile? = getFileByPathAndAccount(remotePath, account.name) + fun getFileByPath(remotePath: String, spaceId: String? = null): OCFile? = getFileByPathAndAccount(remotePath, account.name, spaceId) - private fun getFileByPathAndAccount(remotePath: String, accountName: String): OCFile? = runBlocking(CoroutinesDispatcherProvider().io) { + private fun getFileByPathAndAccount(remotePath: String, accountName: String, spaceId: String? = null): OCFile? = runBlocking(CoroutinesDispatcherProvider().io) { val getFileByRemotePathUseCase: GetFileByRemotePathUseCase by inject() val result = withContext(CoroutineScope(CoroutinesDispatcherProvider().io).coroutineContext) { - getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(accountName, remotePath)) + getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(accountName, remotePath, spaceId)) }.getDataOrNull() result } diff --git a/owncloudApp/src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java b/owncloudApp/src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java index dd32b8c04cb..a7018da0b51 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/owncloudApp/src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -152,7 +152,7 @@ public synchronized void onPerformSync(Account account, Bundle extras, if (!mCancellation) { @NotNull Lazy getFileByRemotePathUseCaseLazy = inject(GetFileByRemotePathUseCase.class); - GetFileByRemotePathUseCase.Params params = new GetFileByRemotePathUseCase.Params(OCFile.ROOT_PATH, account.name); + GetFileByRemotePathUseCase.Params params = new GetFileByRemotePathUseCase.Params(OCFile.ROOT_PATH, account.name, null); UseCaseResult useCaseResult = getFileByRemotePathUseCaseLazy.getValue().execute(params); if (useCaseResult.getDataOrNull() != null) { diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index e060ad6a30a..5dedad2ab10 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -358,7 +358,7 @@ public void showUntrustedCertDialogForThrowable(Throwable throwable) { protected void updateFileFromDB() { OCFile file = getFile(); if (file != null) { - file = getStorageManager().getFileByPath(file.getRemotePath()); + file = getStorageManager().getFileByPath(file.getRemotePath(), file.getSpaceId()); setFile(file); } } @@ -443,7 +443,7 @@ protected OCFile getCurrentDir() { return file; } else if (getStorageManager() != null) { String parentPath = file.getParentRemotePath(); - return getStorageManager().getFileByPath(parentPath); + return getStorageManager().getFileByPath(parentPath, file.getSpaceId()); } } return null; diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 60e01e374ca..f47ad2db338 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -289,7 +289,7 @@ class FileDisplayActivity : FileActivity(), file = null // not able to know the directory where the file is uploading } } else { - file = storageManager.getFileByPath(file.remotePath) + file = storageManager.getFileByPath(file.remotePath, file.spaceId) // currentDir = null if not in the current Account } } @@ -698,11 +698,14 @@ class FileDisplayActivity : FileActivity(), Timber.v("onResume() start") super.onResume() - setCheckedItemAtBottomBar(getMenuItemForFileListOption(fileListOption)) - if (!isSpacesTabSelected()) { - listMainFileFragment?.updateFileListOption(fileListOption, file) + if (listMainFileFragment?.getCurrentSpace()?.isProject == true) { + setCheckedItemAtBottomBar(getMenuItemForFileListOption(FileListOption.SPACES_LIST)) + } else { + setCheckedItemAtBottomBar(getMenuItemForFileListOption(fileListOption)) } + listMainFileFragment?.updateFileListOption(fileListOption, file) + // refresh list of files refreshListOfFilesFragment() diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java index 387315ba726..26a4478c914 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java @@ -107,7 +107,7 @@ protected void onAccountSet(boolean stateWasRecovered) { OCFile folder = getFile(); if (folder == null || !folder.isFolder()) { // fall back to root folder - setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH)); + setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH, null)); folder = getFile(); } @@ -124,7 +124,7 @@ private void initAndShowListOfFilesFragment() { OCFile safeInitialFolder; if (getFile() == null) { FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(getAccount()); - safeInitialFolder = fileDataStorageManager.getFileByPath(OCFile.ROOT_PATH); + safeInitialFolder = fileDataStorageManager.getFileByPath(OCFile.ROOT_PATH, null); } else { safeInitialFolder = getFile(); } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java index b81aacfd632..547237fef66 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java @@ -362,7 +362,7 @@ public void onBackPressed() { } else { mParents.pop(); String full_path = generatePath(mParents); - startSyncFolderOperation(getStorageManager().getFileByPath(full_path)); + startSyncFolderOperation(getStorageManager().getFileByPath(full_path, null)); updateDirectoryList(); } } @@ -448,7 +448,7 @@ private void updateDirectoryList() { String full_path = generatePath(mParents); Timber.d("Populating view with content of : %s", full_path); - mFile = getStorageManager().getFileByPath(full_path); + mFile = getStorageManager().getFileByPath(full_path, null); if (mFile != null) { if (mAdapter == null) { mAdapter = new ReceiveExternalFilesAdapter( @@ -692,14 +692,14 @@ private OCFile getCurrentFolder() { if (file.isFolder()) { return file; } else if (getStorageManager() != null) { - return getStorageManager().getFileByPath(file.getParentRemotePath()); + return getStorageManager().getFileByPath(file.getParentRemotePath(), null); } } return null; } private void browseToRoot() { - OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH); + OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH, null); mFile = root; startSyncFolderOperation(root); } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java index 34f6cb7a43f..fbb630b446a 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java @@ -60,11 +60,11 @@ protected void onAccountSet(boolean stateWasRecovered) { // Check if we need to open an specific folder and navigate to it. // If there is not, fallback to the root folder for this account. String cameraUploadPath = getIntent().getStringExtra(KEY_CAMERA_UPLOAD_PATH); - OCFile initialFile = getStorageManager().getFileByPath(cameraUploadPath); + OCFile initialFile = getStorageManager().getFileByPath(cameraUploadPath, null); if (initialFile == null || !initialFile.isFolder()) { // fall back to root folder - setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH)); + setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH, null)); } else { setFile(initialFile); } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java index 13a191c87e5..f1bdb632216 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java @@ -430,7 +430,7 @@ public void onFileMetadataChanged(OCFile updatedFile) { public void onFileMetadataChanged() { FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); if (storageManager != null) { - setFile(storageManager.getFileByPath(getFile().getRemotePath())); + setFile(storageManager.getFileByPath(getFile().getRemotePath(), null)); } getActivity().invalidateOptionsMenu(); } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFragment.java b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFragment.java index a452557e20a..10cd094362c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFragment.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFragment.java @@ -507,7 +507,7 @@ public void onFileMetadataChanged(OCFile updatedFile) { public void onFileMetadataChanged() { FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); if (storageManager != null) { - setFile(storageManager.getFileByPath(getFile().getRemotePath())); + setFile(storageManager.getFileByPath(getFile().getRemotePath(), null)); } requireActivity().invalidateOptionsMenu(); } diff --git a/owncloudApp/src/main/res/layout/list_fragment_expandable.xml b/owncloudApp/src/main/res/layout/list_fragment_expandable.xml deleted file mode 100755 index 5c736d2ebd2..00000000000 --- a/owncloudApp/src/main/res/layout/list_fragment_expandable.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - From ec8f0ca0fd91976ffc8b02042d4e9370594b4674 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 24 Jan 2023 17:07:10 +0100 Subject: [PATCH 057/152] Space header is now a bit less tall --- owncloudApp/src/main/res/layout/space_header.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/owncloudApp/src/main/res/layout/space_header.xml b/owncloudApp/src/main/res/layout/space_header.xml index fd76274df85..3d2ca015a37 100644 --- a/owncloudApp/src/main/res/layout/space_header.xml +++ b/owncloudApp/src/main/res/layout/space_header.xml @@ -29,12 +29,13 @@ From 18dcec6d9527fafb0a1fa748e4a2f50eef9fb035 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 25 Jan 2023 12:03:02 +0100 Subject: [PATCH 058/152] Fix tests for spaces --- .../android/data/spaces/db/SpacesDaoTest.kt | 4 +--- .../datasources/OCLocalFileDataSourceTest.kt | 24 +++++++++---------- .../datasources/OCRemoteFileDataSourceTest.kt | 4 ++-- .../file/repository/OCFileRepositoryTest.kt | 16 ++++++------- .../com/owncloud/android/testutil/OCSpace.kt | 10 ++++++++ 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/owncloudData/src/androidTest/java/com/owncloud/android/data/spaces/db/SpacesDaoTest.kt b/owncloudData/src/androidTest/java/com/owncloud/android/data/spaces/db/SpacesDaoTest.kt index 243c178d56c..3ef489e5f05 100644 --- a/owncloudData/src/androidTest/java/com/owncloud/android/data/spaces/db/SpacesDaoTest.kt +++ b/owncloudData/src/androidTest/java/com/owncloud/android/data/spaces/db/SpacesDaoTest.kt @@ -146,9 +146,7 @@ class SpacesDaoTest { ) + specialsAlreadyInDatabaseToInsert val newSpacesToInsert = listOf( - OC_SPACE_PROJECT_WITHOUT_IMAGE.copy( - id = anotherSpaceId - ).toEntity() + OC_SPACE_PROJECT_WITHOUT_IMAGE.toEntity() ) + spacesAlreadyInDatabaseToInsert spacesDao.insertOrDeleteSpaces(newSpacesToInsert, newSpecialsToInsert) diff --git a/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCLocalFileDataSourceTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCLocalFileDataSourceTest.kt index b9308773894..0a5e4ab9153 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCLocalFileDataSourceTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCLocalFileDataSourceTest.kt @@ -78,29 +78,29 @@ class OCLocalFileDataSourceTest { @Test fun `get file by remote path - ok`() { - every { dao.getFileByOwnerAndRemotePath(any(), any()) } returns DUMMY_FILE_ENTITY + every { dao.getFileByOwnerAndRemotePath(any(), any(), any()) } returns DUMMY_FILE_ENTITY - val result = localDataSource.getFileByRemotePath(OC_FILE.remotePath, OC_FILE.owner) + val result = localDataSource.getFileByRemotePath(OC_FILE.remotePath, OC_FILE.owner, OC_FILE.spaceId) assertEquals(OC_FILE, result) - verify { dao.getFileByOwnerAndRemotePath(OC_FILE.owner, OC_FILE.remotePath) } + verify { dao.getFileByOwnerAndRemotePath(OC_FILE.owner, OC_FILE.remotePath, OC_FILE.spaceId) } } @Test fun `get file by remote path - ok - null`() { - every { dao.getFileByOwnerAndRemotePath(any(), any()) } returns null + every { dao.getFileByOwnerAndRemotePath(any(), any(), any()) } returns null - val result = localDataSource.getFileByRemotePath(OC_FILE.remotePath, OC_FILE.owner) + val result = localDataSource.getFileByRemotePath(OC_FILE.remotePath, OC_FILE.owner, OC_FILE.spaceId) assertNull(result) - verify { dao.getFileByOwnerAndRemotePath(OC_FILE.owner, OC_FILE.remotePath) } + verify { dao.getFileByOwnerAndRemotePath(OC_FILE.owner, OC_FILE.remotePath, OC_FILE.spaceId) } } @Test fun `get file by remote path - ok - null - create root folder`() { - every { dao.getFileByOwnerAndRemotePath(any(), any()) } returns null + every { dao.getFileByOwnerAndRemotePath(any(), any(), any()) } returns null every { dao.mergeRemoteAndLocalFile(any()) } returns 1234 every { dao.getFileById(1234) } returns DUMMY_FILE_ENTITY.copy( parentId = ROOT_PARENT_ID, @@ -108,7 +108,7 @@ class OCLocalFileDataSourceTest { remotePath = ROOT_PATH ) - val result = localDataSource.getFileByRemotePath(ROOT_PATH, OC_FILE.owner) + val result = localDataSource.getFileByRemotePath(ROOT_PATH, OC_FILE.owner, null) assertNotNull(result) assertEquals(ROOT_PARENT_ID, result!!.parentId) @@ -117,7 +117,7 @@ class OCLocalFileDataSourceTest { assertEquals(ROOT_PATH, result.remotePath) verify { - dao.getFileByOwnerAndRemotePath(OC_FILE.owner, ROOT_PATH) + dao.getFileByOwnerAndRemotePath(OC_FILE.owner, ROOT_PATH, null) dao.mergeRemoteAndLocalFile(any()) dao.getFileById(1234) } @@ -125,11 +125,11 @@ class OCLocalFileDataSourceTest { @Test(expected = Exception::class) fun `get file by remote path - ko`() { - every { dao.getFileByOwnerAndRemotePath(any(), any()) } throws Exception() + every { dao.getFileByOwnerAndRemotePath(any(), any(), any()) } throws Exception() - localDataSource.getFileByRemotePath(OC_FILE.remotePath, OC_FILE.owner) + localDataSource.getFileByRemotePath(OC_FILE.remotePath, OC_FILE.owner, null) - verify { dao.getFileByOwnerAndRemotePath(OC_FILE.owner, OC_FILE.remotePath) } + verify { dao.getFileByOwnerAndRemotePath(OC_FILE.owner, OC_FILE.remotePath, null) } } @Test diff --git a/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCRemoteFileDataSourceTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCRemoteFileDataSourceTest.kt index 58022e73de4..5dbde60a56d 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCRemoteFileDataSourceTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCRemoteFileDataSourceTest.kt @@ -100,7 +100,7 @@ class OCRemoteFileDataSourceTest { ocFileService.createFolder(remotePath = OC_FOLDER.remotePath, createFullPath = false, isChunkFolder = false) } returns createFolderRemoteResult - val createFolderResult = ocRemoteFileDataSource.createFolder(OC_FOLDER.remotePath, false, false, OC_ACCOUNT_NAME) + val createFolderResult = ocRemoteFileDataSource.createFolder(OC_FOLDER.remotePath, false, false, OC_ACCOUNT_NAME, null) assertNotNull(createFolderResult) assertEquals(createFolderRemoteResult.data, createFolderResult) @@ -114,6 +114,6 @@ class OCRemoteFileDataSourceTest { ocFileService.createFolder(OC_FOLDER.remotePath, false, false) } throws Exception() - ocRemoteFileDataSource.createFolder(OC_FOLDER.remotePath, false, false, OC_ACCOUNT_NAME) + ocRemoteFileDataSource.createFolder(OC_FOLDER.remotePath, false, false, OC_ACCOUNT_NAME, null) } } diff --git a/owncloudData/src/test/java/com/owncloud/android/data/file/repository/OCFileRepositoryTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/file/repository/OCFileRepositoryTest.kt index a0cf7b12126..c644ab5026a 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/file/repository/OCFileRepositoryTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/file/repository/OCFileRepositoryTest.kt @@ -60,13 +60,13 @@ class OCFileRepositoryTest { @Test fun `create folder - ok`() { - every { remoteFileDataSource.createFolder(OC_FOLDER.remotePath, false, false, OC_ACCOUNT_NAME) } returns Unit + every { remoteFileDataSource.createFolder(OC_FOLDER.remotePath, false, false, OC_ACCOUNT_NAME, null) } returns Unit ocFileRepository.createFolder(OC_FOLDER.remotePath, OC_FOLDER) verify(exactly = 1) { - remoteFileDataSource.createFolder(any(), false, false, OC_ACCOUNT_NAME) + remoteFileDataSource.createFolder(any(), false, false, OC_ACCOUNT_NAME, null) localFileDataSource.saveFilesInFolderAndReturnThem(any(), OC_FOLDER) } } @@ -74,13 +74,13 @@ class OCFileRepositoryTest { @Test(expected = NoConnectionWithServerException::class) fun `create folder - ko - no connection exception`() { every { - remoteFileDataSource.createFolder(OC_FOLDER.remotePath, false, false, OC_ACCOUNT_NAME) + remoteFileDataSource.createFolder(OC_FOLDER.remotePath, false, false, OC_ACCOUNT_NAME, null) } throws NoConnectionWithServerException() ocFileRepository.createFolder(OC_FOLDER.remotePath, OC_FOLDER) verify(exactly = 1) { - remoteFileDataSource.createFolder(any(), false, false, OC_ACCOUNT_NAME) + remoteFileDataSource.createFolder(any(), false, false, OC_ACCOUNT_NAME, null) } verify(exactly = 0) { localFileDataSource.saveFilesInFolderAndReturnThem(any(), OC_FOLDER) @@ -126,25 +126,25 @@ class OCFileRepositoryTest { @Test fun `get file by remote path - ok`() { - every { localFileDataSource.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner) } returns OC_FOLDER + every { localFileDataSource.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner, null) } returns OC_FOLDER ocFileRepository.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner) verify(exactly = 1) { - localFileDataSource.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner) + localFileDataSource.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner, null) } } @Test(expected = Exception::class) fun `get file by remote path - ko`() { every { - localFileDataSource.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner) + localFileDataSource.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner, null) } throws Exception() ocFileRepository.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner) verify(exactly = 1) { - localFileDataSource.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner) + localFileDataSource.getFileByRemotePath(OC_FOLDER.remotePath, OC_FOLDER.owner, null) } } diff --git a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt index 4dd210033eb..06282ccb63b 100644 --- a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt +++ b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt @@ -94,6 +94,16 @@ val OC_SPACE_PROJECT_WITH_IMAGE = OCSpace( ) val OC_SPACE_PROJECT_WITHOUT_IMAGE = OC_SPACE_PROJECT_WITH_IMAGE.copy( + id = "8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-1234566789", + name = "Space without image", + root = SpaceRoot( + eTag = "989c7968dbbbde8c5fd9849b9123c384", + id = "8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-1234566789", + permissions = null, + webDavUrl = "https://server.com/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-1234566789", + deleted = null + ), + webUrl = "https://server.com/f/8871f4f3-fc6f-4a66-8bed-62f175f76f38$0aa0e03c-ec36-498c-bb9f-1234566789", special = listOf(OC_SPACE_SPECIAL_README) ) From 43cb7f44effd08e560ff5dbbe67700367a2b849b Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 26 Jan 2023 14:48:41 +0100 Subject: [PATCH 059/152] CR changes applied --- .../presentation/files/filelist/MainFileListFragment.kt | 2 +- .../presentation/files/filelist/MainFileListViewModel.kt | 2 +- owncloudApp/src/main/res/layout/space_header.xml | 4 ++-- owncloudApp/src/main/res/values-h640dp/dims.xml | 5 ++++- owncloudApp/src/main/res/values-h740dp/dims.xml | 5 ++++- owncloudApp/src/main/res/values/dims.xml | 3 ++- .../main/java/com/owncloud/android/data/ProviderMeta.java | 6 +++++- .../com/owncloud/android/data/files/db/OCFileEntity.kt | 7 ++----- 8 files changed, 21 insertions(+), 13 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index 90084730fed..7fa3b9c0ac8 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -213,7 +213,7 @@ class MainFileListFragment : Fragment(), private fun subscribeToViewModels() { // Observe the current folder displayed collectLatestLifecycleFlow(mainFileListViewModel.currentFolderDisplayed) { - if (mainFileListViewModel.space.value == null) { + if (getCurrentSpace() == null) { fileActions?.onCurrentFolderUpdated(it) } if (mainFileListViewModel.fileListOption.value.isAllFiles()) { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt index 817372af04c..5cfee1080a4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt @@ -80,7 +80,7 @@ class MainFileListViewModel( val fileListOption: MutableStateFlow = MutableStateFlow(fileListOptionParam) private val searchFilter: MutableStateFlow = MutableStateFlow("") private val sortTypeAndOrder = MutableStateFlow(Pair(SortType.SORT_TYPE_BY_NAME, SortOrder.SORT_ORDER_ASCENDING)) - val space: MutableStateFlow = MutableStateFlow(null) + private val space: MutableStateFlow = MutableStateFlow(null) /** File list ui state combines the other fields and generate a new state whenever any of them changes */ val fileListUiState: StateFlow = diff --git a/owncloudApp/src/main/res/layout/space_header.xml b/owncloudApp/src/main/res/layout/space_header.xml index 3d2ca015a37..b429f7a8bb9 100644 --- a/owncloudApp/src/main/res/layout/space_header.xml +++ b/owncloudApp/src/main/res/layout/space_header.xml @@ -29,8 +29,8 @@ 172dp + 100dp diff --git a/owncloudApp/src/main/res/values-h740dp/dims.xml b/owncloudApp/src/main/res/values-h740dp/dims.xml index d4973189f1d..65e6b45572e 100644 --- a/owncloudApp/src/main/res/values-h740dp/dims.xml +++ b/owncloudApp/src/main/res/values-h740dp/dims.xml @@ -2,7 +2,9 @@ ownCloud Android client application @author David Crespo Ríos - Copyright (C) 2022 ownCloud GmbH. + @author Juan Carlos Garrote Gascón + + Copyright (C) 2023 ownCloud GmbH. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2, @@ -26,4 +28,5 @@ 256dp + 150dp diff --git a/owncloudApp/src/main/res/values/dims.xml b/owncloudApp/src/main/res/values/dims.xml index 563b43811a4..49091c6b41a 100644 --- a/owncloudApp/src/main/res/values/dims.xml +++ b/owncloudApp/src/main/res/values/dims.xml @@ -1,7 +1,7 @@ 156dp + 80dp diff --git a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java index 525983566d1..554624ca2ba 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java +++ b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java @@ -97,7 +97,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String CAPABILITIES_VERSION_STRING = "version_string"; public static final String LEGACY_CAPABILITIES_VERSION_MAYOR = "version_mayor"; - // Columns of filelist table + // Columns of filelist table (legacy) public static final String FILE_ACCOUNT_OWNER = "file_owner"; public static final String FILE_CONTENT_LENGTH = "content_length"; public static final String FILE_CONTENT_TYPE = "content_type"; @@ -134,5 +134,9 @@ static public class ProviderTableMeta implements BaseColumns { public static final String UPLOAD_STATUS = "status"; public static final String UPLOAD_TRANSFER_ID = "transfer_id"; public static final String UPLOAD_UPLOAD_END_TIMESTAMP = "upload_end_timestamp"; + + // Columns of files table + public static final String FILE_OWNER = "owner"; + public static final String FILE_SPACE_ID = "spaceId"; } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt index 9e366fb905e..fff0e7c62c3 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt @@ -37,6 +37,7 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_LAST_SYNC_D import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_MODIFIED import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_NAME +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_OWNER import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_PARENT import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_PATH import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_PERMISSIONS @@ -44,12 +45,11 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_PRIVATE_LIN import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_REMOTE_ID import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_SHARED_VIA_LINK import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_SHARED_WITH_SHAREE +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_SPACE_ID import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_STORAGE_PATH import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_TREE_ETAG import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_UPDATE_THUMBNAIL import com.owncloud.android.data.ProviderMeta.ProviderTableMeta._ID -import com.owncloud.android.data.files.db.OCFileEntity.Companion.FILE_OWNER -import com.owncloud.android.data.files.db.OCFileEntity.Companion.FILE_SPACE_ID import com.owncloud.android.data.spaces.db.SpacesEntity import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ACCOUNT_NAME import com.owncloud.android.data.spaces.db.SpacesEntity.Companion.SPACES_ID @@ -139,8 +139,5 @@ data class OCFileEntity( private fun Cursor.getStringFromColumnOrEmpty( columnName: String ): String = getColumnIndex(columnName).takeUnless { it < 0 }?.let { getString(it) }.orEmpty() - - const val FILE_OWNER = "owner" - const val FILE_SPACE_ID = "spaceId" } } From 773b771fb68df61c19e7ca04906c54e223314350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Fri, 27 Jan 2023 15:04:27 +0100 Subject: [PATCH 060/152] Fix expected paths for folders --- .../com/owncloud/android/data/storage/LocalStorageProvider.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt b/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt index 11e5d08d4ae..8c942bc5081 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt @@ -68,9 +68,9 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { fun getExpectedRemotePath(remotePath: String, newName: String, isFolder: Boolean): String { var parent = (File(remotePath)).parent ?: throw IllegalArgumentException() parent = if (parent.endsWith(File.separator)) parent else parent + File.separator - val newRemotePath = parent + newName + var newRemotePath = parent + newName if (isFolder) { - newRemotePath.plus(File.separator) + newRemotePath += File.separator } return newRemotePath } From dd97f4d675a3f0ccb87c2a2ed7251bba9cab181c Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Mon, 30 Jan 2023 15:02:33 +0100 Subject: [PATCH 061/152] Added animation to make the spaces header appear and disappear smoother --- .../files/filelist/MainFileListFragment.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index 7fa3b9c0ac8..76436cb44ea 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -233,7 +233,15 @@ class MainFileListFragment : Fragment(), fileListAdapter.updateFileList(filesToAdd = fileListUiState.folderContent, fileListOption = fileListUiState.fileListOption) showOrHideEmptyView(fileListUiState) - binding.spaceHeader.root.isVisible = fileListUiState.space?.isProject == true && fileListUiState.folderToDisplay?.remotePath == ROOT_PATH + binding.spaceHeader.root.apply { + if (fileListUiState.space?.isProject == true && fileListUiState.folderToDisplay?.remotePath == ROOT_PATH) { + isVisible = true + animate().translationY(0f).duration = 100 + } else { + animate().translationY(-height.toFloat()).withEndAction { isVisible = false } + } + } + fileListUiState.space?.let { val spaceSpecialImage = it.getSpaceSpecialImage() if (spaceSpecialImage != null) { From 1fff38b83e8e06ba62237a9a93fc47836235608d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 31 Jan 2023 11:54:33 +0100 Subject: [PATCH 062/152] Remove legacy lastSyncDateForProperties. It was not used anymore --- .../android/ui/activity/FileDisplayActivity.kt | 2 +- .../owncloud/android/workers/DownloadFileWorker.kt | 1 - .../40.json | 12 +++--------- .../implementation/OCLocalFileDataSource.kt | 2 -- .../owncloud/android/data/files/db/OCFileEntity.kt | 2 -- .../data/files/repository/OCFileRepository.kt | 11 +++++------ .../android/data/migrations/AutoMigration39To40.kt | 6 ++++++ .../owncloud/android/domain/files/model/OCFile.kt | 1 - 8 files changed, 15 insertions(+), 22 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index f47ad2db338..93d9bb78e3c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -278,7 +278,7 @@ class FileDisplayActivity : FileActivity(), // get parent from path val parentPath: String if (file != null) { - if (file.isAvailableLocally && file.lastSyncDateForProperties == 0L) { + if (file.isAvailableLocally) { // upload in progress - right now, files are not inserted in the local // cache until the upload is successful get parent from path parentPath = file.remotePath.substring( diff --git a/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt b/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt index ed781775461..34d3636a8b4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt @@ -210,7 +210,6 @@ class DownloadFileWorker( etag = downloadRemoteFileOperation.etag storagePath = finalLocationForFile length = (File(finalLocationForFile).length()) - lastSyncDateForProperties = currentTime lastSyncDateForData = currentTime modifiedAtLastSyncForData = downloadRemoteFileOperation.modificationTimestamp } diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json index 56625698e0f..bbb066d3898 100644 --- a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 40, - "identityHash": "72151f55aa7990b47067c2ac269e3cd9", + "identityHash": "a16159ce0a3e084a5f3d044c85ea7fba", "entities": [ { "tableName": "folder_backup", @@ -335,7 +335,7 @@ }, { "tableName": "files", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`parentId` INTEGER, `owner` TEXT NOT NULL, `remotePath` TEXT NOT NULL, `remoteId` TEXT, `length` INTEGER NOT NULL, `creationTimestamp` INTEGER, `modificationTimestamp` INTEGER NOT NULL, `mimeType` TEXT NOT NULL, `etag` TEXT, `permissions` TEXT, `privateLink` TEXT, `storagePath` TEXT, `name` TEXT, `treeEtag` TEXT, `keepInSync` INTEGER, `lastSyncDateForData` INTEGER, `fileShareViaLink` INTEGER, `lastSyncDateForProperties` INTEGER, `needsToUpdateThumbnail` INTEGER NOT NULL, `modifiedAtLastSyncForData` INTEGER, `etagInConflict` TEXT, `fileIsDownloading` INTEGER, `sharedWithSharee` INTEGER, `sharedByLink` INTEGER NOT NULL, `spaceId` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`owner`, `spaceId`) REFERENCES `spaces`(`account_name`, `space_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`parentId` INTEGER, `owner` TEXT NOT NULL, `remotePath` TEXT NOT NULL, `remoteId` TEXT, `length` INTEGER NOT NULL, `creationTimestamp` INTEGER, `modificationTimestamp` INTEGER NOT NULL, `mimeType` TEXT NOT NULL, `etag` TEXT, `permissions` TEXT, `privateLink` TEXT, `storagePath` TEXT, `name` TEXT, `treeEtag` TEXT, `keepInSync` INTEGER, `lastSyncDateForData` INTEGER, `fileShareViaLink` INTEGER, `needsToUpdateThumbnail` INTEGER NOT NULL, `modifiedAtLastSyncForData` INTEGER, `etagInConflict` TEXT, `fileIsDownloading` INTEGER, `sharedWithSharee` INTEGER, `sharedByLink` INTEGER NOT NULL, `spaceId` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`owner`, `spaceId`) REFERENCES `spaces`(`account_name`, `space_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "parentId", @@ -439,12 +439,6 @@ "affinity": "INTEGER", "notNull": false }, - { - "fieldPath": "lastSyncDateForProperties", - "columnName": "lastSyncDateForProperties", - "affinity": "INTEGER", - "notNull": false - }, { "fieldPath": "needsToUpdateThumbnail", "columnName": "needsToUpdateThumbnail", @@ -999,7 +993,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '72151f55aa7990b47067c2ac269e3cd9')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a16159ce0a3e084a5f3d044c85ea7fba')" ] } } \ No newline at end of file diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt index 15d1a7a1af1..bf57a3c2a62 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt @@ -221,7 +221,6 @@ class OCLocalFileDataSource( needsToUpdateThumbnail = needsToUpdateThumbnail, fileIsDownloading = fileIsDownloading, lastSyncDateForData = lastSyncDateForData, - lastSyncDateForProperties = lastSyncDateForProperties, modifiedAtLastSyncForData = modifiedAtLastSyncForData, etagInConflict = etagInConflict, treeEtag = treeEtag, @@ -249,7 +248,6 @@ class OCLocalFileDataSource( needsToUpdateThumbnail = needsToUpdateThumbnail, fileIsDownloading = fileIsDownloading, lastSyncDateForData = lastSyncDateForData, - lastSyncDateForProperties = lastSyncDateForProperties, modifiedAtLastSyncForData = modifiedAtLastSyncForData, etagInConflict = etagInConflict, treeEtag = treeEtag, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt index fff0e7c62c3..a2411d25c54 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt @@ -85,7 +85,6 @@ data class OCFileEntity( var availableOfflineStatus: Int? = null, val lastSyncDateForData: Long? = null, val fileShareViaLink: Int? = null, - var lastSyncDateForProperties: Long? = null, var needsToUpdateThumbnail: Boolean = false, val modifiedAtLastSyncForData: Long? = null, val etagInConflict: String? = null, @@ -122,7 +121,6 @@ data class OCFileEntity( storagePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_STORAGE_PATH)), name = cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)), treeEtag = cursor.getString(cursor.getColumnIndexOrThrow(FILE_TREE_ETAG)), - lastSyncDateForProperties = cursor.getLong(cursor.getColumnIndexOrThrow(FILE_LAST_SYNC_DATE)), lastSyncDateForData = cursor.getLong(cursor.getColumnIndexOrThrow(FILE_LAST_SYNC_DATE_FOR_DATA)), availableOfflineStatus = cursor.getInt(cursor.getColumnIndexOrThrow(FILE_KEEP_IN_SYNC)), fileShareViaLink = cursor.getInt(cursor.getColumnIndexOrThrow(FILE_SHARED_VIA_LINK)), diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 2b1b5094ed5..fa4b75d8c6e 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -228,13 +228,14 @@ class OCFileRepository( return remoteFileDataSource.readFile(remotePath, accountName, spaceWebDavUrl).copy(spaceId = spaceId) } - override fun refreshFolder(remotePath: String, accountName: String, spaceId: String?): List { - val currentSyncTime = System.currentTimeMillis() - + override fun refreshFolder( + remotePath: String, + accountName: String, + spaceId: String?, + ): List { val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(spaceId, accountName) // Retrieve remote folder data - val fetchFolderResult = remoteFileDataSource.refreshFolder(remotePath, accountName, spaceWebDavUrl).map { it.copy(spaceId = spaceId) } @@ -262,8 +263,6 @@ class OCFileRepository( // Loop to sync every child remoteFolderContent.forEach { remoteChild -> - remoteChild.lastSyncDateForProperties = currentSyncTime - // Let's try with remote path if the file does not have remote id yet val localChildToSync = localFilesMap.remove(remoteChild.remoteId) ?: localFilesMap.remove(remoteChild.remotePath) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt b/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt index 7cd707a913b..bd8db8d906f 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/migrations/AutoMigration39To40.kt @@ -20,10 +20,12 @@ package com.owncloud.android.data.migrations +import androidx.room.DeleteColumn import androidx.room.RenameColumn import androidx.room.migration.AutoMigrationSpec import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAJOR +import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME @RenameColumn( tableName = CAPABILITIES_TABLE_NAME, @@ -60,4 +62,8 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.CAPABILITIES_VER fromColumnName = "newUrl", toColumnName = "newUrlAppProviders" ) +@DeleteColumn( + tableName = FILES_TABLE_NAME, + columnName = "lastSyncDateForProperties" +) class AutoMigration39To40 : AutoMigrationSpec diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt index 1b51a1aba9d..1c4b8bae402 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt @@ -50,7 +50,6 @@ data class OCFile( var treeEtag: String? = "", var availableOfflineStatus: AvailableOfflineStatus? = null, var lastSyncDateForData: Long? = 0, - var lastSyncDateForProperties: Long? = 0, var needsToUpdateThumbnail: Boolean = false, var modifiedAtLastSyncForData: Long? = 0, var etagInConflict: String? = null, From 0e8794ad7a029ca5b633b1a52c307202e8258d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 31 Jan 2023 12:39:31 +0100 Subject: [PATCH 063/152] Remove legacy constant --- .../src/main/java/com/owncloud/android/data/ProviderMeta.java | 1 - .../main/java/com/owncloud/android/data/files/db/OCFileEntity.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java index 554624ca2ba..68d30208f84 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java +++ b/owncloudData/src/main/java/com/owncloud/android/data/ProviderMeta.java @@ -106,7 +106,6 @@ static public class ProviderTableMeta implements BaseColumns { public static final String FILE_ETAG_IN_CONFLICT = "etag_in_conflict"; public static final String FILE_IS_DOWNLOADING = "is_downloading"; public static final String FILE_KEEP_IN_SYNC = "keep_in_sync"; - public static final String FILE_LAST_SYNC_DATE = "last_sync_date";// _for_properties, but let's keep it as it is public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data"; public static final String FILE_MODIFIED = "modified"; public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data"; diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt index a2411d25c54..9ec3b0e8268 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt @@ -32,7 +32,6 @@ import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_ETAG import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_ETAG_IN_CONFLICT import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_IS_DOWNLOADING import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_KEEP_IN_SYNC -import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_LAST_SYNC_DATE import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_MODIFIED import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA From 4e5567f2f47f034f7671dc34c4ebd10338c79c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 31 Jan 2023 16:50:23 +0100 Subject: [PATCH 064/152] Adapt tests after attribute removal --- .../com/owncloud/android/domain/files/model/OCFileTest.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/owncloudDomain/src/test/java/com/owncloud/android/domain/files/model/OCFileTest.kt b/owncloudDomain/src/test/java/com/owncloud/android/domain/files/model/OCFileTest.kt index 70bdb558337..bb421c0f5c6 100644 --- a/owncloudDomain/src/test/java/com/owncloud/android/domain/files/model/OCFileTest.kt +++ b/owncloudDomain/src/test/java/com/owncloud/android/domain/files/model/OCFileTest.kt @@ -47,7 +47,6 @@ class OCFileTest { OC_FILE.treeEtag, OC_FILE.availableOfflineStatus, OC_FILE.lastSyncDateForData, - OC_FILE.lastSyncDateForProperties, OC_FILE.needsToUpdateThumbnail, OC_FILE.modifiedAtLastSyncForData, OC_FILE.etagInConflict, @@ -73,7 +72,6 @@ class OCFileTest { treeEtag = OC_FILE.treeEtag, availableOfflineStatus = OC_FILE.availableOfflineStatus, lastSyncDateForData = OC_FILE.lastSyncDateForData, - lastSyncDateForProperties = OC_FILE.lastSyncDateForProperties, needsToUpdateThumbnail = OC_FILE.needsToUpdateThumbnail, modifiedAtLastSyncForData = OC_FILE.modifiedAtLastSyncForData, etagInConflict = OC_FILE.etagInConflict, @@ -105,7 +103,6 @@ class OCFileTest { OC_FILE.treeEtag, OC_FILE.availableOfflineStatus, OC_FILE.lastSyncDateForData, - OC_FILE.lastSyncDateForProperties, OC_FILE.needsToUpdateThumbnail, OC_FILE.modifiedAtLastSyncForData, OC_FILE.etagInConflict, @@ -131,7 +128,6 @@ class OCFileTest { treeEtag = OC_FILE.treeEtag, availableOfflineStatus = OC_FILE.availableOfflineStatus, lastSyncDateForData = OC_FILE.lastSyncDateForData, - lastSyncDateForProperties = OC_FILE.lastSyncDateForProperties, needsToUpdateThumbnail = OC_FILE.needsToUpdateThumbnail, modifiedAtLastSyncForData = OC_FILE.modifiedAtLastSyncForData, etagInConflict = OC_FILE.etagInConflict, From d84d4a337f18dfa4e17557993b9dfd8d6d694762 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 25 Jan 2023 17:49:43 +0100 Subject: [PATCH 065/152] Created new SpaceCursor for the DocumentsProvider --- .../documentsprovider/cursors/SpaceCursor.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt new file mode 100644 index 00000000000..3d8f7baaece --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt @@ -0,0 +1,49 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.presentation.documentsprovider.cursors + +import android.database.MatrixCursor +import android.provider.DocumentsContract.Document +import com.owncloud.android.R +import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.presentation.documentsprovider.cursors.FileCursor.Companion.DEFAULT_DOCUMENT_PROJECTION + +class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) { + + fun addSpace(space: OCSpace) { + val iconRes = R.drawable.ic_spaces + val mimeType = Document.MIME_TYPE_DIR + var flags = Document.FLAG_DIR_SUPPORTS_CREATE + + if (space.isPersonal) { + flags = flags or Document.FLAG_SUPPORTS_WRITE + } + + newRow() + .add(Document.COLUMN_DOCUMENT_ID, space.id) + .add(Document.COLUMN_DISPLAY_NAME, space.name) + .add(Document.COLUMN_LAST_MODIFIED, space.lastModifiedDateTime) + .add(Document.COLUMN_SIZE, space.quota?.used) + .add(Document.COLUMN_FLAGS, flags) + .add(Document.COLUMN_ICON, iconRes) + .add(Document.COLUMN_MIME_TYPE, mimeType) + } +} From f758c2c136da9797afcf2ff514cf517b36a81d2b Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 26 Jan 2023 13:55:42 +0100 Subject: [PATCH 066/152] Show list of spaces in DocumentsProvider for oCIS accounts --- .../dependecyinjection/UseCaseModule.kt | 4 +- .../DocumentsStorageProvider.kt | 81 +++++++++++++------ .../documentsprovider/cursors/FileCursor.kt | 2 +- .../documentsprovider/cursors/RootCursor.kt | 22 +++-- .../documentsprovider/cursors/SpaceCursor.kt | 19 ++++- .../files/datasources/RemoteFileDataSource.kt | 1 + .../implementation/OCRemoteFileDataSource.kt | 4 +- .../datasources/LocalSpacesDataSource.kt | 1 + .../implementation/OCLocalSpacesDataSource.kt | 44 +++++++++- .../android/data/spaces/db/SpacesDao.kt | 12 +++ .../android/data/spaces/db/SpacesEntity.kt | 4 +- .../spaces/repository/OCSpacesRepository.kt | 3 + .../android/domain/spaces/SpacesRepository.kt | 1 + ...rsonalAndProjectSpacesForAccountUseCase.kt | 36 +++++++++ 14 files changed, 196 insertions(+), 38 deletions(-) create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetPersonalAndProjectSpacesForAccountUseCase.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 6027303b166..0b2c2aaf7ea 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -77,6 +77,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.EditPublicShareAsyncU import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase +import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesForAccountUseCase import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAccountUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase @@ -176,9 +177,10 @@ val useCaseModule = module { factory { RefreshSharesFromServerAsyncUseCase(get()) } // Spaces - factory { RefreshSpacesFromServerAsyncUseCase(get()) } + factory { GetPersonalAndProjectSpacesForAccountUseCase(get()) } factory { GetProjectSpacesWithSpecialsForAccountAsStreamUseCase(get()) } factory { GetSpaceWithSpecialsByIdForAccountUseCase(get()) } + factory { RefreshSpacesFromServerAsyncUseCase(get()) } // Transfers factory { CancelDownloadForFileUseCase(get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index 83f03f680fd..0d3dd70f923 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -6,18 +6,20 @@ * @author David González Verdugo * @author Abel García de Prada * @author Shashvat Kedia + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2015 Bartosz Przybylski - * Copyright (C) 2020 ownCloud GmbH. - *

+ * Copyright (C) 2023 ownCloud GmbH. + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation. - *

+ * * 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 . */ @@ -26,6 +28,7 @@ package com.owncloud.android.presentation.documentsprovider import android.content.res.AssetFileDescriptor import android.database.Cursor +import android.database.MatrixCursor import android.graphics.Point import android.net.Uri import android.os.CancellationSignal @@ -37,10 +40,12 @@ import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.data.preferences.datasources.SharedPreferencesProvider import com.owncloud.android.domain.UseCaseResult +import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase import com.owncloud.android.domain.exceptions.NoConnectionWithServerException import com.owncloud.android.domain.exceptions.validation.FileNameException import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.model.OCFile.Companion.PATH_SEPARATOR +import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PARENT_ID import com.owncloud.android.domain.files.usecases.CopyFileUseCase import com.owncloud.android.domain.files.usecases.CreateFolderAsyncUseCase import com.owncloud.android.domain.files.usecases.GetFileByIdUseCase @@ -49,9 +54,11 @@ import com.owncloud.android.domain.files.usecases.GetFolderContentUseCase import com.owncloud.android.domain.files.usecases.MoveFileUseCase import com.owncloud.android.domain.files.usecases.RemoveFileUseCase import com.owncloud.android.domain.files.usecases.RenameFileUseCase +import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesForAccountUseCase import com.owncloud.android.presentation.authentication.AccountUtils import com.owncloud.android.presentation.documentsprovider.cursors.FileCursor import com.owncloud.android.presentation.documentsprovider.cursors.RootCursor +import com.owncloud.android.presentation.documentsprovider.cursors.SpaceCursor import com.owncloud.android.presentation.settings.security.SettingsSecurityFragment.Companion.PREFERENCE_LOCK_ACCESS_FROM_DOCUMENT_PROVIDER import com.owncloud.android.usecases.synchronization.SynchronizeFileUseCase import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase @@ -169,29 +176,43 @@ class DocumentsStorageProvider : DocumentsProvider() { ): Cursor { val folderId = parentDocumentId.toLong() - val resultCursor = FileCursor(projection) + val resultCursor: MatrixCursor - // Create result cursor before syncing folder again, in order to enable faster loading - getFolderContent(folderId.toInt()).forEach { file -> resultCursor.addFile(file) } + if (folderId == ROOT_PARENT_ID) { + resultCursor = SpaceCursor(projection) - //Create notification listener - val notifyUri: Uri = toNotifyUri(toUri(parentDocumentId)) - resultCursor.setNotificationUri(context?.contentResolver, notifyUri) - - /** - * This will start syncing the current folder. User will only see this after updating his view with a - * pull down, or by accessing the folder again. - */ - if (requestedFolderIdForSync != folderId && syncRequired) { - // register for sync - syncDirectoryWithServer(parentDocumentId) - requestedFolderIdForSync = folderId - resultCursor.setMoreToSync(true) + val getPersonalAndProjectSpacesForAccountUseCase: GetPersonalAndProjectSpacesForAccountUseCase by inject() + getPersonalAndProjectSpacesForAccountUseCase.execute( + GetPersonalAndProjectSpacesForAccountUseCase.Params( + accountName = AccountUtils.getCurrentOwnCloudAccount(context).name, + ) + ).forEach { space -> resultCursor.addSpace(space, context)} } else { - requestedFolderIdForSync = -1 + resultCursor = FileCursor(projection) + + // Create result cursor before syncing folder again, in order to enable faster loading + getFolderContent(folderId.toInt()).forEach { file -> resultCursor.addFile(file) } + + /** + * This will start syncing the current folder. User will only see this after updating his view with a + * pull down, or by accessing the folder again. + */ + if (requestedFolderIdForSync != folderId && syncRequired) { + // register for sync + syncDirectoryWithServer(parentDocumentId) + requestedFolderIdForSync = folderId + resultCursor.setMoreToSync(true) + } else { + requestedFolderIdForSync = -1 + } + + syncRequired = true } - syncRequired = true + // Create notification listener + val notifyUri: Uri = toNotifyUri(toUri(parentDocumentId)) + resultCursor.setNotificationUri(context?.contentResolver, notifyUri) + return resultCursor } @@ -202,6 +223,12 @@ class DocumentsStorageProvider : DocumentsProvider() { addFile(fileToUpload) } + if (documentId.toLong() == ROOT_PARENT_ID) { + return SpaceCursor(projection).apply { + addRootForSpaces() + } + } + return FileCursor(projection).apply { addFile(getFileByIdOrException(documentId.toInt())) } @@ -222,7 +249,15 @@ class DocumentsStorageProvider : DocumentsProvider() { } for (account in accounts) { - result.addRoot(account, contextApp) + val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase by inject() + val capabilities = getStoredCapabilitiesUseCase.execute( + GetStoredCapabilitiesUseCase.Params( + accountName = account.name + ) + ) + val spacesAllowed = capabilities?.isSpacesAllowed() + + result.addRoot(account, contextApp, spacesAllowed) } return result } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/FileCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/FileCursor.kt index 59594d56eb8..9041ed07bb2 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/FileCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/FileCursor.kt @@ -67,7 +67,7 @@ class FileCursor(projection: Array?) : MatrixCursor(projection ?: DEFAUL } companion object { - private val DEFAULT_DOCUMENT_PROJECTION = arrayOf( + val DEFAULT_DOCUMENT_PROJECTION = arrayOf( Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE, diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt index b610771803b..e807449a466 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt @@ -3,18 +3,20 @@ * * @author Bartosz Przybylski * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * * Copyright (C) 2015 Bartosz Przybylski - * Copyright (C) 2020 ownCloud GmbH. - *

+ * Copyright (C) 2023 ownCloud GmbH. + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation. - *

+ * * 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 . */ @@ -31,15 +33,21 @@ import com.owncloud.android.domain.files.model.OCFile class RootCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION) { - fun addRoot(account: Account, context: Context) { + fun addRoot(account: Account, context: Context, spacesAllowed: Boolean?) { val manager = FileDataStorageManager(account) - val mainDir = manager.getFileByPath(OCFile.ROOT_PATH) + val mainDirId = if (spacesAllowed == true) { + // Directory with all the spaces + OCFile.ROOT_PARENT_ID + } else { + // Root directory of the personal space (oCIS) or "Files" (oC10) + manager.getFileByPath(OCFile.ROOT_PATH)?.id + } val flags = Root.FLAG_SUPPORTS_SEARCH or Root.FLAG_SUPPORTS_CREATE newRow() .add(Root.COLUMN_ROOT_ID, account.name) - .add(Root.COLUMN_DOCUMENT_ID, mainDir?.id) + .add(Root.COLUMN_DOCUMENT_ID, mainDirId) .add(Root.COLUMN_SUMMARY, account.name) .add(Root.COLUMN_TITLE, context.getString(R.string.app_name)) .add(Root.COLUMN_ICON, R.mipmap.icon) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt index 3d8f7baaece..7914a1696df 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt @@ -20,6 +20,7 @@ package com.owncloud.android.presentation.documentsprovider.cursors +import android.content.Context import android.database.MatrixCursor import android.provider.DocumentsContract.Document import com.owncloud.android.R @@ -28,7 +29,7 @@ import com.owncloud.android.presentation.documentsprovider.cursors.FileCursor.Co class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) { - fun addSpace(space: OCSpace) { + fun addSpace(space: OCSpace, context: Context?) { val iconRes = R.drawable.ic_spaces val mimeType = Document.MIME_TYPE_DIR var flags = Document.FLAG_DIR_SUPPORTS_CREATE @@ -37,13 +38,27 @@ class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAU flags = flags or Document.FLAG_SUPPORTS_WRITE } + val name = if (space.isPersonal) context?.getString(R.string.bottom_nav_personal) else space.name + newRow() .add(Document.COLUMN_DOCUMENT_ID, space.id) - .add(Document.COLUMN_DISPLAY_NAME, space.name) + .add(Document.COLUMN_DISPLAY_NAME, name) .add(Document.COLUMN_LAST_MODIFIED, space.lastModifiedDateTime) .add(Document.COLUMN_SIZE, space.quota?.used) .add(Document.COLUMN_FLAGS, flags) .add(Document.COLUMN_ICON, iconRes) .add(Document.COLUMN_MIME_TYPE, mimeType) } + + fun addRootForSpaces() { + val mimeType = Document.MIME_TYPE_DIR + + newRow() + .add(Document.COLUMN_DOCUMENT_ID, 0) + .add(Document.COLUMN_DISPLAY_NAME, "Spaces") + .add(Document.COLUMN_LAST_MODIFIED, null) + .add(Document.COLUMN_SIZE, null) + .add(Document.COLUMN_FLAGS, 0) + .add(Document.COLUMN_MIME_TYPE, mimeType) + } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index 71d523f5817..560b892f0b1 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -64,6 +64,7 @@ interface RemoteFileDataSource { fun refreshFolder( remotePath: String, accountName: String, + spaceWebDavUrl: String? = null, ): List fun deleteFile( diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index 652f166f619..0ddcdffe383 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -134,11 +134,13 @@ class OCRemoteFileDataSource( override fun refreshFolder( remotePath: String, accountName: String, + spaceWebDavUrl: String?, ): List = // Assert not null, service should return an empty list if no files there. executeRemoteOperation { clientManager.getFileService(accountName).refreshFolder( - remotePath = remotePath + remotePath = remotePath, + spaceWebDavUrl = spaceWebDavUrl, ) }.let { listOfRemote -> listOfRemote.map { remoteFile -> remoteFile.toModel() } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt index 7fdc5bc31f3..5e0300c779a 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/LocalSpacesDataSource.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.Flow interface LocalSpacesDataSource { fun saveSpacesForAccount(listOfSpaces: List) fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> + fun getPersonalAndProjectSpacesForAccount(accountName: String): List fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace fun getWebDavUrlForSpace(spaceId: String?, accountName: String): String? fun deleteSpacesForAccount(accountName: String) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index f7131a90c51..899dfed38c3 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -67,6 +67,10 @@ class OCLocalSpacesDataSource( } } + override fun getPersonalAndProjectSpacesForAccount(accountName: String): List { + return spacesDao.getPersonalAndProjectSpacesForAccount(accountName).map { it.toModel() } + } + override fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace { return spacesDao.getSpaceWithSpecialsByIdForAccount(spaceId, accountName).toModel() } @@ -104,7 +108,7 @@ class OCLocalSpacesDataSource( used = spaceQuotaEntity.used, ) }, - root = space.root!!.let { spaceRootEntity -> + root = space.root.let { spaceRootEntity -> SpaceRoot( eTag = spaceRootEntity.eTag, id = spaceRootEntity.id, @@ -137,6 +141,44 @@ class OCLocalSpacesDataSource( webDavUrl = webDavUrl ) + @VisibleForTesting + fun SpacesEntity.toModel() = + OCSpace( + accountName = accountName, + driveAlias = driveAlias, + driveType = driveType, + id = id, + lastModifiedDateTime = lastModifiedDateTime, + name = name, + owner = ownerId?.let { spaceOwnerIdEntity -> + SpaceOwner( + user = SpaceUser( + id = spaceOwnerIdEntity + ) + ) + }, + quota = quota?.let { spaceQuotaEntity -> + SpaceQuota( + remaining = spaceQuotaEntity.remaining, + state = spaceQuotaEntity.state, + total = spaceQuotaEntity.total, + used = spaceQuotaEntity.used, + ) + }, + root = root.let { spaceRootEntity -> + SpaceRoot( + eTag = spaceRootEntity.eTag, + id = spaceRootEntity.id, + permissions = null, + webDavUrl = spaceRootEntity.webDavUrl, + deleted = spaceRootEntity.deleteState?.let { SpaceDeleted(state = it) }, + ) + }, + webUrl = webUrl, + description = description, + special = null, + ) + @VisibleForTesting fun OCSpace.toEntity() = SpacesEntity( diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index 3bb8b34f8a7..7c9f0dfc172 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -106,6 +106,11 @@ interface SpacesDao { accountName: String, ): Flow> + @Query(SELECT_PERSONAL_AND_PROJECT_SPACES_FOR_ACCOUNT) + fun getPersonalAndProjectSpacesForAccount( + accountName: String, + ): List + @Query(SELECT_SPACE_BY_ID_FOR_ACCOUNT) fun getSpaceWithSpecialsByIdForAccount( spaceId: String?, @@ -144,6 +149,13 @@ interface SpacesDao { ORDER BY name COLLATE NOCASE ASC """ + private const val SELECT_PERSONAL_AND_PROJECT_SPACES_FOR_ACCOUNT = """ + SELECT * + FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} + WHERE $SPACES_ACCOUNT_NAME = :accountName AND ($SPACES_DRIVE_TYPE LIKE '$DRIVE_TYPE_PROJECT' OR $SPACES_DRIVE_TYPE LIKE '$DRIVE_TYPE_PERSONAL') + ORDER BY $SPACES_DRIVE_TYPE ASC, name COLLATE NOCASE ASC + """ + private const val SELECT_SPACE_BY_ID_FOR_ACCOUNT = """ SELECT * FROM ${ProviderMeta.ProviderTableMeta.SPACES_TABLE_NAME} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt index b01a279c948..7145148acb2 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesEntity.kt @@ -4,7 +4,7 @@ * @author Abel García de Prada * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -58,7 +58,7 @@ data class SpacesEntity( @Embedded val quota: SpaceQuotaEntity?, @Embedded - val root: SpaceRootEntity?, + val root: SpaceRootEntity, @ColumnInfo(name = SPACES_WEB_URL) val webUrl: String, val description: String?, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt index de77a3bd20e..a000e856bda 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -38,6 +38,9 @@ class OCSpacesRepository( override fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String) = localSpacesDataSource.getProjectSpacesWithSpecialsForAccountAsFlow(accountName) + override fun getPersonalAndProjectSpacesForAccount(accountName: String) = + localSpacesDataSource.getPersonalAndProjectSpacesForAccount(accountName) + override fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String) = localSpacesDataSource.getSpaceWithSpecialsByIdForAccount(spaceId, accountName) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt index 2596ef22394..1534fc5f750 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -27,5 +27,6 @@ import kotlinx.coroutines.flow.Flow interface SpacesRepository { fun refreshSpacesForAccount(accountName: String) fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> + fun getPersonalAndProjectSpacesForAccount(accountName: String): List fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetPersonalAndProjectSpacesForAccountUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetPersonalAndProjectSpacesForAccountUseCase.kt new file mode 100644 index 00000000000..21f8c30a6b6 --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetPersonalAndProjectSpacesForAccountUseCase.kt @@ -0,0 +1,36 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.usecases + +import com.owncloud.android.domain.BaseUseCase +import com.owncloud.android.domain.spaces.SpacesRepository +import com.owncloud.android.domain.spaces.model.OCSpace + +class GetPersonalAndProjectSpacesForAccountUseCase( + private val spacesRepository: SpacesRepository +) : BaseUseCase, GetPersonalAndProjectSpacesForAccountUseCase.Params>() { + + override fun run(params: Params) = spacesRepository.getPersonalAndProjectSpacesForAccount(params.accountName) + + data class Params( + val accountName: String + ) +} From e6faa73ef08091da87ce91a6839c1da88000e5b2 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Mon, 30 Jan 2023 08:41:59 +0100 Subject: [PATCH 067/152] Able to navigate through spaces folders in DocumentsProvider --- .../documentsprovider/DocumentsStorageProvider.kt | 15 ++++++++++++++- .../documentsprovider/cursors/SpaceCursor.kt | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index 0d3dd70f923..51d88327170 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -46,6 +46,7 @@ import com.owncloud.android.domain.exceptions.validation.FileNameException import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.model.OCFile.Companion.PATH_SEPARATOR import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PARENT_ID +import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH import com.owncloud.android.domain.files.usecases.CopyFileUseCase import com.owncloud.android.domain.files.usecases.CreateFolderAsyncUseCase import com.owncloud.android.domain.files.usecases.GetFileByIdUseCase @@ -182,11 +183,23 @@ class DocumentsStorageProvider : DocumentsProvider() { resultCursor = SpaceCursor(projection) val getPersonalAndProjectSpacesForAccountUseCase: GetPersonalAndProjectSpacesForAccountUseCase by inject() + val getFileByRemotePathUseCase: GetFileByRemotePathUseCase by inject() + getPersonalAndProjectSpacesForAccountUseCase.execute( GetPersonalAndProjectSpacesForAccountUseCase.Params( accountName = AccountUtils.getCurrentOwnCloudAccount(context).name, ) - ).forEach { space -> resultCursor.addSpace(space, context)} + ).forEach { space -> + getFileByRemotePathUseCase.execute( + GetFileByRemotePathUseCase.Params( + owner = space.accountName, + remotePath = ROOT_PATH, + spaceId = space.id, + ) + ).getDataOrNull()?.let { rootFolder -> + resultCursor.addSpace(space, rootFolder.id, context) + } + } } else { resultCursor = FileCursor(projection) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt index 7914a1696df..224b0097564 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt @@ -29,7 +29,7 @@ import com.owncloud.android.presentation.documentsprovider.cursors.FileCursor.Co class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) { - fun addSpace(space: OCSpace, context: Context?) { + fun addSpace(space: OCSpace, rootFolderId: Long?, context: Context?) { val iconRes = R.drawable.ic_spaces val mimeType = Document.MIME_TYPE_DIR var flags = Document.FLAG_DIR_SUPPORTS_CREATE @@ -41,7 +41,7 @@ class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAU val name = if (space.isPersonal) context?.getString(R.string.bottom_nav_personal) else space.name newRow() - .add(Document.COLUMN_DOCUMENT_ID, space.id) + .add(Document.COLUMN_DOCUMENT_ID, rootFolderId) .add(Document.COLUMN_DISPLAY_NAME, name) .add(Document.COLUMN_LAST_MODIFIED, space.lastModifiedDateTime) .add(Document.COLUMN_SIZE, space.quota?.used) From cab2b096cbb658b47f37de1ceac7b786b7586f1b Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Mon, 30 Jan 2023 13:29:58 +0100 Subject: [PATCH 068/152] Fix folder creation operation in DocumentsProvider --- .../documentsprovider/DocumentsStorageProvider.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index 51d88327170..bbd6d910a74 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -421,7 +421,7 @@ class DocumentsStorageProvider : DocumentsProvider() { createFolderAsyncUseCase.execute(CreateFolderAsyncUseCase.Params(displayName, parentDocument)).run { checkUseCaseResult(this, parentDocument.id.toString()) val newPath = parentDocument.remotePath + displayName + File.separator - val newFolder = getFileByPathOrException(newPath, parentDocument.owner) + val newFolder = getFileByPathOrException(newPath, parentDocument.owner, parentDocument.spaceId) return newFolder.id.toString() } } @@ -505,9 +505,9 @@ class DocumentsStorageProvider : DocumentsProvider() { return result.getDataOrNull() ?: throw FileNotFoundException("File $id not found") } - private fun getFileByPathOrException(remotePath: String, accountName: String): OCFile { + private fun getFileByPathOrException(remotePath: String, accountName: String, spaceId: String? = null): OCFile { val getFileByRemotePathUseCase: GetFileByRemotePathUseCase by inject() - val result = getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(owner = accountName, remotePath = remotePath)) + val result = getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(owner = accountName, remotePath = remotePath, spaceId = spaceId)) return result.getDataOrNull() ?: throw FileNotFoundException("File $remotePath not found") } From 74ec397f215b7415bdd0555cc5297d6338256aad Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 31 Jan 2023 10:25:05 +0100 Subject: [PATCH 069/152] Tiny improvements in SpacesCursor --- .../DocumentsStorageProvider.kt | 2 +- .../documentsprovider/cursors/SpaceCursor.kt | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index bbd6d910a74..c695572f25b 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -238,7 +238,7 @@ class DocumentsStorageProvider : DocumentsProvider() { if (documentId.toLong() == ROOT_PARENT_ID) { return SpaceCursor(projection).apply { - addRootForSpaces() + addRootForSpaces(context) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt index 224b0097564..210895825ea 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt @@ -24,14 +24,13 @@ import android.content.Context import android.database.MatrixCursor import android.provider.DocumentsContract.Document import com.owncloud.android.R +import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PARENT_ID import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.presentation.documentsprovider.cursors.FileCursor.Companion.DEFAULT_DOCUMENT_PROJECTION class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) { fun addSpace(space: OCSpace, rootFolderId: Long?, context: Context?) { - val iconRes = R.drawable.ic_spaces - val mimeType = Document.MIME_TYPE_DIR var flags = Document.FLAG_DIR_SUPPORTS_CREATE if (space.isPersonal) { @@ -46,19 +45,17 @@ class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAU .add(Document.COLUMN_LAST_MODIFIED, space.lastModifiedDateTime) .add(Document.COLUMN_SIZE, space.quota?.used) .add(Document.COLUMN_FLAGS, flags) - .add(Document.COLUMN_ICON, iconRes) - .add(Document.COLUMN_MIME_TYPE, mimeType) + .add(Document.COLUMN_ICON, R.drawable.ic_spaces) + .add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR) } - fun addRootForSpaces() { - val mimeType = Document.MIME_TYPE_DIR - + fun addRootForSpaces(context: Context?) { newRow() - .add(Document.COLUMN_DOCUMENT_ID, 0) - .add(Document.COLUMN_DISPLAY_NAME, "Spaces") + .add(Document.COLUMN_DOCUMENT_ID, ROOT_PARENT_ID) + .add(Document.COLUMN_DISPLAY_NAME, context?.getString(R.string.bottom_nav_spaces)) .add(Document.COLUMN_LAST_MODIFIED, null) .add(Document.COLUMN_SIZE, null) .add(Document.COLUMN_FLAGS, 0) - .add(Document.COLUMN_MIME_TYPE, mimeType) + .add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR) } } From 8d7aba80831fce5e7313668d396f037394c7f052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 31 Jan 2023 11:29:03 +0100 Subject: [PATCH 070/152] Unify spaces entity to model function --- .../implementation/OCLocalSpacesDataSource.kt | 42 ++----------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 899dfed38c3..427e309abef 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -86,43 +86,7 @@ class OCLocalSpacesDataSource( companion object { @VisibleForTesting fun SpacesWithSpecials.toModel() = - OCSpace( - accountName = space.accountName, - driveAlias = space.driveAlias, - driveType = space.driveType, - id = space.id, - lastModifiedDateTime = space.lastModifiedDateTime, - name = space.name, - owner = space.ownerId?.let { spaceOwnerIdEntity -> - SpaceOwner( - user = SpaceUser( - id = spaceOwnerIdEntity - ) - ) - }, - quota = space.quota?.let { spaceQuotaEntity -> - SpaceQuota( - remaining = spaceQuotaEntity.remaining, - state = spaceQuotaEntity.state, - total = spaceQuotaEntity.total, - used = spaceQuotaEntity.used, - ) - }, - root = space.root.let { spaceRootEntity -> - SpaceRoot( - eTag = spaceRootEntity.eTag, - id = spaceRootEntity.id, - permissions = null, - webDavUrl = spaceRootEntity.webDavUrl, - deleted = spaceRootEntity.deleteState?.let { SpaceDeleted(state = it) }, - ) - }, - webUrl = space.webUrl, - description = space.description, - special = specials.map { - it.toModel() - } - ) + space.toModel(specials = specials) @VisibleForTesting fun SpaceSpecialEntity.toModel() = @@ -142,7 +106,7 @@ class OCLocalSpacesDataSource( ) @VisibleForTesting - fun SpacesEntity.toModel() = + fun SpacesEntity.toModel(specials: List? = null) = OCSpace( accountName = accountName, driveAlias = driveAlias, @@ -176,7 +140,7 @@ class OCLocalSpacesDataSource( }, webUrl = webUrl, description = description, - special = null, + special = specials?.map { it.toModel() }, ) @VisibleForTesting From 76ab69ec3a821d852b027e1b994749b11a45ab44 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 31 Jan 2023 16:12:46 +0100 Subject: [PATCH 071/152] New constant for spaces parent folder document ID --- .../documentsprovider/DocumentsStorageProvider.kt | 8 ++++---- .../presentation/documentsprovider/cursors/RootCursor.kt | 3 ++- .../presentation/documentsprovider/cursors/SpaceCursor.kt | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index c695572f25b..a0e0b2663f0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -45,7 +45,6 @@ import com.owncloud.android.domain.exceptions.NoConnectionWithServerException import com.owncloud.android.domain.exceptions.validation.FileNameException import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.model.OCFile.Companion.PATH_SEPARATOR -import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PARENT_ID import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH import com.owncloud.android.domain.files.usecases.CopyFileUseCase import com.owncloud.android.domain.files.usecases.CreateFolderAsyncUseCase @@ -179,7 +178,7 @@ class DocumentsStorageProvider : DocumentsProvider() { val resultCursor: MatrixCursor - if (folderId == ROOT_PARENT_ID) { + if (folderId == DISPLAY_SPACES_DOCUMENT_ID) { resultCursor = SpaceCursor(projection) val getPersonalAndProjectSpacesForAccountUseCase: GetPersonalAndProjectSpacesForAccountUseCase by inject() @@ -236,7 +235,7 @@ class DocumentsStorageProvider : DocumentsProvider() { addFile(fileToUpload) } - if (documentId.toLong() == ROOT_PARENT_ID) { + if (documentId.toLong() == DISPLAY_SPACES_DOCUMENT_ID) { return SpaceCursor(projection).apply { addRootForSpaces(context) } @@ -297,7 +296,7 @@ class DocumentsStorageProvider : DocumentsProvider() { ): Cursor { val result = FileCursor(projection) - val root = getFileByPathOrException(OCFile.ROOT_PATH, AccountUtils.getCurrentOwnCloudAccount(context).name) + val root = getFileByPathOrException(ROOT_PATH, AccountUtils.getCurrentOwnCloudAccount(context).name) for (f in findFiles(root, query)) { result.addFile(f) @@ -519,5 +518,6 @@ class DocumentsStorageProvider : DocumentsProvider() { companion object { const val NONEXISTENT_DOCUMENT_ID = "-1" + const val DISPLAY_SPACES_DOCUMENT_ID = -2L } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt index e807449a466..02c30a76c93 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt @@ -30,6 +30,7 @@ import android.provider.DocumentsContract.Root import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.presentation.documentsprovider.DocumentsStorageProvider class RootCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION) { @@ -37,7 +38,7 @@ class RootCursor(projection: Array?) : MatrixCursor(projection ?: DEFAUL val manager = FileDataStorageManager(account) val mainDirId = if (spacesAllowed == true) { // Directory with all the spaces - OCFile.ROOT_PARENT_ID + DocumentsStorageProvider.DISPLAY_SPACES_DOCUMENT_ID } else { // Root directory of the personal space (oCIS) or "Files" (oC10) manager.getFileByPath(OCFile.ROOT_PATH)?.id diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt index 210895825ea..e48006f169f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt @@ -24,8 +24,8 @@ import android.content.Context import android.database.MatrixCursor import android.provider.DocumentsContract.Document import com.owncloud.android.R -import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PARENT_ID import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.presentation.documentsprovider.DocumentsStorageProvider.Companion.DISPLAY_SPACES_DOCUMENT_ID import com.owncloud.android.presentation.documentsprovider.cursors.FileCursor.Companion.DEFAULT_DOCUMENT_PROJECTION class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) { @@ -51,7 +51,7 @@ class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAU fun addRootForSpaces(context: Context?) { newRow() - .add(Document.COLUMN_DOCUMENT_ID, ROOT_PARENT_ID) + .add(Document.COLUMN_DOCUMENT_ID, DISPLAY_SPACES_DOCUMENT_ID) .add(Document.COLUMN_DISPLAY_NAME, context?.getString(R.string.bottom_nav_spaces)) .add(Document.COLUMN_LAST_MODIFIED, null) .add(Document.COLUMN_SIZE, null) From 482df4e5058dd249345113c734f27927e0280970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Tue, 31 Jan 2023 19:24:22 +0100 Subject: [PATCH 072/152] Tweak to support spaces on the documents provider. Added the account name as the document id for spaces --- .../DocumentsStorageProvider.kt | 40 +++++++++++++------ .../documentsprovider/cursors/RootCursor.kt | 6 +-- .../documentsprovider/cursors/SpaceCursor.kt | 10 +++-- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index a0e0b2663f0..55870d549ad 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -174,11 +174,16 @@ class DocumentsStorageProvider : DocumentsProvider() { projection: Array?, sortOrder: String?, ): Cursor { - val folderId = parentDocumentId.toLong() - val resultCursor: MatrixCursor - if (folderId == DISPLAY_SPACES_DOCUMENT_ID) { + val folderId = try { + parentDocumentId.toLong() + } catch (numberFormatException: NumberFormatException) { + null + } + + // Folder id is null, so at this point we need to list the spaces for the account. + if (folderId == null) { resultCursor = SpaceCursor(projection) val getPersonalAndProjectSpacesForAccountUseCase: GetPersonalAndProjectSpacesForAccountUseCase by inject() @@ -186,7 +191,7 @@ class DocumentsStorageProvider : DocumentsProvider() { getPersonalAndProjectSpacesForAccountUseCase.execute( GetPersonalAndProjectSpacesForAccountUseCase.Params( - accountName = AccountUtils.getCurrentOwnCloudAccount(context).name, + accountName = parentDocumentId, ) ).forEach { space -> getFileByRemotePathUseCase.execute( @@ -200,6 +205,7 @@ class DocumentsStorageProvider : DocumentsProvider() { } } } else { + // Folder id is not null, so this is a regular folder resultCursor = FileCursor(projection) // Create result cursor before syncing folder again, in order to enable faster loading @@ -235,14 +241,22 @@ class DocumentsStorageProvider : DocumentsProvider() { addFile(fileToUpload) } - if (documentId.toLong() == DISPLAY_SPACES_DOCUMENT_ID) { - return SpaceCursor(projection).apply { - addRootForSpaces(context) - } + val fileId = try { + documentId.toInt() + } catch (numberFormatException: NumberFormatException) { + null } - return FileCursor(projection).apply { - addFile(getFileByIdOrException(documentId.toInt())) + return if (fileId != null) { + // file id is not null, this is a regular file. + FileCursor(projection).apply { + addFile(getFileByIdOrException(fileId)) + } + } else { + // file id is null, so at this point this is the root folder for spaces supported account. + SpaceCursor(projection).apply { + addRootForSpaces(context = context, accountName = documentId) + } } } @@ -279,7 +293,7 @@ class DocumentsStorageProvider : DocumentsProvider() { sizeHint: Point?, signal: CancellationSignal? ): AssetFileDescriptor { - + // TODO: Show thumbnail for spaces val file = getFileByIdOrException(documentId.toInt()) val realFile = File(file.storagePath) @@ -506,7 +520,8 @@ class DocumentsStorageProvider : DocumentsProvider() { private fun getFileByPathOrException(remotePath: String, accountName: String, spaceId: String? = null): OCFile { val getFileByRemotePathUseCase: GetFileByRemotePathUseCase by inject() - val result = getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(owner = accountName, remotePath = remotePath, spaceId = spaceId)) + val result = + getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(owner = accountName, remotePath = remotePath, spaceId = spaceId)) return result.getDataOrNull() ?: throw FileNotFoundException("File $remotePath not found") } @@ -518,6 +533,5 @@ class DocumentsStorageProvider : DocumentsProvider() { companion object { const val NONEXISTENT_DOCUMENT_ID = "-1" - const val DISPLAY_SPACES_DOCUMENT_ID = -2L } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt index 02c30a76c93..10e6e379033 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/RootCursor.kt @@ -30,15 +30,15 @@ import android.provider.DocumentsContract.Root import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.domain.files.model.OCFile -import com.owncloud.android.presentation.documentsprovider.DocumentsStorageProvider class RootCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION) { fun addRoot(account: Account, context: Context, spacesAllowed: Boolean?) { val manager = FileDataStorageManager(account) val mainDirId = if (spacesAllowed == true) { - // Directory with all the spaces - DocumentsStorageProvider.DISPLAY_SPACES_DOCUMENT_ID + // To display the list of spaces for an account, we need to do this trick. + // If the document id is not a number, we will know that it is the time to display the list of spaces for the account + account.name } else { // Root directory of the personal space (oCIS) or "Files" (oC10) manager.getFileByPath(OCFile.ROOT_PATH)?.id diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt index e48006f169f..bb9809eca3f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/cursors/SpaceCursor.kt @@ -25,7 +25,6 @@ import android.database.MatrixCursor import android.provider.DocumentsContract.Document import com.owncloud.android.R import com.owncloud.android.domain.spaces.model.OCSpace -import com.owncloud.android.presentation.documentsprovider.DocumentsStorageProvider.Companion.DISPLAY_SPACES_DOCUMENT_ID import com.owncloud.android.presentation.documentsprovider.cursors.FileCursor.Companion.DEFAULT_DOCUMENT_PROJECTION class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) { @@ -49,9 +48,14 @@ class SpaceCursor(projection: Array?) : MatrixCursor(projection ?: DEFAU .add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR) } - fun addRootForSpaces(context: Context?) { + /** + * Add root for spaces. Main difference is that we add the account name as the document id, + * so we need to take it into account in order to display the list of spaces or + * the actual list of files inside the folder. + */ + fun addRootForSpaces(context: Context?, accountName: String) { newRow() - .add(Document.COLUMN_DOCUMENT_ID, DISPLAY_SPACES_DOCUMENT_ID) + .add(Document.COLUMN_DOCUMENT_ID, accountName) .add(Document.COLUMN_DISPLAY_NAME, context?.getString(R.string.bottom_nav_spaces)) .add(Document.COLUMN_LAST_MODIFIED, null) .add(Document.COLUMN_SIZE, null) From 6c1f806e24adb4c1b43fad3f8602df45371ca5a0 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 7 Feb 2023 10:28:30 +0100 Subject: [PATCH 073/152] Disabled spaces are not shown in documents provider anymore --- .../DocumentsStorageProvider.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index 55870d549ad..5ddcb305d76 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -194,14 +194,16 @@ class DocumentsStorageProvider : DocumentsProvider() { accountName = parentDocumentId, ) ).forEach { space -> - getFileByRemotePathUseCase.execute( - GetFileByRemotePathUseCase.Params( - owner = space.accountName, - remotePath = ROOT_PATH, - spaceId = space.id, - ) - ).getDataOrNull()?.let { rootFolder -> - resultCursor.addSpace(space, rootFolder.id, context) + if (!space.isDisabled) { + getFileByRemotePathUseCase.execute( + GetFileByRemotePathUseCase.Params( + owner = space.accountName, + remotePath = ROOT_PATH, + spaceId = space.id, + ) + ).getDataOrNull()?.let { rootFolder -> + resultCursor.addSpace(space, rootFolder.id, context) + } } } } else { From 6b65a5546265b77db55fea8c290a6f4f67eec550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Thu, 2 Feb 2023 14:40:59 +0100 Subject: [PATCH 074/152] Support downloads from specific spaces Spaces will have their own hierarchy under the spaceId of each space --- .../dependecyinjection/UseCaseModule.kt | 2 + .../DocumentsStorageProvider.kt | 2 +- .../ReceiveExternalFilesActivity.java | 7 ++- .../CopyAndUploadContentUrisTask.java | 9 ++- .../android/ui/helpers/UriUploader.java | 10 +++- .../android/utils/FileStorageUtils.java | 4 +- .../android/workers/DownloadFileWorker.kt | 16 ++++-- .../40.json | 10 ++-- .../data/files/repository/OCFileRepository.kt | 6 +- .../spaces/repository/OCSpacesRepository.kt | 3 + .../data/storage/LocalStorageProvider.kt | 56 +++++++++++++++---- .../usecases/GetWebDavUrlForSpaceUseCase.kt | 37 ++++++++++++ .../android/domain/spaces/SpacesRepository.kt | 1 + 13 files changed, 128 insertions(+), 35 deletions(-) create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetWebDavUrlForSpaceUseCase.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 0b2c2aaf7ea..6ecf38d4e47 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -58,6 +58,7 @@ import com.owncloud.android.domain.files.usecases.GetFolderContentUseCase import com.owncloud.android.domain.files.usecases.GetFolderImagesUseCase import com.owncloud.android.domain.files.usecases.GetSearchFolderContentUseCase import com.owncloud.android.domain.files.usecases.GetSharedByLinkForAccountAsStreamUseCase +import com.owncloud.android.domain.files.usecases.GetWebDavUrlForSpaceUseCase import com.owncloud.android.domain.files.usecases.MoveFileUseCase import com.owncloud.android.domain.files.usecases.RemoveFileUseCase import com.owncloud.android.domain.files.usecases.RenameFileUseCase @@ -181,6 +182,7 @@ val useCaseModule = module { factory { GetProjectSpacesWithSpecialsForAccountAsStreamUseCase(get()) } factory { GetSpaceWithSpecialsByIdForAccountUseCase(get()) } factory { RefreshSpacesFromServerAsyncUseCase(get()) } + factory { GetWebDavUrlForSpaceUseCase(get()) } // Transfers factory { CancelDownloadForFileUseCase(get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt index 5ddcb305d76..3f964307d87 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/documentsprovider/DocumentsStorageProvider.kt @@ -448,7 +448,7 @@ class DocumentsStorageProvider : DocumentsProvider() { ): String { // We just need to return a Document ID, so we'll return an empty one. File does not exist in our db yet. // File will be created at [openDocument] method. - val tempDir = File(FileStorageUtils.getTemporalPath(parentDocument.owner)) + val tempDir = File(FileStorageUtils.getTemporalPath(parentDocument.owner, parentDocument.spaceId)) val newFile = File(tempDir, displayName) fileToUpload = OCFile( remotePath = parentDocument.remotePath + displayName, mimeType = mimeType, parentId = parentDocument.id, owner = parentDocument.owner diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java index 547237fef66..61523de9769 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java @@ -69,8 +69,6 @@ import com.owncloud.android.domain.files.model.OCFile; import com.owncloud.android.extensions.ActivityExtKt; import com.owncloud.android.extensions.ThrowableExtKt; -import com.owncloud.android.presentation.security.SecurityEnforced; -import com.owncloud.android.presentation.security.LockType; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.network.CertificateCombinedException; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; @@ -83,6 +81,8 @@ import com.owncloud.android.presentation.files.createfolder.CreateFolderDialogFragment; import com.owncloud.android.presentation.files.operations.FileOperation; import com.owncloud.android.presentation.files.operations.FileOperationsViewModel; +import com.owncloud.android.presentation.security.LockType; +import com.owncloud.android.presentation.security.SecurityEnforced; import com.owncloud.android.presentation.transfers.TransfersViewModel; import com.owncloud.android.ui.ReceiveExternalFilesViewModel; import com.owncloud.android.ui.adapter.ReceiveExternalFilesAdapter; @@ -583,6 +583,7 @@ public void uploadFiles() { mStreamsToUpload, mUploadPath, getAccount(), + mFile.getSpaceId(), true, // Show waiting dialog while file is being copied from private storage this // Copy temp task listener ); @@ -879,7 +880,7 @@ public void afterTextChanged(Editable editable) { } else if (fileName.length() == 0) { error = getString(R.string.uploader_upload_text_dialog_filename_error_empty); } else if (fileName.contains("/")) { - error = getString(R.string.filename_forbidden_characters); + error = getString(R.string.filename_forbidden_characters); } else { fileName += ".txt"; String filePath = savePlainTextToFile(fileName); diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java b/owncloudApp/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java index 3cbbf35d161..0edec4457d4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java @@ -83,14 +83,16 @@ public static Object[] makeParamsToExecute( Account account, Uri[] sourceUris, String uploadPath, - ContentResolver contentResolver + ContentResolver contentResolver, + String spaceId ) { return new Object[]{ account, sourceUris, uploadPath, - contentResolver + contentResolver, + spaceId }; } @@ -136,6 +138,7 @@ protected ResultCode doInBackground(Object[] params) { Uri[] uris = (Uri[]) params[1]; String uploadPath = (String) params[2]; ContentResolver leakedContentResolver = (ContentResolver) params[3]; + String spaceId = (String) params[4]; String currentRemotePath; ArrayList filesToUpload = new ArrayList<>(); @@ -144,7 +147,7 @@ protected ResultCode doInBackground(Object[] params) { currentUri = uris[i]; currentRemotePath = uploadPath + UriUtils.getDisplayNameForUri(currentUri, mAppContext); - fullTempPath = FileStorageUtils.getTemporalPath(account.name) + currentRemotePath; + fullTempPath = FileStorageUtils.getTemporalPath(account.name, spaceId) + currentRemotePath; inputStream = leakedContentResolver.openInputStream(currentUri); File cacheFile = new File(fullTempPath); File tempDir = cacheFile.getParentFile(); diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java index 56a0978a64c..57ed3cde0c4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java @@ -52,6 +52,7 @@ public class UriUploader { private String mUploadPath; private Account mAccount; + private String mSpaceId; private boolean mShowWaitingDialog; private UriUploaderResultCode mCode = UriUploaderResultCode.OK; @@ -69,6 +70,7 @@ public UriUploader( ArrayList uris, String uploadPath, Account account, + String spaceId, boolean showWaitingDialog, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener copyTmpTaskListener ) { @@ -76,6 +78,7 @@ public UriUploader( mUrisToUpload = uris; mUploadPath = uploadPath; mAccount = account; + mSpaceId = spaceId; mShowWaitingDialog = showWaitingDialog; mCopyTmpTaskListener = copyTmpTaskListener; } @@ -102,7 +105,7 @@ public UriUploaderResultCode uploadUris() { if (!contentUris.isEmpty()) { /// content: uris will be copied to temporary files before calling the upload usecase - copyThenUpload(contentUris.toArray(new Uri[0]), mUploadPath); + copyThenUpload(contentUris.toArray(new Uri[0]), mUploadPath, mSpaceId); // Listen to CopyAndUploadContentUrisTask before killing the app or a SecurityException may appear. // At least when receiving files to upload. @@ -128,7 +131,7 @@ public UriUploaderResultCode uploadUris() { * @param sourceUris Array of content:// URIs to the files to upload * @param uploadPath Absolute paths where we want to upload the selected files */ - private void copyThenUpload(Uri[] sourceUris, String uploadPath) { + private void copyThenUpload(Uri[] sourceUris, String uploadPath, String spaceId) { if (mShowWaitingDialog) { mActivity.showLoadingDialog(R.string.wait_for_tmp_copy_from_private_storage); } @@ -151,7 +154,8 @@ private void copyThenUpload(Uri[] sourceUris, String uploadPath) { mAccount, sourceUris, uploadPath, - mActivity.getContentResolver() + mActivity.getContentResolver(), + spaceId ) ); } diff --git a/owncloudApp/src/main/java/com/owncloud/android/utils/FileStorageUtils.java b/owncloudApp/src/main/java/com/owncloud/android/utils/FileStorageUtils.java index d05c4796928..1e256844eb7 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/utils/FileStorageUtils.java +++ b/owncloudApp/src/main/java/com/owncloud/android/utils/FileStorageUtils.java @@ -58,8 +58,8 @@ private static LocalStorageProvider getLocalStorageProvider() { /** * Get absolute path to tmp folder inside datafolder in sd-card for given accountName. */ - public static String getTemporalPath(String accountName) { - return getLocalStorageProvider().getTemporalPath(accountName); + public static String getTemporalPath(String accountName, String spaceId) { + return getLocalStorageProvider().getTemporalPath(accountName, spaceId); } /** diff --git a/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt b/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt index 34d3636a8b4..a1349e072dc 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt @@ -30,7 +30,6 @@ import androidx.work.WorkerParameters import androidx.work.workDataOf import at.bitfire.dav4jvm.exception.UnauthorizedException import com.owncloud.android.R -import com.owncloud.android.presentation.authentication.AccountUtils import com.owncloud.android.data.executeRemoteOperation import com.owncloud.android.data.storage.LocalStorageProvider import com.owncloud.android.domain.exceptions.CancelledException @@ -40,6 +39,7 @@ import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.usecases.CleanConflictUseCase import com.owncloud.android.domain.files.usecases.CleanWorkersUUIDUseCase import com.owncloud.android.domain.files.usecases.GetFileByIdUseCase +import com.owncloud.android.domain.files.usecases.GetWebDavUrlForSpaceUseCase import com.owncloud.android.domain.files.usecases.SaveDownloadWorkerUUIDUseCase import com.owncloud.android.domain.files.usecases.SaveFileOrFolderUseCase import com.owncloud.android.lib.common.OwnCloudAccount @@ -48,13 +48,14 @@ import com.owncloud.android.lib.common.SingleSessionManager import com.owncloud.android.lib.common.network.OnDatatransferProgressListener import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation import com.owncloud.android.presentation.authentication.ACTION_UPDATE_EXPIRED_TOKEN +import com.owncloud.android.presentation.authentication.AccountUtils import com.owncloud.android.presentation.authentication.EXTRA_ACCOUNT import com.owncloud.android.presentation.authentication.EXTRA_ACTION import com.owncloud.android.presentation.authentication.LoginActivity +import com.owncloud.android.presentation.transfers.TransferOperation.Download import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.errorhandling.ErrorMessageAdapter -import com.owncloud.android.presentation.transfers.TransferOperation.Download import com.owncloud.android.ui.preview.PreviewImageActivity import com.owncloud.android.ui.preview.PreviewImageFragment.Companion.canBePreviewed import com.owncloud.android.utils.DOWNLOAD_NOTIFICATION_CHANNEL_ID @@ -80,6 +81,7 @@ class DownloadFileWorker( private val getFileByIdUseCase: GetFileByIdUseCase by inject() private val saveFileOrFolderUseCase: SaveFileOrFolderUseCase by inject() + private val getWebdavUrlForSpaceUseCase: GetWebDavUrlForSpaceUseCase by inject() private val cleanConflictUseCase: CleanConflictUseCase by inject() private val saveDownloadWorkerUuidUseCase: SaveDownloadWorkerUUIDUseCase by inject() private val cleanWorkersUuidUseCase: CleanWorkersUUIDUseCase by inject() @@ -101,7 +103,7 @@ class DownloadFileWorker( * Temporal path where every file of this account will be downloaded. */ private val temporalFolderPath - get() = FileStorageUtils.getTemporalPath(account.name) + get() = FileStorageUtils.getTemporalPath(account.name, ocFile.spaceId) /** * Final path where this file should be stored. @@ -111,7 +113,7 @@ class DownloadFileWorker( */ private val finalLocationForFile: String get() = ocFile.storagePath.takeUnless { it.isNullOrBlank() } - ?: localStorageProvider.getDefaultSavePathFor(account.name, ocFile.remotePath) + ?: localStorageProvider.getDefaultSavePathFor(account.name, ocFile.remotePath, ocFile.spaceId) override suspend fun doWork(): Result { if (!areParametersValid()) return Result.failure() @@ -160,9 +162,13 @@ class DownloadFileWorker( ) ) + val spaceWebDavUrl = + getWebdavUrlForSpaceUseCase.execute(GetWebDavUrlForSpaceUseCase.Params(accountName = account.name, spaceId = ocFile.spaceId)) + downloadRemoteFileOperation = DownloadRemoteFileOperation( ocFile.remotePath, - temporalFolderPath + temporalFolderPath, + spaceWebDavUrl, ).also { it.addDatatransferProgressListener(this) } diff --git a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json index bbb066d3898..f2d409b4ece 100644 --- a/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json +++ b/owncloudData/schemas/com.owncloud.android.data.OwncloudDatabase/40.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 40, - "identityHash": "a16159ce0a3e084a5f3d044c85ea7fba", + "identityHash": "a0b1f509e19a5d74d809760baa02dc08", "entities": [ { "tableName": "folder_backup", @@ -753,7 +753,7 @@ }, { "tableName": "spaces", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_name` TEXT NOT NULL, `drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT, `name` TEXT NOT NULL, `owner_id` TEXT, `web_url` TEXT NOT NULL, `description` TEXT, `quota_remaining` INTEGER, `quota_state` TEXT, `quota_total` INTEGER, `quota_used` INTEGER, `root_etag` TEXT, `root_id` TEXT, `root_web_dav_url` TEXT, `root_deleted_state` TEXT, PRIMARY KEY(`account_name`, `space_id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_name` TEXT NOT NULL, `drive_alias` TEXT NOT NULL, `drive_type` TEXT NOT NULL, `space_id` TEXT NOT NULL, `last_modified_date_time` TEXT, `name` TEXT NOT NULL, `owner_id` TEXT, `web_url` TEXT NOT NULL, `description` TEXT, `quota_remaining` INTEGER, `quota_state` TEXT, `quota_total` INTEGER, `quota_used` INTEGER, `root_etag` TEXT, `root_id` TEXT NOT NULL, `root_web_dav_url` TEXT NOT NULL, `root_deleted_state` TEXT, PRIMARY KEY(`account_name`, `space_id`))", "fields": [ { "fieldPath": "accountName", @@ -843,13 +843,13 @@ "fieldPath": "root.id", "columnName": "root_id", "affinity": "TEXT", - "notNull": false + "notNull": true }, { "fieldPath": "root.webDavUrl", "columnName": "root_web_dav_url", "affinity": "TEXT", - "notNull": false + "notNull": true }, { "fieldPath": "root.deleteState", @@ -993,7 +993,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a16159ce0a3e084a5f3d044c85ea7fba')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a0b1f509e19a5d74d809760baa02dc08')" ] } } \ No newline at end of file diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index fa4b75d8c6e..3805e44ffa6 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -173,7 +173,7 @@ class OCFileRepository( val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath(expectedRemotePath, targetFile.owner).let { if (ocFile.isFolder) it.plus(File.separator) else it } - val finalStoragePath: String = localStorageProvider.getDefaultSavePathFor(targetFile.owner, finalRemotePath) + val finalStoragePath: String = localStorageProvider.getDefaultSavePathFor(targetFile.owner, finalRemotePath, targetFile.spaceId) // 2. Try to move files in server try { @@ -383,13 +383,13 @@ class OCFileRepository( localFileDataSource.renameFile( fileToRename = ocFile, finalRemotePath = newRemotePath, - finalStoragePath = localStorageProvider.getDefaultSavePathFor(ocFile.owner, newRemotePath) + finalStoragePath = localStorageProvider.getDefaultSavePathFor(ocFile.owner, newRemotePath, ocFile.spaceId) ) // 6. Update local storage localStorageProvider.moveLocalFile( ocFile = ocFile, - finalStoragePath = localStorageProvider.getDefaultSavePathFor(ocFile.owner, newRemotePath) + finalStoragePath = localStorageProvider.getDefaultSavePathFor(ocFile.owner, newRemotePath, ocFile.spaceId) ) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt index a000e856bda..463371e47fd 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -43,4 +43,7 @@ class OCSpacesRepository( override fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String) = localSpacesDataSource.getSpaceWithSpecialsByIdForAccount(spaceId, accountName) + + override fun getWebDavUrlForSpace(accountName: String, spaceId: String?): String? = + localSpacesDataSource.getWebDavUrlForSpace(accountName = accountName, spaceId = spaceId) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt b/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt index 8c942bc5081..6c5d4a5b699 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt @@ -7,7 +7,7 @@ * @author Shashvat Kedia * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -48,8 +48,8 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { /** * Get local storage path for accountName. */ - fun getAccountDirectoryPath( - accountName: String? + private fun getAccountDirectoryPath( + accountName: String ): String = getRootFolderPath() + File.separator + getEncodedAccountName(accountName) /** @@ -58,9 +58,18 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { * file. */ fun getDefaultSavePathFor( - accountName: String?, - remotePath: String - ): String = getAccountDirectoryPath(accountName) + remotePath + accountName: String, + remotePath: String, + spaceId: String?, + ): String { + val defaultPathWithoutSpace = getAccountDirectoryPath(accountName) + remotePath + + return if (spaceId != null) { + defaultPathWithoutSpace + getEncodedSpace(spaceId) + File.separator + } else { + defaultPathWithoutSpace + } + } /** * Get expected remote path for a file creation, rename, move etc @@ -79,8 +88,18 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { * Get absolute path to tmp folder inside datafolder in sd-card for given accountName. */ fun getTemporalPath( - accountName: String? - ): String = getRootFolderPath() + File.separator + TEMPORAL_FOLDER_NAME + File.separator + getEncodedAccountName(accountName) + accountName: String?, + spaceId: String? = null, + ): String { + val temporalPathWithoutSpace = + getRootFolderPath() + File.separator + TEMPORAL_FOLDER_NAME + File.separator + getEncodedAccountName(accountName) + + return if (spaceId != null) { + temporalPathWithoutSpace + File.separator + getEncodedSpace(spaceId) + } else { + temporalPathWithoutSpace + } + } fun getLogsPath(): String = getRootFolderPath() + File.separator + LOGS_FOLDER_NAME + File.separator @@ -132,6 +151,12 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { */ private fun getEncodedAccountName(accountName: String?): String = Uri.encode(accountName, "@") + /** + * URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, + * that can be in the accountName since 0.1.190B + */ + private fun getEncodedSpace(spaceId: String?): String = Uri.encode(spaceId, "-$") + fun moveLegacyToScopedStorage() { val timeInMillis = measureTimeMillis { moveFileOrFolderToScopedStorage(retrieveRootLegacyStorage()) @@ -184,7 +209,7 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { * Best-effort to remove the file locally. If storage path is null, let's try to remove it anyway. */ fun deleteLocalFile(ocFile: OCFile): Boolean { - val safeStoragePath = ocFile.storagePath ?: getDefaultSavePathFor(accountName = ocFile.owner, remotePath = ocFile.remotePath) + val safeStoragePath = ocFile.getStoragePathOrExpectedPathForFile() val fileToDelete = File(safeStoragePath) if (!fileToDelete.exists()) { @@ -195,7 +220,7 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { } fun moveLocalFile(ocFile: OCFile, finalStoragePath: String) { - val safeStoragePath = ocFile.storagePath ?: getDefaultSavePathFor(accountName = ocFile.owner, remotePath = ocFile.remotePath) + val safeStoragePath = ocFile.getStoragePathOrExpectedPathForFile() val fileToMove = File(safeStoragePath) if (!fileToMove.exists()) { @@ -246,6 +271,17 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { } } + /** + * Return the storage path if the file is already in the device storage or + * the expected storage path for the file in case it's not available locally yet. + */ + private fun OCFile.getStoragePathOrExpectedPathForFile() = + storagePath.takeUnless { it.isNullOrBlank() } ?: getDefaultSavePathFor( + accountName = owner, + remotePath = remotePath, + spaceId = spaceId, + ) + companion object { private const val LOGS_FOLDER_NAME = "logs" private const val TEMPORAL_FOLDER_NAME = "tmp" diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetWebDavUrlForSpaceUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetWebDavUrlForSpaceUseCase.kt new file mode 100644 index 00000000000..76ecf165c9b --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetWebDavUrlForSpaceUseCase.kt @@ -0,0 +1,37 @@ +/** + * ownCloud Android client application + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.files.usecases + +import com.owncloud.android.domain.BaseUseCase +import com.owncloud.android.domain.spaces.SpacesRepository + +class GetWebDavUrlForSpaceUseCase( + private val spacesRepository: SpacesRepository +) : BaseUseCase() { + + override fun run(params: Params): String? = spacesRepository.getWebDavUrlForSpace( + accountName = params.accountName, + spaceId = params.spaceId, + ) + + data class Params( + val accountName: String, + val spaceId: String?, + ) +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt index 1534fc5f750..4f367f3fb63 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -29,4 +29,5 @@ interface SpacesRepository { fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> fun getPersonalAndProjectSpacesForAccount(accountName: String): List fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace + fun getWebDavUrlForSpace(accountName: String, spaceId: String?): String? } From ecf4c57178aa76ac3e15ddfba174b78efc42087b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Thu, 2 Feb 2023 15:03:04 +0100 Subject: [PATCH 075/152] Fix storage path for downloads within spaces --- .../files/filelist/MainFileListFragment.kt | 2 +- .../owncloud/android/workers/DownloadFileWorker.kt | 2 +- .../android/data/storage/LocalStorageProvider.kt | 14 +++----------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index 76436cb44ea..c7138c023b5 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -146,7 +146,7 @@ class MainFileListFragment : Fragment(), true } if (isPickingAFolder() || getCurrentSpace() != null) { - menu.removeItem(menu.findItem(R.id.action_share_current_folder).itemId) + menu.findItem(R.id.action_share_current_folder)?.itemId?.let { menu.removeItem(it) } } else { menu.findItem(R.id.action_share_current_folder)?.setOnMenuItemClickListener { fileActions?.onShareFileClicked(mainFileListViewModel.getFile()) diff --git a/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt b/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt index a1349e072dc..c002f7c3eea 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt @@ -113,7 +113,7 @@ class DownloadFileWorker( */ private val finalLocationForFile: String get() = ocFile.storagePath.takeUnless { it.isNullOrBlank() } - ?: localStorageProvider.getDefaultSavePathFor(account.name, ocFile.remotePath, ocFile.spaceId) + ?: localStorageProvider.getDefaultSavePathFor(accountName = account.name, remotePath = ocFile.remotePath, spaceId = ocFile.spaceId) override suspend fun doWork(): Result { if (!areParametersValid()) return Result.failure() diff --git a/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt b/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt index 6c5d4a5b699..cd29a6f90b1 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/storage/LocalStorageProvider.kt @@ -62,12 +62,10 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { remotePath: String, spaceId: String?, ): String { - val defaultPathWithoutSpace = getAccountDirectoryPath(accountName) + remotePath - return if (spaceId != null) { - defaultPathWithoutSpace + getEncodedSpace(spaceId) + File.separator + getAccountDirectoryPath(accountName) + File.separator + spaceId + File.separator + remotePath } else { - defaultPathWithoutSpace + getAccountDirectoryPath(accountName) + remotePath } } @@ -95,7 +93,7 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { getRootFolderPath() + File.separator + TEMPORAL_FOLDER_NAME + File.separator + getEncodedAccountName(accountName) return if (spaceId != null) { - temporalPathWithoutSpace + File.separator + getEncodedSpace(spaceId) + temporalPathWithoutSpace + File.separator + spaceId } else { temporalPathWithoutSpace } @@ -151,12 +149,6 @@ sealed class LocalStorageProvider(private val rootFolderName: String) { */ private fun getEncodedAccountName(accountName: String?): String = Uri.encode(accountName, "@") - /** - * URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, - * that can be in the accountName since 0.1.190B - */ - private fun getEncodedSpace(spaceId: String?): String = Uri.encode(spaceId, "-$") - fun moveLegacyToScopedStorage() { val timeInMillis = measureTimeMillis { moveFileOrFolderToScopedStorage(retrieveRootLegacyStorage()) From 1dde14486032e209353b1e20a4e028a84d41205a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Wed, 8 Feb 2023 17:31:07 +0100 Subject: [PATCH 076/152] Fix send and open with options. They work now on spaces --- .../owncloud/android/ui/activity/FileDisplayActivity.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 93d9bb78e3c..41818da63cd 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -760,11 +760,11 @@ class FileDisplayActivity : FileActivity(), var currentFile: OCFile? = if (file == null) null else - storageManager.getFileByPath(file.remotePath) + storageManager.getFileByPath(file.remotePath, file.spaceId) val currentDir = if (currentDir == null) null else - storageManager.getFileByPath(currentDir!!.remotePath) + storageManager.getFileByPath(currentDir!!.remotePath, currentDir.spaceId) if (currentDir == null) { // current folder was removed from the server @@ -1166,10 +1166,10 @@ class FileDisplayActivity : FileActivity(), onWorkSucceeded = { CoroutineScope(Dispatchers.IO).launch { if (file.id == waitingToSend?.id) { - waitingToSend = storageManager.getFileByPath(file.remotePath) + waitingToSend = storageManager.getFileByPath(file.remotePath, file.spaceId) sendDownloadedFile() } else if (file.id == waitingToOpen?.id) { - waitingToOpen = storageManager.getFileByPath(file.remotePath) + waitingToOpen = storageManager.getFileByPath(file.remotePath, file.spaceId) openDownloadedFile() } } From 424c3b38f35bd8595f246a056943ed8a4f79c703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Thu, 9 Feb 2023 10:21:51 +0100 Subject: [PATCH 077/152] Fix gallery mode on spaces --- .../com/owncloud/android/ui/preview/PreviewImageActivity.kt | 4 ++-- .../owncloud/android/ui/preview/PreviewImagePagerAdapter.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt index 7495ce12c46..bbd1ad02c02 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt @@ -137,10 +137,10 @@ class PreviewImageActivity : FileActivity(), 0, file.remotePath.lastIndexOf(file.fileName) ) - var parentFolder = storageManager.getFileByPath(parentPath) + var parentFolder = storageManager.getFileByPath(parentPath, file.spaceId) if (parentFolder == null) { // should not be necessary - parentFolder = storageManager.getFileByPath(OCFile.ROOT_PATH) + parentFolder = storageManager.getFileByPath(OCFile.ROOT_PATH, file.spaceId) } val sharedPreferencesProvider: SharedPreferencesProvider by inject() diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt index 1bd5318a86b..31e7b7cc894 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt @@ -160,7 +160,7 @@ class PreviewImagePagerAdapter( // trigger the creation of new PreviewImageFragment to replace current FileDownloadFragment // only if the download succeeded. If not trigger an error notifyDataSetChanged() - } else fragment?.onSyncEvent(action, success, null) + } else fragment?.onSyncEvent(action, success, file) } } } From 6f785ef71efcea71b64f9354460b1e92cf88508e Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 2 Feb 2023 09:04:13 +0100 Subject: [PATCH 078/152] Kotlinize FolderPickerActivity --- .../SettingsPictureUploadsViewModel.kt | 4 +- .../SettingsVideoUploadsViewModel.kt | 4 +- .../ui/activity/FolderPickerActivity.java | 324 ------------------ .../ui/activity/FolderPickerActivity.kt | 283 +++++++++++++++ 4 files changed, 287 insertions(+), 328 deletions(-) delete mode 100644 owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java create mode 100644 owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsPictureUploadsViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsPictureUploadsViewModel.kt index 55cdec32675..e3f3f394856 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsPictureUploadsViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsPictureUploadsViewModel.kt @@ -35,7 +35,7 @@ import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.providers.AccountProvider import com.owncloud.android.providers.CoroutinesDispatcherProvider import com.owncloud.android.providers.WorkManagerProvider -import com.owncloud.android.ui.activity.UploadPathActivity +import com.owncloud.android.ui.activity.FolderPickerActivity import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -111,7 +111,7 @@ class SettingsPictureUploadsViewModel( fun getPictureUploadsSourcePath(): String? = _pictureUploads.value?.sourcePath fun handleSelectPictureUploadsPath(data: Intent?) { - val folderToUpload = data?.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER) + val folderToUpload = data?.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER) folderToUpload?.remotePath?.let { viewModelScope.launch(coroutinesDispatcherProvider.io) { savePictureUploadsConfigurationUseCase.execute( diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsVideoUploadsViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsVideoUploadsViewModel.kt index 4a0c9d0664e..66a8e870f5f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsVideoUploadsViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsVideoUploadsViewModel.kt @@ -35,7 +35,7 @@ import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.providers.AccountProvider import com.owncloud.android.providers.CoroutinesDispatcherProvider import com.owncloud.android.providers.WorkManagerProvider -import com.owncloud.android.ui.activity.UploadPathActivity +import com.owncloud.android.ui.activity.FolderPickerActivity import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -110,7 +110,7 @@ class SettingsVideoUploadsViewModel( fun getVideoUploadsSourcePath(): String? = _videoUploads.value?.sourcePath fun handleSelectVideoUploadsPath(data: Intent?) { - val folderToUpload = data?.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER) + val folderToUpload = data?.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER) folderToUpload?.remotePath?.let { viewModelScope.launch(coroutinesDispatcherProvider.io) { saveVideoUploadsConfigurationUseCase.execute( diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java deleted file mode 100644 index 26a4478c914..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java +++ /dev/null @@ -1,324 +0,0 @@ -/** - * ownCloud Android client application - * - * @author Shashvat Kedia - * @author David González Verdugo - * @author Abel García de Prada - * Copyright (C) 2020 ownCloud GmbH. - *

- * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - *

- * 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.owncloud.android.ui.activity; - -import android.accounts.Account; -import android.content.Intent; -import android.os.Bundle; -import android.os.Parcelable; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.LinearLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentTransaction; -import com.owncloud.android.R; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.domain.files.model.FileListOption; -import com.owncloud.android.domain.files.model.OCFile; -import com.owncloud.android.domain.spaces.model.OCSpace; -import com.owncloud.android.presentation.files.filelist.MainFileListFragment; -import com.owncloud.android.ui.fragment.FileFragment; -import com.owncloud.android.utils.PreferenceUtils; -import timber.log.Timber; - -import java.util.ArrayList; - -public class FolderPickerActivity extends FileActivity implements FileFragment.ContainerActivity, - OnClickListener, MainFileListFragment.FileActions { - - public static final String EXTRA_FOLDER = FolderPickerActivity.class.getCanonicalName() - + ".EXTRA_FOLDER"; - public static final String EXTRA_FILES = FolderPickerActivity.class.getCanonicalName() - + ".EXTRA_FILES"; - - private static final String TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS"; - - public static final String EXTRA_PICKER_OPTION = "EXTRA_PICKER_OPTION"; - - protected Button mCancelBtn; - protected Button mChooseBtn; - - @Override - protected void onCreate(Bundle savedInstanceState) { - Timber.d("onCreate() start"); - - super.onCreate(savedInstanceState); - - setContentView(R.layout.files_folder_picker); // beware - inflated in other activities too - - // Allow or disallow touches with other visible windows - LinearLayout filesFolderPickerLayout = findViewById(R.id.filesFolderPickerLayout); - filesFolderPickerLayout.setFilterTouchesWhenObscured( - PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) - ); - - if (savedInstanceState == null) { - initAndShowListOfFilesFragment(); - } - - // sets callback listeners for UI elements - initPickerListeners(); - - // Action bar setup - setupStandardToolbar(null, false, false, true); - - // Set action button text - setActionButtonText(); - - Timber.d("onCreate() end"); - } - - /** - * Called when the ownCloud {@link Account} associated to the Activity was just updated. - */ - @Override - protected void onAccountSet(boolean stateWasRecovered) { - super.onAccountSet(stateWasRecovered); - if (getAccount() != null) { - - updateFileFromDB(); - - OCFile folder = getFile(); - if (folder == null || !folder.isFolder()) { - // fall back to root folder - setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH, null)); - folder = getFile(); - } - - if (!stateWasRecovered) { - MainFileListFragment listOfFolders = getListOfFilesFragment(); - listOfFolders.navigateToFolder(folder); - } - - updateNavigationElementsInActionBar(); - } - } - - private void initAndShowListOfFilesFragment() { - OCFile safeInitialFolder; - if (getFile() == null) { - FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(getAccount()); - safeInitialFolder = fileDataStorageManager.getFileByPath(OCFile.ROOT_PATH, null); - } else { - safeInitialFolder = getFile(); - } - - MainFileListFragment mainListOfFiles = MainFileListFragment.newInstance(safeInitialFolder, true, FileListOption.ALL_FILES); - mainListOfFiles.setFileActions(this); - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.add(R.id.fragment_container, mainListOfFiles, TAG_LIST_OF_FOLDERS); - transaction.commit(); - } - - private void setActionButtonText() { - PickerMode actionButton = (PickerMode) getIntent().getSerializableExtra(EXTRA_PICKER_OPTION); - Button chooseButton = findViewById(R.id.folder_picker_btn_choose); - chooseButton.setText(getString(actionButton.getButtonString())); - } - - protected MainFileListFragment getListOfFilesFragment() { - Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(FolderPickerActivity.TAG_LIST_OF_FOLDERS); - if (listOfFiles != null) { - return (MainFileListFragment) listOfFiles; - } - Timber.e("Access to unexisting list of files fragment!!"); - return null; - } - - @Override - public void onSavedCertificate() { - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - boolean retval = true; - switch (item.getItemId()) { - case android.R.id.home: { - OCFile currentDir = getCurrentFolder(); - if (currentDir != null && currentDir.getParentId() != 0) { - onBackPressed(); - } - break; - } - default: - retval = super.onOptionsItemSelected(item); - } - return retval; - } - - protected OCFile getCurrentFolder() { - MainFileListFragment listOfFiles = getListOfFilesFragment(); - if (listOfFiles != null) { // should never be null, indeed - return listOfFiles.getCurrentFile(); - } - return null; - } - - @Override - public void onBackPressed() { - MainFileListFragment listOfFiles = getListOfFilesFragment(); - if (listOfFiles != null) { // should never be null, indeed - OCFile fileBeforeBrowsingUp = listOfFiles.getCurrentFile(); - if (fileBeforeBrowsingUp.getParentId() != null && - fileBeforeBrowsingUp.getParentId() == OCFile.ROOT_PARENT_ID - ) { - // If we are already at root, let's finish the picker. No sense to keep browsing up. - finish(); - return; - } - listOfFiles.onBrowseUp(); - setFile(listOfFiles.getCurrentFile()); - updateNavigationElementsInActionBar(); - } - } - - protected void updateNavigationElementsInActionBar() { - OCFile currentDir; - - try { - currentDir = getCurrentFolder(); - } catch (NullPointerException e) { - currentDir = getFile(); - } - - boolean atRoot = (currentDir == null || currentDir.getParentId() == 0); - updateStandardToolbar( - atRoot ? getString(R.string.default_display_name_for_root_folder) : currentDir.getFileName(), - !atRoot, - !atRoot - ); - } - - /** - * Set per-view controllers - */ - private void initPickerListeners() { - mCancelBtn = findViewById(R.id.folder_picker_btn_cancel); - mCancelBtn.setOnClickListener(this); - mChooseBtn = findViewById(R.id.folder_picker_btn_choose); - mChooseBtn.setOnClickListener(this); - } - - @Override - public void onClick(View v) { - if (v == mCancelBtn) { - finish(); - } else if (v == mChooseBtn) { - Intent i = getIntent(); - ArrayList targetFiles = i.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES); - - Intent data = new Intent(); - data.putExtra(EXTRA_FOLDER, getCurrentFolder()); - data.putParcelableArrayListExtra(EXTRA_FILES, targetFiles); - setResult(RESULT_OK, data); - - finish(); - } - } - - @Override - public void onCurrentFolderUpdated(@NonNull OCFile newCurrentFolder, @Nullable OCSpace currentSpace) { - updateNavigationElementsInActionBar(); - setFile(newCurrentFolder); - } - - @Override - public void initDownloadForSending(@NonNull OCFile file) { - - } - - @Override - public void cancelFileTransference(@NonNull ArrayList files) { - - } - - @Override - public void setBottomBarVisibility(boolean isVisible) { - - } - - @Override - public void onFileClicked(@NonNull OCFile file) { - // Nothing to do. Clicking on files is not allowed. - } - - @Override - public void onShareFileClicked(@NonNull OCFile file) { - // Nothing to do. Clicking on files is not allowed. - } - - @Override - public void syncFile(@NonNull OCFile file) { - // Nothing to do. Clicking on files is not allowed. - } - - @Override - public void openFile(@NonNull OCFile file) { - // Nothing to do. Clicking on files is not allowed. - } - - @Override - public void sendDownloadedFile(@NonNull OCFile file) { - // Nothing to do. Clicking on files is not allowed. - - } - - /** - * Nothing to do. Details can't be opened from {@link FolderPickerActivity} - * - * @param file {@link OCFile} whose details will be shown - */ - @Override - public void showDetails(OCFile file) { - - } - - public enum PickerMode { - MOVE, COPY, CAMERA_FOLDER; - - public Integer getButtonString() { - switch (this) { - case MOVE: - return R.string.folder_picker_move_here_button_text; - case COPY: - return R.string.folder_picker_copy_here_button_text; - default: - return R.string.folder_picker_choose_button_text; - } - } - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt new file mode 100644 index 00000000000..945393f7c5e --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -0,0 +1,283 @@ +/** + * ownCloud Android client application + * + * @author Shashvat Kedia + * @author David González Verdugo + * @author Abel García de Prada + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.ui.activity + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.View.OnClickListener +import android.widget.Button +import android.widget.LinearLayout +import com.owncloud.android.R +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.domain.files.model.FileListOption +import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.presentation.files.filelist.MainFileListFragment +import com.owncloud.android.ui.fragment.FileFragment +import com.owncloud.android.utils.PreferenceUtils +import timber.log.Timber + +open class FolderPickerActivity : FileActivity(), + FileFragment.ContainerActivity, + OnClickListener, + MainFileListFragment.FileActions { + + private lateinit var cancelButton: Button + private lateinit var chooseButton: Button + + override fun onCreate(savedInstanceState: Bundle?) { + Timber.d("onCreate() start") + + super.onCreate(savedInstanceState) + + setContentView(R.layout.files_folder_picker) // Beware - inflated in other activities too + + // Allow or disallow touches with other visible windows + val filesFolderPickerLayout = findViewById(R.id.filesFolderPickerLayout) + filesFolderPickerLayout.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) + + if (savedInstanceState == null) { + initAndShowListOfFilesFragment() + } + + // Set callback listeners for UI elements + initPickerListeners() + + // Action bar setup + setupStandardToolbar( + title = null, + displayHomeAsUpEnabled = false, + homeButtonEnabled = false, + displayShowTitleEnabled = true, + ) + + // Set action button text + setActionButtonText() + + Timber.d("onCreate() end") + } + + /** + * Called when the ownCloud {@link Account} associated to the Activity was just updated. + */ + override fun onAccountSet(stateWasRecovered: Boolean) { + super.onAccountSet(stateWasRecovered) + + if (account != null) { + updateFileFromDB() + + var folder = file + if (folder == null || !folder.isFolder) { + // Fall back to root folder + file = storageManager.getFileByPath(OCFile.ROOT_PATH) + folder = file + } + + if (!stateWasRecovered) { + val listOfFolders = getListOfFilesFragment() + listOfFolders?.navigateToFolder(folder) + } + + updateNavigationElementsInActionBar() + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.main_menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + var returnValue = true + when (item.itemId) { + android.R.id.home -> { + val currentDir = getCurrentFolder() + if (currentDir != null && currentDir.parentId != 0L) { + onBackPressed() + } + } + else -> returnValue = super.onOptionsItemSelected(item) + } + + return returnValue + } + + override fun onBackPressed() { + val listOfFiles = getListOfFilesFragment() + if (listOfFiles != null) { // Should never be null, indeed + val fileBeforeBrowsingUp = listOfFiles.getCurrentFile() + if (fileBeforeBrowsingUp.parentId != null && fileBeforeBrowsingUp.parentId == OCFile.ROOT_PARENT_ID) { + // If we are already at root, let's finish the picker. No sense to keep browsing up. + finish() + return + } + listOfFiles.onBrowseUp() + file = listOfFiles.getCurrentFile() + updateNavigationElementsInActionBar() + } + } + + override fun onClick(v: View?) { + when (v) { + cancelButton -> finish() + chooseButton -> { + val data = Intent().apply { + val targetFiles = intent.getParcelableArrayListExtra(EXTRA_FILES) + putExtra(EXTRA_FOLDER, getCurrentFolder()) + putParcelableArrayListExtra(EXTRA_FILES, targetFiles) + } + setResult(RESULT_OK, data) + + finish() + } + } + } + + override fun onCurrentFolderUpdated(newCurrentFolder: OCFile, currentSpace: OCSpace?) { + updateNavigationElementsInActionBar() + file = newCurrentFolder + } + + override fun initDownloadForSending(file: OCFile) { + // Nothing to do. Downloading files is not allowed. + } + + override fun cancelFileTransference(files: ArrayList) { + // Nothing to do. Transferring files is not allowed. + } + + override fun setBottomBarVisibility(isVisible: Boolean) { + // Nothing to do. No changes will be done in the bottom bar visibility. + } + + override fun onFileClicked(file: OCFile) { + // Nothing to do. Clicking on files is not allowed. + } + + override fun onShareFileClicked(file: OCFile) { + // Nothing to do. Clicking on files is not allowed. + } + + override fun syncFile(file: OCFile) { + // Nothing to do. Clicking on files is not allowed. + } + + override fun openFile(file: OCFile) { + // Nothing to do. Clicking on files is not allowed. + } + + override fun sendDownloadedFile(file: OCFile) { + // Nothing to do. Clicking on files is not allowed. + } + + override fun showDetails(file: OCFile) { + // Nothing to do. Details can't be opened here. + } + + private fun initAndShowListOfFilesFragment() { + val safeInitialFolder = if (file == null) { + val fileDataStorageManager = FileDataStorageManager(account) + fileDataStorageManager.getFileByPath(OCFile.ROOT_PATH) + } else { + file + } + + safeInitialFolder?.let { + val mainListOfFiles = MainFileListFragment.newInstance(it, true, FileListOption.ALL_FILES) + mainListOfFiles.fileActions = this + val transaction = supportFragmentManager.beginTransaction() + transaction.add(R.id.fragment_container, mainListOfFiles, TAG_LIST_OF_FOLDERS) + transaction.commit() + } + } + + /** + * Set per-view controllers + */ + private fun initPickerListeners() { + cancelButton = findViewById(R.id.folder_picker_btn_cancel) + cancelButton.setOnClickListener(this) + chooseButton = findViewById(R.id.folder_picker_btn_choose) + chooseButton.setOnClickListener(this) + } + + private fun setActionButtonText() { + val pickerMode = intent.getSerializableExtra(EXTRA_PICKER_OPTION) as PickerMode + chooseButton.text = getString(pickerMode.getButtonString()) + } + + protected fun getListOfFilesFragment(): MainFileListFragment? { + val listOfFiles = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FOLDERS) + if (listOfFiles != null) { + return listOfFiles as MainFileListFragment + } + Timber.e("Access to unexisting list of files fragment!!") + return null + } + + private fun getCurrentFolder(): OCFile? { + val listOfFiles = getListOfFilesFragment() + if (listOfFiles != null) { // should never be null, indeed + return listOfFiles.getCurrentFile() + } + return null + } + + protected fun updateNavigationElementsInActionBar() { + val currentDir = try { + getCurrentFolder() + } catch (e: NullPointerException) { + file + } + + val atRoot = (currentDir == null || currentDir.parentId == 0L) + updateStandardToolbar( + title = if (atRoot) getString(R.string.default_display_name_for_root_folder) else currentDir!!.fileName, + displayHomeAsUpEnabled = !atRoot, + homeButtonEnabled = !atRoot, + ) + } + + enum class PickerMode { + MOVE, COPY, CAMERA_FOLDER; + + fun getButtonString(): Int { + return when (this) { + MOVE -> R.string.folder_picker_move_here_button_text + COPY -> R.string.folder_picker_copy_here_button_text + CAMERA_FOLDER -> R.string.folder_picker_choose_button_text + } + } + } + + companion object { + const val EXTRA_FOLDER = "FOLDER_PICKER_EXTRA_FOLDER" + const val EXTRA_FILES = "FOLDER_PICKER_EXTRA_FILES" + const val EXTRA_PICKER_OPTION = "FOLDER_PICKER_EXTRA_PICKER_OPTION" + private const val TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS" + } +} From fdf50a5fbe2a9bd0d04d78e3e5596e0a159a8bcb Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 2 Feb 2023 09:55:37 +0100 Subject: [PATCH 079/152] Show only personal space in folder picker for camera uploads option --- .../ui/activity/FolderPickerActivity.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index 945393f7c5e..97fc73e9779 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -48,6 +48,7 @@ open class FolderPickerActivity : FileActivity(), private lateinit var cancelButton: Button private lateinit var chooseButton: Button + private lateinit var pickerMode: PickerMode override fun onCreate(savedInstanceState: Bundle?) { Timber.d("onCreate() start") @@ -60,13 +61,22 @@ open class FolderPickerActivity : FileActivity(), val filesFolderPickerLayout = findViewById(R.id.filesFolderPickerLayout) filesFolderPickerLayout.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) + pickerMode = intent.getSerializableExtra(EXTRA_PICKER_OPTION) as PickerMode + if (savedInstanceState == null) { - initAndShowListOfFilesFragment() + when (pickerMode) { + PickerMode.MOVE -> {} + PickerMode.COPY -> {} + PickerMode.CAMERA_FOLDER -> { + // Show the personal space + initAndShowListOfFilesFragment(spaceId = null) + } + } } // Set callback listeners for UI elements initPickerListeners() - + // Action bar setup setupStandardToolbar( title = null, @@ -198,10 +208,10 @@ open class FolderPickerActivity : FileActivity(), // Nothing to do. Details can't be opened here. } - private fun initAndShowListOfFilesFragment() { + private fun initAndShowListOfFilesFragment(spaceId: String?) { val safeInitialFolder = if (file == null) { val fileDataStorageManager = FileDataStorageManager(account) - fileDataStorageManager.getFileByPath(OCFile.ROOT_PATH) + fileDataStorageManager.getFileByPath(OCFile.ROOT_PATH, spaceId) } else { file } @@ -226,7 +236,6 @@ open class FolderPickerActivity : FileActivity(), } private fun setActionButtonText() { - val pickerMode = intent.getSerializableExtra(EXTRA_PICKER_OPTION) as PickerMode chooseButton.text = getString(pickerMode.getButtonString()) } @@ -265,7 +274,7 @@ open class FolderPickerActivity : FileActivity(), enum class PickerMode { MOVE, COPY, CAMERA_FOLDER; - fun getButtonString(): Int { + fun getButtonString(): Int { return when (this) { MOVE -> R.string.folder_picker_move_here_button_text COPY -> R.string.folder_picker_copy_here_button_text From d01d22f6ba0afa22b49cc3bc450d747ceed39aed Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 2 Feb 2023 11:14:47 +0100 Subject: [PATCH 080/152] Show only current space in folder picker for move option --- .../android/ui/activity/FolderPickerActivity.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index 97fc73e9779..662c0363dc0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -65,12 +65,17 @@ open class FolderPickerActivity : FileActivity(), if (savedInstanceState == null) { when (pickerMode) { - PickerMode.MOVE -> {} + PickerMode.MOVE -> { + // Show the space where the files come from + val targetFiles = intent.getParcelableArrayListExtra(EXTRA_FILES) + val spaceIdOfFiles = targetFiles?.get(0)?.spaceId + initAndShowListOfFilesFragment(spaceId = spaceIdOfFiles) + } PickerMode.COPY -> {} PickerMode.CAMERA_FOLDER -> { // Show the personal space initAndShowListOfFilesFragment(spaceId = null) - } + } } } @@ -216,6 +221,8 @@ open class FolderPickerActivity : FileActivity(), file } + file = safeInitialFolder + safeInitialFolder?.let { val mainListOfFiles = MainFileListFragment.newInstance(it, true, FileListOption.ALL_FILES) mainListOfFiles.fileActions = this From 8d5b788af3361b48dda1e55e05b0345675620601 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Thu, 2 Feb 2023 13:58:02 +0100 Subject: [PATCH 081/152] Show list of spaces in folder picker for copy option --- .../presentation/spaces/SpacesListFragment.kt | 12 +++++--- .../ui/activity/FileDisplayActivity.kt | 14 ++++++++-- .../ui/activity/FolderPickerActivity.kt | 28 +++++++++++++++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index 5a6b5959e7f..eee143c8189 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -30,13 +30,13 @@ import androidx.recyclerview.widget.GridLayoutManager import com.owncloud.android.R import com.owncloud.android.databinding.SpacesListFragmentBinding import com.owncloud.android.domain.files.model.FileListOption +import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.extensions.showErrorInSnackbar import com.owncloud.android.extensions.toDrawableRes import com.owncloud.android.extensions.toSubtitleStringRes import com.owncloud.android.extensions.toTitleStringRes -import com.owncloud.android.ui.activity.FileDisplayActivity import org.koin.androidx.viewmodel.ext.android.viewModel class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment() { @@ -47,6 +47,8 @@ class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment private lateinit var spacesListAdapter: SpacesListAdapter + var spacesActions: SpacesActions? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -83,9 +85,7 @@ class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment uiState.error?.let { showErrorInSnackbar(R.string.spaces_sync_failed, it) } uiState.rootFolderFromSelectedSpace?.let { - val parentActivity = requireActivity() as FileDisplayActivity - parentActivity.file = it - parentActivity.initAndShowListOfFiles() + spacesActions?.onSpaceClicked(it) } } } @@ -104,4 +104,8 @@ class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment override fun onItemClick(ocSpace: OCSpace) { spacesListViewModel.getRootFileForSpace(ocSpace) } + + interface SpacesActions { + fun onSpaceClicked(rootFolder: OCFile) + } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 41818da63cd..f8dd087edd4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -118,7 +118,8 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, SecurityEnforced, MainFileListFragment.FileActions, - MainFileListFragment.UploadActions { + MainFileListFragment.UploadActions, + SpacesListFragment.SpacesActions { private val job = Job() override val coroutineContext: CoroutineContext @@ -324,7 +325,7 @@ class FileDisplayActivity : FileActivity(), } } - fun initAndShowListOfFiles(fileListOption: FileListOption = FileListOption.ALL_FILES) { + private fun initAndShowListOfFiles(fileListOption: FileListOption = FileListOption.ALL_FILES) { val mainListOfFiles = MainFileListFragment.newInstance( initialFolderToDisplay = file, fileListOption = fileListOption, @@ -340,7 +341,9 @@ class FileDisplayActivity : FileActivity(), } private fun initAndShowListOfSpaces() { - val listOfSpaces = SpacesListFragment() + val listOfSpaces = SpacesListFragment().apply { + spacesActions = this@FileDisplayActivity + } val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.left_fragment_container, listOfSpaces, TAG_LIST_OF_SPACES) transaction.commit() @@ -1484,6 +1487,11 @@ class FileDisplayActivity : FileActivity(), ) } + override fun onSpaceClicked(rootFolder: OCFile) { + file = rootFolder + initAndShowListOfFiles() + } + companion object { private const val TAG_LIST_OF_FILES = "LIST_OF_FILES" private const val TAG_LIST_OF_SPACES = "LIST_OF_SPACES" diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index 662c0363dc0..1c43d763748 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -37,6 +37,7 @@ import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.presentation.files.filelist.MainFileListFragment +import com.owncloud.android.presentation.spaces.SpacesListFragment import com.owncloud.android.ui.fragment.FileFragment import com.owncloud.android.utils.PreferenceUtils import timber.log.Timber @@ -44,7 +45,8 @@ import timber.log.Timber open class FolderPickerActivity : FileActivity(), FileFragment.ContainerActivity, OnClickListener, - MainFileListFragment.FileActions { + MainFileListFragment.FileActions, + SpacesListFragment.SpacesActions { private lateinit var cancelButton: Button private lateinit var chooseButton: Button @@ -71,11 +73,14 @@ open class FolderPickerActivity : FileActivity(), val spaceIdOfFiles = targetFiles?.get(0)?.spaceId initAndShowListOfFilesFragment(spaceId = spaceIdOfFiles) } - PickerMode.COPY -> {} + PickerMode.COPY -> { + // Show the list of spaces + initAndShowListOfSpaces() + } PickerMode.CAMERA_FOLDER -> { // Show the personal space initAndShowListOfFilesFragment(spaceId = null) - } + } } } @@ -213,7 +218,12 @@ open class FolderPickerActivity : FileActivity(), // Nothing to do. Details can't be opened here. } - private fun initAndShowListOfFilesFragment(spaceId: String?) { + override fun onSpaceClicked(rootFolder: OCFile) { + file = rootFolder + initAndShowListOfFilesFragment() + } + + private fun initAndShowListOfFilesFragment(spaceId: String? = null) { val safeInitialFolder = if (file == null) { val fileDataStorageManager = FileDataStorageManager(account) fileDataStorageManager.getFileByPath(OCFile.ROOT_PATH, spaceId) @@ -227,11 +237,19 @@ open class FolderPickerActivity : FileActivity(), val mainListOfFiles = MainFileListFragment.newInstance(it, true, FileListOption.ALL_FILES) mainListOfFiles.fileActions = this val transaction = supportFragmentManager.beginTransaction() - transaction.add(R.id.fragment_container, mainListOfFiles, TAG_LIST_OF_FOLDERS) + transaction.replace(R.id.fragment_container, mainListOfFiles, TAG_LIST_OF_FOLDERS) transaction.commit() } } + private fun initAndShowListOfSpaces() { + val listOfSpaces = SpacesListFragment() + listOfSpaces.spacesActions = this + val transaction = supportFragmentManager.beginTransaction() + transaction.replace(R.id.fragment_container, listOfSpaces) + transaction.commit() + } + /** * Set per-view controllers */ From 66d25e87606e45c3c85b9319353fd27fe23e9c17 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Mon, 6 Feb 2023 08:58:02 +0100 Subject: [PATCH 082/152] Fix toolbar behaviour and navigation for copy and move actions --- .../ui/activity/FileDisplayActivity.kt | 1 + .../ui/activity/FolderPickerActivity.kt | 70 +++++++++++-------- .../ui/activity/UploadPathActivity.java | 3 +- owncloudApp/src/main/res/values/styles.xml | 5 ++ 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index f8dd087edd4..c54911dc33c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -703,6 +703,7 @@ class FileDisplayActivity : FileActivity(), if (listMainFileFragment?.getCurrentSpace()?.isProject == true) { setCheckedItemAtBottomBar(getMenuItemForFileListOption(FileListOption.SPACES_LIST)) + updateToolbar(null, listMainFileFragment?.getCurrentSpace()) } else { setCheckedItemAtBottomBar(getMenuItemForFileListOption(fileListOption)) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index 1c43d763748..4bf1bb483f0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -48,6 +48,9 @@ open class FolderPickerActivity : FileActivity(), MainFileListFragment.FileActions, SpacesListFragment.SpacesActions { + protected val listMainFileFragment: MainFileListFragment? + get() = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FOLDERS) as MainFileListFragment? + private lateinit var cancelButton: Button private lateinit var chooseButton: Button private lateinit var pickerMode: PickerMode @@ -101,6 +104,11 @@ open class FolderPickerActivity : FileActivity(), Timber.d("onCreate() end") } + override fun onResume() { + super.onResume() + updateToolbar(null, listMainFileFragment?.getCurrentSpace()) + } + /** * Called when the ownCloud {@link Account} associated to the Activity was just updated. */ @@ -118,8 +126,7 @@ open class FolderPickerActivity : FileActivity(), } if (!stateWasRecovered) { - val listOfFolders = getListOfFilesFragment() - listOfFolders?.navigateToFolder(folder) + listMainFileFragment?.navigateToFolder(folder) } updateNavigationElementsInActionBar() @@ -132,32 +139,37 @@ open class FolderPickerActivity : FileActivity(), } override fun onOptionsItemSelected(item: MenuItem): Boolean { - var returnValue = true when (item.itemId) { android.R.id.home -> { - val currentDir = getCurrentFolder() - if (currentDir != null && currentDir.parentId != 0L) { - onBackPressed() - } + onBackPressed() } - else -> returnValue = super.onOptionsItemSelected(item) } - return returnValue + return super.onOptionsItemSelected(item) } override fun onBackPressed() { - val listOfFiles = getListOfFilesFragment() - if (listOfFiles != null) { // Should never be null, indeed - val fileBeforeBrowsingUp = listOfFiles.getCurrentFile() - if (fileBeforeBrowsingUp.parentId != null && fileBeforeBrowsingUp.parentId == OCFile.ROOT_PARENT_ID) { - // If we are already at root, let's finish the picker. No sense to keep browsing up. + val currentDirDisplayed = listMainFileFragment?.getCurrentFile() + // If current file is null (we are in the spaces list, for example), close the activity + if (currentDirDisplayed == null) { + finish() + return + } + // If current file is root folder + else if (currentDirDisplayed.parentId == OCFile.ROOT_PARENT_ID) { + // If we are not in COPY mode, close the activity + if (pickerMode != PickerMode.COPY) { finish() return } - listOfFiles.onBrowseUp() - file = listOfFiles.getCurrentFile() - updateNavigationElementsInActionBar() + // If we are in COPY mode and inside a space, navigate back to the spaces list + if (listMainFileFragment?.getCurrentSpace()?.isProject == true || listMainFileFragment?.getCurrentSpace()?.isPersonal == true) { + file = null + initAndShowListOfSpaces() + updateToolbar(null) + } + } else { + listMainFileFragment?.onBrowseUp() } } @@ -178,7 +190,7 @@ open class FolderPickerActivity : FileActivity(), } override fun onCurrentFolderUpdated(newCurrentFolder: OCFile, currentSpace: OCSpace?) { - updateNavigationElementsInActionBar() + updateToolbar(newCurrentFolder, currentSpace) file = newCurrentFolder } @@ -264,21 +276,23 @@ open class FolderPickerActivity : FileActivity(), chooseButton.text = getString(pickerMode.getButtonString()) } - protected fun getListOfFilesFragment(): MainFileListFragment? { - val listOfFiles = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FOLDERS) - if (listOfFiles != null) { - return listOfFiles as MainFileListFragment + private fun getCurrentFolder(): OCFile? { + if (listMainFileFragment != null) { + return listMainFileFragment?.getCurrentFile() } - Timber.e("Access to unexisting list of files fragment!!") return null } - private fun getCurrentFolder(): OCFile? { - val listOfFiles = getListOfFilesFragment() - if (listOfFiles != null) { // should never be null, indeed - return listOfFiles.getCurrentFile() + private fun updateToolbar(chosenFileFromParam: OCFile?, space: OCSpace? = null) { + val chosenFile = chosenFileFromParam ?: file // If no file is passed, current file decides + + if (chosenFile == null || (chosenFile.remotePath == OCFile.ROOT_PATH && (space == null || !space.isProject))) { + updateStandardToolbar(title = getString(R.string.default_display_name_for_root_folder), displayHomeAsUpEnabled = false, homeButtonEnabled = false) + } else if (space?.isProject == true && chosenFile.remotePath == OCFile.ROOT_PATH) { + updateStandardToolbar(title = space.name, displayHomeAsUpEnabled = pickerMode == PickerMode.COPY, homeButtonEnabled = pickerMode == PickerMode.COPY) + } else { + updateStandardToolbar(title = chosenFile.fileName, displayHomeAsUpEnabled = true, homeButtonEnabled = true) } - return null } protected fun updateNavigationElementsInActionBar() { diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java index fbb630b446a..b91cbee1f77 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java @@ -57,6 +57,7 @@ protected void onCreate(Bundle savedInstanceState) { protected void onAccountSet(boolean stateWasRecovered) { super.onAccountSet(stateWasRecovered); if (getAccount() != null) { + // TODO: Check how to solve glitch when accessing the folder picker from camera uploads, sending the app to background, returning and avoid falling back to initial folder // Check if we need to open an specific folder and navigate to it. // If there is not, fallback to the root folder for this account. String cameraUploadPath = getIntent().getStringExtra(KEY_CAMERA_UPLOAD_PATH); @@ -70,7 +71,7 @@ protected void onAccountSet(boolean stateWasRecovered) { } if (!stateWasRecovered) { - MainFileListFragment listOfFolders = getListOfFilesFragment(); + MainFileListFragment listOfFolders = getListMainFileFragment(); listOfFolders.navigateToFolder(getFile()); } diff --git a/owncloudApp/src/main/res/values/styles.xml b/owncloudApp/src/main/res/values/styles.xml index 441d30ebb98..591372ed650 100644 --- a/owncloudApp/src/main/res/values/styles.xml +++ b/owncloudApp/src/main/res/values/styles.xml @@ -104,6 +104,11 @@ --> + + From 5213c8856c7a356c6561cabbd7a1be54f54908c5 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 7 Feb 2023 09:08:56 +0100 Subject: [PATCH 083/152] Personal space now appears in the space list with copy action --- .../dependecyinjection/UseCaseModule.kt | 2 + .../dependecyinjection/ViewModelModule.kt | 4 +- .../presentation/spaces/SpacesListAdapter.kt | 54 ++++++++++--------- .../presentation/spaces/SpacesListFragment.kt | 13 +++-- .../spaces/SpacesListViewModel.kt | 12 +++-- .../ui/activity/FolderPickerActivity.kt | 7 +-- .../src/main/res/drawable/ic_folder.xml | 4 +- .../datasources/LocalSpacesDataSource.kt | 1 + .../implementation/OCLocalSpacesDataSource.kt | 8 +++ .../android/data/spaces/db/SpacesDao.kt | 5 ++ .../spaces/repository/OCSpacesRepository.kt | 3 ++ .../android/domain/spaces/SpacesRepository.kt | 1 + ...esWithSpecialsForAccountAsStreamUseCase.kt | 37 +++++++++++++ 13 files changed, 115 insertions(+), 36 deletions(-) create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 6ecf38d4e47..9a5756b59f6 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -79,6 +79,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUse import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesForAccountUseCase +import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAccountUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase @@ -179,6 +180,7 @@ val useCaseModule = module { // Spaces factory { GetPersonalAndProjectSpacesForAccountUseCase(get()) } + factory { GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase(get()) } factory { GetProjectSpacesWithSpecialsForAccountAsStreamUseCase(get()) } factory { GetSpaceWithSpecialsByIdForAccountUseCase(get()) } factory { RefreshSpacesFromServerAsyncUseCase(get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index e084fdc30d0..56660a2e945 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -113,5 +113,7 @@ val viewModelModule = module { viewModel { (ocFile: OCFile) -> ConflictsResolveViewModel(get(), get(), get(), get(), get(), ocFile) } viewModel { ReceiveExternalFilesViewModel(get(), get()) } viewModel { AccountsManagementViewModel(get()) } - viewModel { SpacesListViewModel(get(), get(), get(), get(), get()) } + viewModel { (showPersonalSpace: Boolean) -> + SpacesListViewModel(get(), get(), get(), get(), get(), get(), showPersonalSpace) + } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt index 43260858d26..c4a507834bd 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -3,7 +3,7 @@ * * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -54,36 +54,42 @@ class SpacesListAdapter( listener.onItemClick(space) } - spacesListItemName.text = space.name - spacesListItemSubtitle.text = space.description + if (space.isPersonal) { + spacesListItemName.text = holder.itemView.context.getString(R.string.bottom_nav_personal) - val spaceSpecialImage = space.getSpaceSpecialImage() - spacesListItemImage.tag = spaceSpecialImage?.id + spacesListItemImage.setImageResource(R.drawable.ic_folder) + } else { + spacesListItemName.text = space.name + spacesListItemSubtitle.text = space.description - if (spaceSpecialImage != null) { - val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(spaceSpecialImage.id) - if (thumbnail != null) { - spacesListItemImage.run { - setImageBitmap(thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - } - } - if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(spaceSpecialImage, spacesListItemImage)) { - val account = AccountUtils.getOwnCloudAccountByName(spacesViewHolder.itemView.context, space.accountName) - val task = ThumbnailsCacheManager.ThumbnailGenerationTask(spacesListItemImage, account) - val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(spacesViewHolder.itemView.resources, thumbnail, task) + val spaceSpecialImage = space.getSpaceSpecialImage() + spacesListItemImage.tag = spaceSpecialImage?.id - // If drawable is not visible, do not update it. - if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) { + if (spaceSpecialImage != null) { + val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(spaceSpecialImage.id) + if (thumbnail != null) { spacesListItemImage.run { - spacesListItemImage.setImageDrawable(asyncDrawable) + setImageBitmap(thumbnail) scaleType = ImageView.ScaleType.CENTER_CROP } } - task.execute(spaceSpecialImage) - } - if (spaceSpecialImage.file.mimeType == "image/png") { - spacesListItemImage.setBackgroundColor(ContextCompat.getColor(spacesViewHolder.itemView.context, R.color.background_color)) + if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(spaceSpecialImage, spacesListItemImage)) { + val account = AccountUtils.getOwnCloudAccountByName(spacesViewHolder.itemView.context, space.accountName) + val task = ThumbnailsCacheManager.ThumbnailGenerationTask(spacesListItemImage, account) + val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(spacesViewHolder.itemView.resources, thumbnail, task) + + // If drawable is not visible, do not update it. + if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) { + spacesListItemImage.run { + spacesListItemImage.setImageDrawable(asyncDrawable) + scaleType = ImageView.ScaleType.CENTER_CROP + } + } + task.execute(spaceSpecialImage) + } + if (spaceSpecialImage.file.mimeType == "image/png") { + spacesListItemImage.setBackgroundColor(ContextCompat.getColor(spacesViewHolder.itemView.context, R.color.background_color)) + } } } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index eee143c8189..ef092719232 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -3,7 +3,7 @@ * * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -38,12 +38,19 @@ import com.owncloud.android.extensions.toDrawableRes import com.owncloud.android.extensions.toSubtitleStringRes import com.owncloud.android.extensions.toTitleStringRes import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf -class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment() { +class SpacesListFragment( + val showPersonalSpace: Boolean = false, +) : SpacesListAdapter.SpacesListAdapterListener, Fragment() { private var _binding: SpacesListFragmentBinding? = null private val binding get() = _binding!! - private val spacesListViewModel: SpacesListViewModel by viewModel() + private val spacesListViewModel: SpacesListViewModel by viewModel() { + parametersOf( + showPersonalSpace + ) + } private lateinit var spacesListAdapter: SpacesListAdapter diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt index 4d1a611d3e0..22d3d642d09 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt @@ -3,7 +3,7 @@ * * @author Juan Carlos Garrote Gascón * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -28,6 +28,7 @@ import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase import com.owncloud.android.domain.spaces.model.OCSpace +import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase import com.owncloud.android.providers.CoroutinesDispatcherProvider @@ -38,10 +39,12 @@ import kotlinx.coroutines.launch class SpacesListViewModel( private val refreshSpacesFromServerAsyncUseCase: RefreshSpacesFromServerAsyncUseCase, + private val getPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase: GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase, private val getProjectSpacesWithSpecialsForAccountAsStreamUseCase: GetProjectSpacesWithSpecialsForAccountAsStreamUseCase, private val getFileByRemotePathUseCase: GetFileByRemotePathUseCase, private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, private val account: Account, + private val showPersonalSpace: Boolean, ) : ViewModel() { private val _spacesList: MutableStateFlow = @@ -51,9 +54,12 @@ class SpacesListViewModel( init { viewModelScope.launch(coroutinesDispatcherProvider.io) { refreshSpacesFromServer() - getProjectSpacesWithSpecialsForAccountAsStreamUseCase.execute( + val spacesListFlow = if (showPersonalSpace) getPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase.execute( + GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase.Params(accountName = account.name) + ) else getProjectSpacesWithSpecialsForAccountAsStreamUseCase.execute( GetProjectSpacesWithSpecialsForAccountAsStreamUseCase.Params(accountName = account.name) - ).collect { spaces -> + ) + spacesListFlow.collect { spaces -> _spacesList.update { it.copy(spaces = spaces) } } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index 4bf1bb483f0..37087c6b457 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -255,7 +255,7 @@ open class FolderPickerActivity : FileActivity(), } private fun initAndShowListOfSpaces() { - val listOfSpaces = SpacesListFragment() + val listOfSpaces = SpacesListFragment(showPersonalSpace = true) listOfSpaces.spacesActions = this val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.fragment_container, listOfSpaces) @@ -285,8 +285,9 @@ open class FolderPickerActivity : FileActivity(), private fun updateToolbar(chosenFileFromParam: OCFile?, space: OCSpace? = null) { val chosenFile = chosenFileFromParam ?: file // If no file is passed, current file decides - - if (chosenFile == null || (chosenFile.remotePath == OCFile.ROOT_PATH && (space == null || !space.isProject))) { + if (chosenFile != null && chosenFile.remotePath == OCFile.ROOT_PATH && space?.isProject == false && pickerMode == PickerMode.COPY) { + updateStandardToolbar(title = getString(R.string.default_display_name_for_root_folder), displayHomeAsUpEnabled = true, homeButtonEnabled = true) + } else if (chosenFile == null || (chosenFile.remotePath == OCFile.ROOT_PATH && (space == null || !space.isProject))) { updateStandardToolbar(title = getString(R.string.default_display_name_for_root_folder), displayHomeAsUpEnabled = false, homeButtonEnabled = false) } else if (space?.isProject == true && chosenFile.remotePath == OCFile.ROOT_PATH) { updateStandardToolbar(title = space.name, displayHomeAsUpEnabled = pickerMode == PickerMode.COPY, homeButtonEnabled = pickerMode == PickerMode.COPY) diff --git a/owncloudApp/src/main/res/drawable/ic_folder.xml b/owncloudApp/src/main/res/drawable/ic_folder.xml index d7c6145c60c..62be2e5082b 100644 --- a/owncloudApp/src/main/res/drawable/ic_folder.xml +++ b/owncloudApp/src/main/res/drawable/ic_folder.xml @@ -1,6 +1,6 @@ ) fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> + fun getPersonalAndProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> fun getPersonalAndProjectSpacesForAccount(accountName: String): List fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace fun getWebDavUrlForSpace(spaceId: String?, accountName: String): String? diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt index 427e309abef..42a11cfca60 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCLocalSpacesDataSource.kt @@ -67,6 +67,14 @@ class OCLocalSpacesDataSource( } } + override fun getPersonalAndProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> { + return spacesDao.getPersonalAndProjectSpacesWithSpecialsForAccountAsFlow(accountName).map { spacesWithSpecialsEntitiesList -> + spacesWithSpecialsEntitiesList.map { spacesWithSpecialsEntity -> + spacesWithSpecialsEntity.toModel() + } + } + } + override fun getPersonalAndProjectSpacesForAccount(accountName: String): List { return spacesDao.getPersonalAndProjectSpacesForAccount(accountName).map { it.toModel() } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt index 7c9f0dfc172..f0b3da81895 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/db/SpacesDao.kt @@ -111,6 +111,11 @@ interface SpacesDao { accountName: String, ): List + @Query(SELECT_PERSONAL_AND_PROJECT_SPACES_FOR_ACCOUNT) + fun getPersonalAndProjectSpacesWithSpecialsForAccountAsFlow( + accountName: String, + ): Flow> + @Query(SELECT_SPACE_BY_ID_FOR_ACCOUNT) fun getSpaceWithSpecialsByIdForAccount( spaceId: String?, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt index 463371e47fd..4326541e41b 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -38,6 +38,9 @@ class OCSpacesRepository( override fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String) = localSpacesDataSource.getProjectSpacesWithSpecialsForAccountAsFlow(accountName) + override fun getPersonalAndProjectSpacesWithSpecialsForAccountAsFlow(accountName: String) = + localSpacesDataSource.getPersonalAndProjectSpacesWithSpecialsForAccountAsFlow(accountName) + override fun getPersonalAndProjectSpacesForAccount(accountName: String) = localSpacesDataSource.getPersonalAndProjectSpacesForAccount(accountName) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt index 4f367f3fb63..beac00d542c 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.Flow interface SpacesRepository { fun refreshSpacesForAccount(accountName: String) fun getProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> + fun getPersonalAndProjectSpacesWithSpecialsForAccountAsFlow(accountName: String): Flow> fun getPersonalAndProjectSpacesForAccount(accountName: String): List fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace fun getWebDavUrlForSpace(accountName: String, spaceId: String?): String? diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase.kt new file mode 100644 index 00000000000..9f93428eaeb --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase.kt @@ -0,0 +1,37 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.usecases + +import com.owncloud.android.domain.BaseUseCase +import com.owncloud.android.domain.spaces.SpacesRepository +import com.owncloud.android.domain.spaces.model.OCSpace +import kotlinx.coroutines.flow.Flow + +class GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase( + private val spacesRepository: SpacesRepository +) : BaseUseCase>, GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase.Params>() { + + override fun run(params: Params) = spacesRepository.getPersonalAndProjectSpacesWithSpecialsForAccountAsFlow(params.accountName) + + data class Params( + val accountName: String + ) +} From 19b6b0a95b999155ee380fffbbd7132d669aad83 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Tue, 7 Feb 2023 17:06:44 +0100 Subject: [PATCH 084/152] Adapted move operation for spaces --- .../CheckCurrentCredentialsOperation.java | 4 ++-- .../files/datasources/RemoteFileDataSource.kt | 7 ++++++- .../implementation/OCRemoteFileDataSource.kt | 19 +++++++++++++------ .../data/files/repository/OCFileRepository.kt | 10 ++++++++-- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/CheckCurrentCredentialsOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/CheckCurrentCredentialsOperation.java index da2e9a6cc89..6bb5d4cd1a5 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/CheckCurrentCredentialsOperation.java +++ b/owncloudApp/src/main/java/com/owncloud/android/operations/CheckCurrentCredentialsOperation.java @@ -50,7 +50,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { return new RemoteOperationResult<>(new IllegalStateException( "Account to validate is not the account connected to!")); } else { - RemoteOperation checkPathExistenceOperation = new CheckPathExistenceRemoteOperation(OCFile.ROOT_PATH, false); + RemoteOperation checkPathExistenceOperation = new CheckPathExistenceRemoteOperation(OCFile.ROOT_PATH, false, null); final RemoteOperationResult existenceCheckResult = checkPathExistenceOperation.execute(client); final RemoteOperationResult result = new RemoteOperationResult<>(existenceCheckResult.getCode()); @@ -58,4 +58,4 @@ protected RemoteOperationResult run(OwnCloudClient client) { return result; } } -} \ No newline at end of file +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index 560b892f0b1..3a7a2c09dab 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel García de Prada - * Copyright (C) 2021 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -26,6 +28,7 @@ interface RemoteFileDataSource { path: String, checkUserCredentials: Boolean, accountName: String, + spaceWebDavUrl: String?, ): Boolean fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): String @@ -47,12 +50,14 @@ interface RemoteFileDataSource { fun getAvailableRemotePath( remotePath: String, accountName: String, + spaceWebDavUrl: String?, ): String fun moveFile( sourceRemotePath: String, targetRemotePath: String, accountName: String, + spaceWebDavUrl: String?, ) fun readFile( diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index 0ddcdffe383..47082184a5b 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel García de Prada - * Copyright (C) 2020 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -36,9 +38,11 @@ class OCRemoteFileDataSource( path: String, checkUserCredentials: Boolean, accountName: String, + spaceWebDavUrl: String?, ): Boolean = clientManager.getFileService(accountName).checkPathExistence( path = path, - isUserLogged = checkUserCredentials + isUserLogged = checkUserCredentials, + spaceWebDavUrl = spaceWebDavUrl, ).data override fun copyFile( @@ -77,8 +81,9 @@ class OCRemoteFileDataSource( override fun getAvailableRemotePath( remotePath: String, accountName: String, + spaceWebDavUrl: String?, ): String { - var checkExistsFile = checkPathExistence(remotePath, false, accountName) + var checkExistsFile = checkPathExistence(remotePath, false, accountName, spaceWebDavUrl) if (!checkExistsFile) { return remotePath } @@ -96,9 +101,9 @@ class OCRemoteFileDataSource( do { suffix = " ($count)" checkExistsFile = if (pos >= 0) { - checkPathExistence("${remotePath.substringBeforeLast('.', "")}$suffix.$extension", false, accountName) + checkPathExistence("${remotePath.substringBeforeLast('.', "")}$suffix.$extension", false, accountName, spaceWebDavUrl) } else { - checkPathExistence(remotePath + suffix, false, accountName) + checkPathExistence(remotePath + suffix, false, accountName, spaceWebDavUrl) } count++ } while (checkExistsFile) @@ -113,10 +118,12 @@ class OCRemoteFileDataSource( sourceRemotePath: String, targetRemotePath: String, accountName: String, + spaceWebDavUrl: String?, ) = executeRemoteOperation { clientManager.getFileService(accountName).moveFile( sourceRemotePath = sourceRemotePath, - targetRemotePath = targetRemotePath + targetRemotePath = targetRemotePath, + spaceWebDavUrl = spaceWebDavUrl, ) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 3805e44ffa6..d144cded784 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -82,13 +82,16 @@ class OCFileRepository( } override fun copyFile(listOfFilesToCopy: List, targetFolder: OCFile) { + val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(targetFolder.spaceId, targetFolder.owner) + listOfFilesToCopy.forEach { ocFile -> // 1. Get the final remote path for this file. val expectedRemotePath: String = targetFolder.remotePath + ocFile.fileName val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath( expectedRemotePath, - targetFolder.owner + targetFolder.owner, + spaceWebDavUrl, ).let { if (ocFile.isFolder) it.plus(File.separator) else it } @@ -166,11 +169,13 @@ class OCFileRepository( localFileDataSource.getFilesAvailableOfflineFromEveryAccount() override fun moveFile(listOfFilesToMove: List, targetFile: OCFile) { + val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(targetFile.spaceId, targetFile.owner) + listOfFilesToMove.forEach { ocFile -> // 1. Get the final remote path for this file. val expectedRemotePath: String = targetFile.remotePath + ocFile.fileName - val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath(expectedRemotePath, targetFile.owner).let { + val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath(expectedRemotePath, targetFile.owner, spaceWebDavUrl).let { if (ocFile.isFolder) it.plus(File.separator) else it } val finalStoragePath: String = localStorageProvider.getDefaultSavePathFor(targetFile.owner, finalRemotePath, targetFile.spaceId) @@ -181,6 +186,7 @@ class OCFileRepository( sourceRemotePath = ocFile.remotePath, targetRemotePath = finalRemotePath, accountName = ocFile.owner, + spaceWebDavUrl = spaceWebDavUrl, ) } catch (targetNodeDoesNotExist: ConflictException) { // Target node does not exist anymore. Remove target folder from database and local storage and return From 7d8939c2ec7a6761a76c5d7a86f0484f0af6736a Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 8 Feb 2023 13:30:22 +0100 Subject: [PATCH 085/152] Adapted copy operation for spaces --- .../android/data/files/datasources/RemoteFileDataSource.kt | 2 ++ .../datasources/implementation/OCRemoteFileDataSource.kt | 6 +++++- .../android/data/files/repository/OCFileRepository.kt | 7 +++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index 3a7a2c09dab..15aa49bb04f 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -37,6 +37,8 @@ interface RemoteFileDataSource { sourceRemotePath: String, targetRemotePath: String, accountName: String, + sourceSpaceWebDavUrl: String?, + targetSpaceWebDavUrl: String?, ): String fun createFolder( diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index 47082184a5b..41a9601015c 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -49,10 +49,14 @@ class OCRemoteFileDataSource( sourceRemotePath: String, targetRemotePath: String, accountName: String, + sourceSpaceWebDavUrl: String?, + targetSpaceWebDavUrl: String?, ): String = executeRemoteOperation { clientManager.getFileService(accountName).copyFile( sourceRemotePath = sourceRemotePath, - targetRemotePath = targetRemotePath + targetRemotePath = targetRemotePath, + sourceSpaceWebDavUrl = sourceSpaceWebDavUrl, + targetSpaceWebDavUrl = targetSpaceWebDavUrl, ) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index d144cded784..405f9eb009d 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -82,7 +82,8 @@ class OCFileRepository( } override fun copyFile(listOfFilesToCopy: List, targetFolder: OCFile) { - val spaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(targetFolder.spaceId, targetFolder.owner) + val sourceSpaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(listOfFilesToCopy[0].spaceId, listOfFilesToCopy[0].owner) + val targetSpaceWebDavUrl = localSpacesDataSource.getWebDavUrlForSpace(targetFolder.spaceId, targetFolder.owner) listOfFilesToCopy.forEach { ocFile -> @@ -91,7 +92,7 @@ class OCFileRepository( val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath( expectedRemotePath, targetFolder.owner, - spaceWebDavUrl, + targetSpaceWebDavUrl, ).let { if (ocFile.isFolder) it.plus(File.separator) else it } @@ -102,6 +103,8 @@ class OCFileRepository( sourceRemotePath = ocFile.remotePath, targetRemotePath = finalRemotePath, accountName = ocFile.owner, + sourceSpaceWebDavUrl = sourceSpaceWebDavUrl, + targetSpaceWebDavUrl = targetSpaceWebDavUrl, ) } catch (targetNodeDoesNotExist: ConflictException) { // Target node does not exist anymore. Remove target folder from database and local storage and return From b39851ffd91155d839758b4929178df0d0c3f5f3 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 8 Feb 2023 14:44:31 +0100 Subject: [PATCH 086/152] Added assert in move file usecase to check it is always inside the same space --- .../MoveIntoAnotherSpaceException.kt | 25 +++++++++++++++++++ .../domain/files/usecases/MoveFileUseCase.kt | 16 ++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/exceptions/MoveIntoAnotherSpaceException.kt diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/exceptions/MoveIntoAnotherSpaceException.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/exceptions/MoveIntoAnotherSpaceException.kt new file mode 100644 index 00000000000..225a364845c --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/exceptions/MoveIntoAnotherSpaceException.kt @@ -0,0 +1,25 @@ +/** + * ownCloud Android client application + * + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.exceptions + +import java.lang.Exception + +class MoveIntoAnotherSpaceException : Exception() diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/MoveFileUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/MoveFileUseCase.kt index 0ad029ec4b7..524e1f51dde 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/MoveFileUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/MoveFileUseCase.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel García de Prada - * Copyright (C) 2021 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2023 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -16,18 +18,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.domain.files.usecases import com.owncloud.android.domain.BaseUseCaseWithResult import com.owncloud.android.domain.exceptions.MoveIntoDescendantException import com.owncloud.android.domain.exceptions.MoveIntoSameFolderException +import com.owncloud.android.domain.exceptions.MoveIntoAnotherSpaceException import com.owncloud.android.domain.files.FileRepository import com.owncloud.android.domain.files.model.OCFile /** * Move a list of files with the SAME hierarchy to a target folder. * - * Moving files to a descendant or moving files to the same directory will throw an exception. + * Moving files to a descendant, moving files to the same directory + * or moving files to another space will throw an exception. */ class MoveFileUseCase( private val fileRepository: FileRepository @@ -42,11 +47,12 @@ class MoveFileUseCase( ) } - @Throws(IllegalArgumentException::class, MoveIntoSameFolderException::class, MoveIntoDescendantException::class) + @Throws(IllegalArgumentException::class, MoveIntoSameFolderException::class, MoveIntoDescendantException::class, MoveIntoAnotherSpaceException::class) fun validateOrThrowException(listOfFilesToMove: List, targetFolder: OCFile) { require(listOfFilesToMove.isNotEmpty()) - - if (listOfFilesToMove.any { targetFolder.remotePath.startsWith(it.remotePath) }) { + if (listOfFilesToMove[0].spaceId != targetFolder.spaceId) { + throw MoveIntoAnotherSpaceException() + } else if (listOfFilesToMove.any { targetFolder.remotePath.startsWith(it.remotePath) }) { throw MoveIntoDescendantException() } else if (listOfFilesToMove.any { it.parentId == targetFolder.id }) { throw MoveIntoSameFolderException() From cb38429a9919739c3cb1cb11d85b2a7e3f64841a Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Wed, 8 Feb 2023 17:53:57 +0100 Subject: [PATCH 087/152] Fix in unit tests --- .../data/file/datasources/OCRemoteFileDataSourceTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCRemoteFileDataSourceTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCRemoteFileDataSourceTest.kt index 5dbde60a56d..a16c2c999d4 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCRemoteFileDataSourceTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/file/datasources/OCRemoteFileDataSourceTest.kt @@ -57,7 +57,7 @@ class OCRemoteFileDataSourceTest { ocFileService.checkPathExistence(OC_SERVER_INFO.baseUrl, true) } returns checkPathExistenceRemoteResult - val checkPathExistence = ocRemoteFileDataSource.checkPathExistence(OC_SERVER_INFO.baseUrl, true, OC_ACCOUNT_NAME) + val checkPathExistence = ocRemoteFileDataSource.checkPathExistence(OC_SERVER_INFO.baseUrl, true, OC_ACCOUNT_NAME, null) assertNotNull(checkPathExistence) assertEquals(checkPathExistenceRemoteResult.data, checkPathExistence) @@ -74,7 +74,7 @@ class OCRemoteFileDataSourceTest { ocFileService.checkPathExistence(OC_SERVER_INFO.baseUrl, true) } returns checkPathExistenceRemoteResult - val checkPathExistence = ocRemoteFileDataSource.checkPathExistence(OC_SERVER_INFO.baseUrl, true, OC_ACCOUNT_NAME) + val checkPathExistence = ocRemoteFileDataSource.checkPathExistence(OC_SERVER_INFO.baseUrl, true, OC_ACCOUNT_NAME, null) assertNotNull(checkPathExistence) assertEquals(checkPathExistenceRemoteResult.data, checkPathExistence) @@ -88,7 +88,7 @@ class OCRemoteFileDataSourceTest { ocFileService.checkPathExistence(OC_SERVER_INFO.baseUrl, true) } throws Exception() - ocRemoteFileDataSource.checkPathExistence(OC_SERVER_INFO.baseUrl, true, OC_ACCOUNT_NAME) + ocRemoteFileDataSource.checkPathExistence(OC_SERVER_INFO.baseUrl, true, OC_ACCOUNT_NAME, null) } @Test From ac2f2b4bfad06d97317a8d8a023b0cb7538f8d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garc=C3=ADa=20de=20Prada?= Date: Thu, 9 Feb 2023 14:26:03 +0100 Subject: [PATCH 088/152] Fix race condition when moving files --- .../usecases/synchronization/SynchronizeFileUseCase.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFileUseCase.kt b/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFileUseCase.kt index 65c3fa0bf7f..2b6eec35b41 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFileUseCase.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFileUseCase.kt @@ -51,8 +51,13 @@ class SynchronizeFileUseCase( spaceId = fileToSynchronize.spaceId ) } catch (exception: FileNotFoundException) { - // 1.1 File not exists anymore -> remove file locally (DB and Storage) - fileRepository.deleteFiles(listOf(fileToSynchronize), false) + // 1.1 File does not exist anymore in remote + val localFile = fileToSynchronize.id?.let { fileRepository.getFileById(it) } + // If it still exists locally, but file has different path, another operation could have been done simultaneously + // Do not remove the file in that case. It may be synced later + if (localFile != null && (localFile.remotePath == fileToSynchronize.remotePath && localFile.spaceId == fileToSynchronize.spaceId)) { + fileRepository.deleteFiles(listOf(fileToSynchronize), false) + } return SyncType.FileNotFound } From a889c777ac19e7685ce1c4b29704d94c3abf6c86 Mon Sep 17 00:00:00 2001 From: Juan Carlos Garrote Date: Mon, 13 Feb 2023 14:05:54 +0100 Subject: [PATCH 089/152] Fix in spaces list and improvements in code --- .../files/filelist/MainFileListFragment.kt | 4 +- .../SettingsPictureUploadsFragment.kt | 2 +- .../SettingsVideoUploadsFragment.kt | 2 +- .../presentation/spaces/SpacesListFragment.kt | 14 +-- .../ui/activity/FileDisplayActivity.kt | 64 ++++++------- .../ui/activity/FolderPickerActivity.kt | 91 +++++++++---------- .../ui/activity/UploadPathActivity.java | 7 +- 7 files changed, 87 insertions(+), 97 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index c7138c023b5..2b33cf34e18 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -596,7 +596,7 @@ class MainFileListFragment : Fragment(), R.id.action_move -> { val action = Intent(activity, FolderPickerActivity::class.java) action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, checkedFiles) - action.putExtra(FolderPickerActivity.EXTRA_PICKER_OPTION, FolderPickerActivity.PickerMode.MOVE) + action.putExtra(FolderPickerActivity.EXTRA_PICKER_MODE, FolderPickerActivity.PickerMode.MOVE) requireActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES) fileListAdapter.clearSelection() updateActionModeAfterTogglingSelected() @@ -605,7 +605,7 @@ class MainFileListFragment : Fragment(), R.id.action_copy -> { val action = Intent(activity, FolderPickerActivity::class.java) action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, checkedFiles) - action.putExtra(FolderPickerActivity.EXTRA_PICKER_OPTION, FolderPickerActivity.PickerMode.COPY) + action.putExtra(FolderPickerActivity.EXTRA_PICKER_MODE, FolderPickerActivity.PickerMode.COPY) requireActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES) fileListAdapter.clearSelection() updateActionModeAfterTogglingSelected() diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsPictureUploadsFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsPictureUploadsFragment.kt index f4e64e44ebf..c043f83e7bc 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsPictureUploadsFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsPictureUploadsFragment.kt @@ -167,7 +167,7 @@ class SettingsPictureUploadsFragment : PreferenceFragmentCompat() { } val intent = Intent(activity, UploadPathActivity::class.java).apply { putExtra(UploadPathActivity.KEY_CAMERA_UPLOAD_PATH, uploadPath) - putExtra(FolderPickerActivity.EXTRA_PICKER_OPTION, FolderPickerActivity.PickerMode.CAMERA_FOLDER) + putExtra(FolderPickerActivity.EXTRA_PICKER_MODE, FolderPickerActivity.PickerMode.CAMERA_FOLDER) putExtra(UploadPathActivity.KEY_CAMERA_UPLOAD_ACCOUNT, picturesViewModel.getPictureUploadsAccount()) } selectPictureUploadsPathLauncher.launch(intent) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsVideoUploadsFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsVideoUploadsFragment.kt index 620fafeafce..bc2796c5747 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsVideoUploadsFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/settings/autouploads/SettingsVideoUploadsFragment.kt @@ -167,7 +167,7 @@ class SettingsVideoUploadsFragment : PreferenceFragmentCompat() { } val intent = Intent(activity, UploadPathActivity::class.java).apply { putExtra(UploadPathActivity.KEY_CAMERA_UPLOAD_PATH, uploadPath) - putExtra(FolderPickerActivity.EXTRA_PICKER_OPTION, FolderPickerActivity.PickerMode.CAMERA_FOLDER) + putExtra(FolderPickerActivity.EXTRA_PICKER_MODE, FolderPickerActivity.PickerMode.CAMERA_FOLDER) putExtra(UploadPathActivity.KEY_CAMERA_UPLOAD_ACCOUNT, videosViewModel.getVideoUploadsAccount()) } selectVideoUploadsPathLauncher.launch(intent) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index ef092719232..a743dfdc567 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -24,13 +24,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.setFragmentResult import androidx.recyclerview.widget.GridLayoutManager import com.owncloud.android.R import com.owncloud.android.databinding.SpacesListFragmentBinding import com.owncloud.android.domain.files.model.FileListOption -import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.extensions.showErrorInSnackbar @@ -46,7 +47,7 @@ class SpacesListFragment( private var _binding: SpacesListFragmentBinding? = null private val binding get() = _binding!! - private val spacesListViewModel: SpacesListViewModel by viewModel() { + private val spacesListViewModel: SpacesListViewModel by viewModel { parametersOf( showPersonalSpace ) @@ -54,8 +55,6 @@ class SpacesListFragment( private lateinit var spacesListAdapter: SpacesListAdapter - var spacesActions: SpacesActions? = null - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -92,7 +91,7 @@ class SpacesListFragment( uiState.error?.let { showErrorInSnackbar(R.string.spaces_sync_failed, it) } uiState.rootFolderFromSelectedSpace?.let { - spacesActions?.onSpaceClicked(it) + setFragmentResult(REQUEST_KEY_CLICK_SPACE, bundleOf(BUNDLE_KEY_CLICK_SPACE to it)) } } } @@ -112,7 +111,8 @@ class SpacesListFragment( spacesListViewModel.getRootFileForSpace(ocSpace) } - interface SpacesActions { - fun onSpaceClicked(rootFolder: OCFile) + companion object { + const val REQUEST_KEY_CLICK_SPACE = "REQUEST_KEY_CLICK_SPACE" + const val BUNDLE_KEY_CLICK_SPACE = "BUNDLE_KEY_CLICK_SPACE" } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index c54911dc33c..f3a6de081ac 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -84,6 +84,8 @@ import com.owncloud.android.presentation.files.operations.FileOperationsViewMode import com.owncloud.android.presentation.security.bayPassUnlockOnce import com.owncloud.android.presentation.capabilities.CapabilityViewModel import com.owncloud.android.presentation.spaces.SpacesListFragment +import com.owncloud.android.presentation.spaces.SpacesListFragment.Companion.BUNDLE_KEY_CLICK_SPACE +import com.owncloud.android.presentation.spaces.SpacesListFragment.Companion.REQUEST_KEY_CLICK_SPACE import com.owncloud.android.presentation.transfers.TransfersViewModel import com.owncloud.android.providers.WorkManagerProvider import com.owncloud.android.syncadapter.FileSyncAdapter @@ -118,8 +120,7 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, SecurityEnforced, MainFileListFragment.FileActions, - MainFileListFragment.UploadActions, - SpacesListFragment.SpacesActions { + MainFileListFragment.UploadActions { private val job = Job() override val coroutineContext: CoroutineContext @@ -130,7 +131,7 @@ class FileDisplayActivity : FileActivity(), /** * FileDisplayActivity is based on those two containers. - * Left one is used for showing a list of files - [listMainFileFragment] + * Left one is used for showing a list of files - [mainFileListFragment] * Right one is used for showing previews, details... - [secondFragment] * * We should rename them to a more accurate names. @@ -140,7 +141,7 @@ class FileDisplayActivity : FileActivity(), private var leftFragmentContainer: FrameLayout? = null private var rightFragmentContainer: FrameLayout? = null - private val listMainFileFragment: MainFileListFragment? + private val mainFileListFragment: MainFileListFragment? get() = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FILES) as MainFileListFragment? private val spacesListFragment: SpacesListFragment? @@ -237,11 +238,17 @@ class FileDisplayActivity : FileActivity(), .add(taskRetainerFragment, TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT).commit() } // else, Fragment already created and retained across configuration change - Timber.v("onCreate() end") + supportFragmentManager.setFragmentResultListener(REQUEST_KEY_CLICK_SPACE, this) { _, bundle -> + val rootSpaceFolder = bundle.getParcelable(BUNDLE_KEY_CLICK_SPACE) + file = rootSpaceFolder + initAndShowListOfFiles() + } if (resources.getBoolean(R.bool.enable_rate_me_feature) && !BuildConfig.DEBUG) { AppRater.appLaunched(this, packageName) } + + Timber.v("onCreate() end") } override fun onPostCreate(savedInstanceState: Bundle?) { @@ -341,9 +348,7 @@ class FileDisplayActivity : FileActivity(), } private fun initAndShowListOfSpaces() { - val listOfSpaces = SpacesListFragment().apply { - spacesActions = this@FileDisplayActivity - } + val listOfSpaces = SpacesListFragment() val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.left_fragment_container, listOfSpaces, TAG_LIST_OF_SPACES) transaction.commit() @@ -352,7 +357,7 @@ class FileDisplayActivity : FileActivity(), private fun initFragmentsWithFile() { if (account != null && file != null) { /// First fragment - listMainFileFragment?.navigateToFolder(currentDir) + mainFileListFragment?.navigateToFolder(currentDir) ?: Timber.e("Still have a chance to lose the initialization of list fragment >(") /// Second fragment @@ -470,8 +475,8 @@ class FileDisplayActivity : FileActivity(), /*val fileListFragment = listOfFilesFragment fileListFragment?.listDirectory(reloadData)*/ if (file != null) { - val fileListFragment = listMainFileFragment - listMainFileFragment?.fileActions = this + val fileListFragment = mainFileListFragment + mainFileListFragment?.fileActions = this fileListFragment?.navigateToFolder(file) } } @@ -631,7 +636,7 @@ class FileDisplayActivity : FileActivity(), } override fun onBackPressed() { - val isFabOpen = listMainFileFragment?.isFabExpanded() ?: false + val isFabOpen = mainFileListFragment?.isFabExpanded() ?: false /* * BackPressed priority/hierarchy: @@ -647,17 +652,17 @@ class FileDisplayActivity : FileActivity(), super.onBackPressed() } else if (!isDrawerOpen() && isFabOpen) { // close fab - listMainFileFragment?.collapseFab() + mainFileListFragment?.collapseFab() } else { // Every single menu is collapsed. We can navigate up. if (secondFragment != null) { // If secondFragment was shown, we need to navigate to the parent of the displayed file // Need a cleanup - listMainFileFragment?.navigateToFolderId(secondFragment!!.file!!.parentId!!) + mainFileListFragment?.navigateToFolderId(secondFragment!!.file!!.parentId!!) cleanSecondFragment() - updateToolbar(listMainFileFragment?.getCurrentFile()) + updateToolbar(mainFileListFragment?.getCurrentFile()) } else { - val currentDirDisplayed = listMainFileFragment?.getCurrentFile() + val currentDirDisplayed = mainFileListFragment?.getCurrentFile() // If current file is null (we are in the spaces list, for example), close the app if (currentDirDisplayed == null) { finish() @@ -666,7 +671,7 @@ class FileDisplayActivity : FileActivity(), // If current file is root folder else if (currentDirDisplayed.parentId == FileDataStorageManager.ROOT_PARENT_ID.toLong()) { // If current space is a project space (not personal, not shares), navigate back to the spaces list - if (listMainFileFragment?.getCurrentSpace()?.isProject == true) { + if (mainFileListFragment?.getCurrentSpace()?.isProject == true) { navigateTo(FileListOption.SPACES_LIST) } // If current space is not a project space (personal or shares) or it is null ("Files" in oC10), close the app @@ -675,7 +680,7 @@ class FileDisplayActivity : FileActivity(), return } } else { - listMainFileFragment?.onBrowseUp() + mainFileListFragment?.onBrowseUp() } } } @@ -701,14 +706,14 @@ class FileDisplayActivity : FileActivity(), Timber.v("onResume() start") super.onResume() - if (listMainFileFragment?.getCurrentSpace()?.isProject == true) { + if (mainFileListFragment?.getCurrentSpace()?.isProject == true) { setCheckedItemAtBottomBar(getMenuItemForFileListOption(FileListOption.SPACES_LIST)) - updateToolbar(null, listMainFileFragment?.getCurrentSpace()) + updateToolbar(null, mainFileListFragment?.getCurrentSpace()) } else { setCheckedItemAtBottomBar(getMenuItemForFileListOption(fileListOption)) } - listMainFileFragment?.updateFileListOption(fileListOption, file) + mainFileListFragment?.updateFileListOption(fileListOption, file) // refresh list of files refreshListOfFilesFragment() @@ -790,7 +795,7 @@ class FileDisplayActivity : FileActivity(), } if (synchFolderRemotePath != null && currentDir.remotePath == synchFolderRemotePath) { - listMainFileFragment?.navigateToFolder(currentDir) + mainFileListFragment?.navigateToFolder(currentDir) } file = currentFile } @@ -799,7 +804,7 @@ class FileDisplayActivity : FileActivity(), FileSyncAdapter.EVENT_FULL_SYNC_END != event } - listMainFileFragment?.setProgressBarAsIndeterminate(syncInProgress) + mainFileListFragment?.setProgressBarAsIndeterminate(syncInProgress) Timber.d("Setting progress visibility to $syncInProgress") } @@ -820,7 +825,7 @@ class FileDisplayActivity : FileActivity(), } fun browseToRoot() { - val listOfFiles = listMainFileFragment + val listOfFiles = mainFileListFragment if (listOfFiles != null) { // should never be null, indeed val root = storageManager.getFileByPath(OCFile.ROOT_PATH) listOfFiles.navigateToFolder(root!!) @@ -870,7 +875,7 @@ class FileDisplayActivity : FileActivity(), FileListOption.SPACES_LIST -> getString(R.string.drawer_item_spaces) } setupRootToolbar(title, isSearchEnabled = fileListOption != FileListOption.SPACES_LIST) - listMainFileFragment?.setSearchListener(findViewById(R.id.root_toolbar_search_view)) + mainFileListFragment?.setSearchListener(findViewById(R.id.root_toolbar_search_view)) } else if (space?.isProject == true && chosenFile.remotePath == OCFile.ROOT_PATH) { updateStandardToolbar(title = space.name, displayHomeAsUpEnabled = true, homeButtonEnabled = true) } else { @@ -1357,10 +1362,10 @@ class FileDisplayActivity : FileActivity(), file = null initAndShowListOfSpaces() updateToolbar(null) - } else if (listMainFileFragment != null) { + } else if (mainFileListFragment != null) { fileListOption = newFileListOption file = storageManager.getFileByPath(OCFile.ROOT_PATH) - listMainFileFragment?.updateFileListOption(newFileListOption, file) + mainFileListFragment?.updateFileListOption(newFileListOption, file) updateToolbar(null) } else if (spacesListFragment != null) { fileListOption = newFileListOption @@ -1488,11 +1493,6 @@ class FileDisplayActivity : FileActivity(), ) } - override fun onSpaceClicked(rootFolder: OCFile) { - file = rootFolder - initAndShowListOfFiles() - } - companion object { private const val TAG_LIST_OF_FILES = "LIST_OF_FILES" private const val TAG_LIST_OF_SPACES = "LIST_OF_SPACES" diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index 37087c6b457..7f4ed926b5d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -27,10 +27,9 @@ import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem -import android.view.View -import android.view.View.OnClickListener import android.widget.Button import android.widget.LinearLayout +import androidx.annotation.StringRes import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.domain.files.model.FileListOption @@ -44,15 +43,11 @@ import timber.log.Timber open class FolderPickerActivity : FileActivity(), FileFragment.ContainerActivity, - OnClickListener, - MainFileListFragment.FileActions, - SpacesListFragment.SpacesActions { + MainFileListFragment.FileActions { - protected val listMainFileFragment: MainFileListFragment? + protected val mainFileListFragment: MainFileListFragment? get() = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FOLDERS) as MainFileListFragment? - private lateinit var cancelButton: Button - private lateinit var chooseButton: Button private lateinit var pickerMode: PickerMode override fun onCreate(savedInstanceState: Bundle?) { @@ -66,7 +61,7 @@ open class FolderPickerActivity : FileActivity(), val filesFolderPickerLayout = findViewById(R.id.filesFolderPickerLayout) filesFolderPickerLayout.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(this) - pickerMode = intent.getSerializableExtra(EXTRA_PICKER_OPTION) as PickerMode + pickerMode = intent.getSerializableExtra(EXTRA_PICKER_MODE) as PickerMode if (savedInstanceState == null) { when (pickerMode) { @@ -101,12 +96,18 @@ open class FolderPickerActivity : FileActivity(), // Set action button text setActionButtonText() + supportFragmentManager.setFragmentResultListener(SpacesListFragment.REQUEST_KEY_CLICK_SPACE, this) { _, bundle -> + val rootSpaceFolder = bundle.getParcelable(SpacesListFragment.BUNDLE_KEY_CLICK_SPACE) + file = rootSpaceFolder + initAndShowListOfFilesFragment() + } + Timber.d("onCreate() end") } override fun onResume() { super.onResume() - updateToolbar(null, listMainFileFragment?.getCurrentSpace()) + updateToolbar(null, mainFileListFragment?.getCurrentSpace()) } /** @@ -126,7 +127,7 @@ open class FolderPickerActivity : FileActivity(), } if (!stateWasRecovered) { - listMainFileFragment?.navigateToFolder(folder) + mainFileListFragment?.navigateToFolder(folder) } updateNavigationElementsInActionBar() @@ -149,7 +150,7 @@ open class FolderPickerActivity : FileActivity(), } override fun onBackPressed() { - val currentDirDisplayed = listMainFileFragment?.getCurrentFile() + val currentDirDisplayed = mainFileListFragment?.getCurrentFile() // If current file is null (we are in the spaces list, for example), close the activity if (currentDirDisplayed == null) { finish() @@ -163,29 +164,13 @@ open class FolderPickerActivity : FileActivity(), return } // If we are in COPY mode and inside a space, navigate back to the spaces list - if (listMainFileFragment?.getCurrentSpace()?.isProject == true || listMainFileFragment?.getCurrentSpace()?.isPersonal == true) { + if (mainFileListFragment?.getCurrentSpace()?.isProject == true || mainFileListFragment?.getCurrentSpace()?.isPersonal == true) { file = null initAndShowListOfSpaces() updateToolbar(null) } } else { - listMainFileFragment?.onBrowseUp() - } - } - - override fun onClick(v: View?) { - when (v) { - cancelButton -> finish() - chooseButton -> { - val data = Intent().apply { - val targetFiles = intent.getParcelableArrayListExtra(EXTRA_FILES) - putExtra(EXTRA_FOLDER, getCurrentFolder()) - putParcelableArrayListExtra(EXTRA_FILES, targetFiles) - } - setResult(RESULT_OK, data) - - finish() - } + mainFileListFragment?.onBrowseUp() } } @@ -230,11 +215,6 @@ open class FolderPickerActivity : FileActivity(), // Nothing to do. Details can't be opened here. } - override fun onSpaceClicked(rootFolder: OCFile) { - file = rootFolder - initAndShowListOfFilesFragment() - } - private fun initAndShowListOfFilesFragment(spaceId: String? = null) { val safeInitialFolder = if (file == null) { val fileDataStorageManager = FileDataStorageManager(account) @@ -256,7 +236,6 @@ open class FolderPickerActivity : FileActivity(), private fun initAndShowListOfSpaces() { val listOfSpaces = SpacesListFragment(showPersonalSpace = true) - listOfSpaces.spacesActions = this val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.fragment_container, listOfSpaces) transaction.commit() @@ -266,31 +245,44 @@ open class FolderPickerActivity : FileActivity(), * Set per-view controllers */ private fun initPickerListeners() { - cancelButton = findViewById(R.id.folder_picker_btn_cancel) - cancelButton.setOnClickListener(this) - chooseButton = findViewById(R.id.folder_picker_btn_choose) - chooseButton.setOnClickListener(this) + findViewById