Эх сурвалжийг харах

Merge pull request #11515 from nextcloud/tags

Support for viewing tags
Andy Scherzinger 1 жил өмнө
parent
commit
3e1f61afc9
23 өөрчлөгдсөн 1393 нэмэгдсэн , 67 устгасан
  1. 1137 0
      app/schemas/com.nextcloud.client.database.NextcloudDatabase/69.json
  2. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.activity.UploadFilesActivityIT_localFolderPickerMode.png
  3. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.activity.UploadFilesActivityIT_noneSelected.png
  4. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showFiles.png
  5. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showFolderTypes.png
  6. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showRichWorkspace.png
  7. BIN
      app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showSharedFiles.png
  8. 18 14
      app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt
  9. 3 1
      app/src/main/java/com/nextcloud/client/database/entity/FileEntity.kt
  10. 17 1
      app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
  11. 10 0
      app/src/main/java/com/owncloud/android/datamodel/OCFile.java
  12. 4 2
      app/src/main/java/com/owncloud/android/db/ProviderMeta.java
  13. 7 0
      app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt
  14. 1 0
      app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
  15. 25 0
      app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
  16. 12 0
      app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt
  17. 29 4
      app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  18. 3 1
      app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java
  19. 6 0
      app/src/main/res/layout/file_details_fragment.xml
  20. 118 43
      app/src/main/res/layout/list_item.xml
  21. 1 0
      app/src/main/res/values/dims.xml
  22. 1 1
      app/src/main/res/values/setup.xml
  23. 1 0
      app/src/main/res/values/strings.xml

+ 1137 - 0
app/schemas/com.nextcloud.client.database.NextcloudDatabase/69.json

@@ -0,0 +1,1137 @@
+{
+    "formatVersion": 1,
+    "database": {
+        "version": 69,
+        "identityHash": "4f593cdd41a85be7b67c756cf2848028",
+        "entities": [
+            {
+                "tableName": "arbitrary_data",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `cloud_id` TEXT, `key` TEXT, `value` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "cloudId",
+                        "columnName": "cloud_id",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "key",
+                        "columnName": "key",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "value",
+                        "columnName": "value",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "capabilities",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` TEXT, `version_mayor` INTEGER, `version_minor` INTEGER, `version_micro` INTEGER, `version_string` TEXT, `version_edition` TEXT, `extended_support` INTEGER, `core_pollinterval` INTEGER, `sharing_api_enabled` INTEGER, `sharing_public_enabled` INTEGER, `sharing_public_password_enforced` INTEGER, `sharing_public_expire_date_enabled` INTEGER, `sharing_public_expire_date_days` INTEGER, `sharing_public_expire_date_enforced` INTEGER, `sharing_public_send_mail` INTEGER, `sharing_public_upload` INTEGER, `sharing_user_send_mail` INTEGER, `sharing_resharing` INTEGER, `sharing_federation_outgoing` INTEGER, `sharing_federation_incoming` INTEGER, `files_bigfilechunking` INTEGER, `files_undelete` INTEGER, `files_versioning` INTEGER, `external_links` INTEGER, `server_name` TEXT, `server_color` TEXT, `server_text_color` TEXT, `server_element_color` TEXT, `server_slogan` TEXT, `server_logo` TEXT, `background_url` TEXT, `end_to_end_encryption` INTEGER, `end_to_end_encryption_keys_exist` INTEGER, `activity` INTEGER, `background_default` INTEGER, `background_plain` INTEGER, `richdocument` INTEGER, `richdocument_mimetype_list` TEXT, `richdocument_direct_editing` INTEGER, `richdocument_direct_templates` INTEGER, `richdocument_optional_mimetype_list` TEXT, `sharing_public_ask_for_optional_password` INTEGER, `richdocument_product_name` TEXT, `direct_editing_etag` TEXT, `user_status` INTEGER, `user_status_supports_emoji` INTEGER, `etag` TEXT, `files_locking_version` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "accountName",
+                        "columnName": "account",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionMajor",
+                        "columnName": "version_mayor",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionMinor",
+                        "columnName": "version_minor",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionMicro",
+                        "columnName": "version_micro",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionString",
+                        "columnName": "version_string",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionEditor",
+                        "columnName": "version_edition",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "extendedSupport",
+                        "columnName": "extended_support",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "corePollinterval",
+                        "columnName": "core_pollinterval",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingApiEnabled",
+                        "columnName": "sharing_api_enabled",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicEnabled",
+                        "columnName": "sharing_public_enabled",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicPasswordEnforced",
+                        "columnName": "sharing_public_password_enforced",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicExpireDateEnabled",
+                        "columnName": "sharing_public_expire_date_enabled",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicExpireDateDays",
+                        "columnName": "sharing_public_expire_date_days",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicExpireDateEnforced",
+                        "columnName": "sharing_public_expire_date_enforced",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicSendMail",
+                        "columnName": "sharing_public_send_mail",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicUpload",
+                        "columnName": "sharing_public_upload",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingUserSendMail",
+                        "columnName": "sharing_user_send_mail",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingResharing",
+                        "columnName": "sharing_resharing",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingFederationOutgoing",
+                        "columnName": "sharing_federation_outgoing",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingFederationIncoming",
+                        "columnName": "sharing_federation_incoming",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "filesBigfilechunking",
+                        "columnName": "files_bigfilechunking",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "filesUndelete",
+                        "columnName": "files_undelete",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "filesVersioning",
+                        "columnName": "files_versioning",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "externalLinks",
+                        "columnName": "external_links",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverName",
+                        "columnName": "server_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverColor",
+                        "columnName": "server_color",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverTextColor",
+                        "columnName": "server_text_color",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverElementColor",
+                        "columnName": "server_element_color",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverSlogan",
+                        "columnName": "server_slogan",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverLogo",
+                        "columnName": "server_logo",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverBackgroundUrl",
+                        "columnName": "background_url",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "endToEndEncryption",
+                        "columnName": "end_to_end_encryption",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "endToEndEncryptionKeysExist",
+                        "columnName": "end_to_end_encryption_keys_exist",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "activity",
+                        "columnName": "activity",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverBackgroundDefault",
+                        "columnName": "background_default",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverBackgroundPlain",
+                        "columnName": "background_plain",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocument",
+                        "columnName": "richdocument",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentMimetypeList",
+                        "columnName": "richdocument_mimetype_list",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentDirectEditing",
+                        "columnName": "richdocument_direct_editing",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentTemplates",
+                        "columnName": "richdocument_direct_templates",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentOptionalMimetypeList",
+                        "columnName": "richdocument_optional_mimetype_list",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicAskForOptionalPassword",
+                        "columnName": "sharing_public_ask_for_optional_password",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentProductName",
+                        "columnName": "richdocument_product_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "directEditingEtag",
+                        "columnName": "direct_editing_etag",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "userStatus",
+                        "columnName": "user_status",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "userStatusSupportsEmoji",
+                        "columnName": "user_status_supports_emoji",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "etag",
+                        "columnName": "etag",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "filesLockingVersion",
+                        "columnName": "files_locking_version",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "external_links",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `icon_url` TEXT, `language` TEXT, `type` INTEGER, `name` TEXT, `url` TEXT, `redirect` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "iconUrl",
+                        "columnName": "icon_url",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "language",
+                        "columnName": "language",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "type",
+                        "columnName": "type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "name",
+                        "columnName": "name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "url",
+                        "columnName": "url",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "redirect",
+                        "columnName": "redirect",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "filelist",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `filename` TEXT, `encrypted_filename` TEXT, `path` TEXT, `path_decrypted` TEXT, `parent` INTEGER, `created` INTEGER, `modified` INTEGER, `content_type` TEXT, `content_length` INTEGER, `media_path` TEXT, `file_owner` TEXT, `last_sync_date` INTEGER, `last_sync_date_for_data` INTEGER, `modified_at_last_sync_for_data` INTEGER, `etag` TEXT, `etag_on_server` TEXT, `share_by_link` INTEGER, `permissions` TEXT, `remote_id` TEXT, `local_id` INTEGER NOT NULL DEFAULT -1, `update_thumbnail` INTEGER, `is_downloading` INTEGER, `favorite` INTEGER, `is_encrypted` INTEGER, `etag_in_conflict` TEXT, `shared_via_users` INTEGER, `mount_type` INTEGER, `has_preview` INTEGER, `unread_comments_count` INTEGER, `owner_id` TEXT, `owner_display_name` TEXT, `note` TEXT, `sharees` TEXT, `rich_workspace` TEXT, `metadata_size` TEXT, `locked` INTEGER, `lock_type` INTEGER, `lock_owner` TEXT, `lock_owner_display_name` TEXT, `lock_owner_editor` TEXT, `lock_timestamp` INTEGER, `lock_timeout` INTEGER, `lock_token` TEXT, `tags` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "name",
+                        "columnName": "filename",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "encryptedName",
+                        "columnName": "encrypted_filename",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "path",
+                        "columnName": "path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "pathDecrypted",
+                        "columnName": "path_decrypted",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "parent",
+                        "columnName": "parent",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "creation",
+                        "columnName": "created",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "modified",
+                        "columnName": "modified",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "contentType",
+                        "columnName": "content_type",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "contentLength",
+                        "columnName": "content_length",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "storagePath",
+                        "columnName": "media_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "accountOwner",
+                        "columnName": "file_owner",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lastSyncDate",
+                        "columnName": "last_sync_date",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lastSyncDateForData",
+                        "columnName": "last_sync_date_for_data",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "modifiedAtLastSyncForData",
+                        "columnName": "modified_at_last_sync_for_data",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "etag",
+                        "columnName": "etag",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "etagOnServer",
+                        "columnName": "etag_on_server",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharedViaLink",
+                        "columnName": "share_by_link",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "permissions",
+                        "columnName": "permissions",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "remoteId",
+                        "columnName": "remote_id",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localId",
+                        "columnName": "local_id",
+                        "affinity": "INTEGER",
+                        "notNull": true,
+                        "defaultValue": "-1"
+                    },
+                    {
+                        "fieldPath": "updateThumbnail",
+                        "columnName": "update_thumbnail",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isDownloading",
+                        "columnName": "is_downloading",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "favorite",
+                        "columnName": "favorite",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isEncrypted",
+                        "columnName": "is_encrypted",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "etagInConflict",
+                        "columnName": "etag_in_conflict",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharedWithSharee",
+                        "columnName": "shared_via_users",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "mountType",
+                        "columnName": "mount_type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "hasPreview",
+                        "columnName": "has_preview",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "unreadCommentsCount",
+                        "columnName": "unread_comments_count",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "ownerId",
+                        "columnName": "owner_id",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "ownerDisplayName",
+                        "columnName": "owner_display_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "note",
+                        "columnName": "note",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharees",
+                        "columnName": "sharees",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richWorkspace",
+                        "columnName": "rich_workspace",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "metadataSize",
+                        "columnName": "metadata_size",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "locked",
+                        "columnName": "locked",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockType",
+                        "columnName": "lock_type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockOwner",
+                        "columnName": "lock_owner",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockOwnerDisplayName",
+                        "columnName": "lock_owner_display_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockOwnerEditor",
+                        "columnName": "lock_owner_editor",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockTimestamp",
+                        "columnName": "lock_timestamp",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockTimeout",
+                        "columnName": "lock_timeout",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockToken",
+                        "columnName": "lock_token",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "tags",
+                        "columnName": "tags",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "filesystem",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `is_folder` INTEGER, `found_at` INTEGER, `upload_triggered` INTEGER, `syncedfolder_id` TEXT, `crc32` TEXT, `modified_at` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localPath",
+                        "columnName": "local_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileIsFolder",
+                        "columnName": "is_folder",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileFoundRecently",
+                        "columnName": "found_at",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileSentForUpload",
+                        "columnName": "upload_triggered",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "syncedFolderId",
+                        "columnName": "syncedfolder_id",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "crc32",
+                        "columnName": "crc32",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileModified",
+                        "columnName": "modified_at",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "ocshares",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `file_source` INTEGER, `item_source` INTEGER, `share_type` INTEGER, `shate_with` TEXT, `path` TEXT, `permissions` INTEGER, `shared_date` INTEGER, `expiration_date` INTEGER, `token` TEXT, `shared_with_display_name` TEXT, `is_directory` INTEGER, `user_id` INTEGER, `id_remote_shared` INTEGER, `owner_share` TEXT, `is_password_protected` INTEGER, `note` TEXT, `hide_download` INTEGER, `share_link` TEXT, `share_label` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileSource",
+                        "columnName": "file_source",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "itemSource",
+                        "columnName": "item_source",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareType",
+                        "columnName": "share_type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareWith",
+                        "columnName": "shate_with",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "path",
+                        "columnName": "path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "permissions",
+                        "columnName": "permissions",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharedDate",
+                        "columnName": "shared_date",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "expirationDate",
+                        "columnName": "expiration_date",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "token",
+                        "columnName": "token",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareWithDisplayName",
+                        "columnName": "shared_with_display_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isDirectory",
+                        "columnName": "is_directory",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "userId",
+                        "columnName": "user_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "idRemoteShared",
+                        "columnName": "id_remote_shared",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "accountOwner",
+                        "columnName": "owner_share",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isPasswordProtected",
+                        "columnName": "is_password_protected",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "note",
+                        "columnName": "note",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "hideDownload",
+                        "columnName": "hide_download",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareLink",
+                        "columnName": "share_link",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareLabel",
+                        "columnName": "share_label",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "synced_folders",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `wifi_only` INTEGER, `charging_only` INTEGER, `existing` INTEGER, `enabled` INTEGER, `enabled_timestamp_ms` INTEGER, `subfolder_by_date` INTEGER, `account` TEXT, `upload_option` INTEGER, `name_collision_policy` INTEGER, `type` INTEGER, `hidden` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localPath",
+                        "columnName": "local_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "remotePath",
+                        "columnName": "remote_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "wifiOnly",
+                        "columnName": "wifi_only",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "chargingOnly",
+                        "columnName": "charging_only",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "existing",
+                        "columnName": "existing",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "enabled",
+                        "columnName": "enabled",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "enabledTimestampMs",
+                        "columnName": "enabled_timestamp_ms",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "subfolderByDate",
+                        "columnName": "subfolder_by_date",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "account",
+                        "columnName": "account",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "uploadAction",
+                        "columnName": "upload_option",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "nameCollisionPolicy",
+                        "columnName": "name_collision_policy",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "type",
+                        "columnName": "type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "hidden",
+                        "columnName": "hidden",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "list_of_uploads",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `account_name` TEXT, `file_size` INTEGER, `status` INTEGER, `local_behaviour` INTEGER, `upload_time` INTEGER, `name_collision_policy` INTEGER, `is_create_remote_folder` INTEGER, `upload_end_timestamp` INTEGER, `last_result` INTEGER, `is_while_charging_only` INTEGER, `is_wifi_only` INTEGER, `created_by` INTEGER, `folder_unlock_token` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localPath",
+                        "columnName": "local_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "remotePath",
+                        "columnName": "remote_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "accountName",
+                        "columnName": "account_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileSize",
+                        "columnName": "file_size",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "status",
+                        "columnName": "status",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localBehaviour",
+                        "columnName": "local_behaviour",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "uploadTime",
+                        "columnName": "upload_time",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "nameCollisionPolicy",
+                        "columnName": "name_collision_policy",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isCreateRemoteFolder",
+                        "columnName": "is_create_remote_folder",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "uploadEndTimestamp",
+                        "columnName": "upload_end_timestamp",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lastResult",
+                        "columnName": "last_result",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isWhileChargingOnly",
+                        "columnName": "is_while_charging_only",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isWifiOnly",
+                        "columnName": "is_wifi_only",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "createdBy",
+                        "columnName": "created_by",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "folderUnlockToken",
+                        "columnName": "folder_unlock_token",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "virtual",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` TEXT, `ocfile_id` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "type",
+                        "columnName": "type",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "ocFileId",
+                        "columnName": "ocfile_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "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, '4f593cdd41a85be7b67c756cf2848028')"
+        ]
+    }
+}

BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.activity.UploadFilesActivityIT_localFolderPickerMode.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.activity.UploadFilesActivityIT_noneSelected.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showFiles.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showFolderTypes.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showRichWorkspace.png


BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showSharedFiles.png


+ 18 - 14
app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt

@@ -48,20 +48,23 @@ class OCFileListFragmentStaticServerIT : AbstractIT() {
     fun showFiles() {
         val sut = testActivityRule.launchActivity(null)
 
-        val textFile = OCFile("/1.png")
-        textFile.mimeType = "image/png"
-        textFile.fileLength = 1024000
-        textFile.modificationTimestamp = 1188206955000
-        textFile.parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
-        sut.storageManager.saveFile(textFile)
-
-        val imageFile = OCFile("/image.png")
-        imageFile.mimeType = "image/png"
-        imageFile.isPreviewAvailable = false
-        imageFile.fileLength = 3072000
-        imageFile.modificationTimestamp = 746443755000
-        imageFile.parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
-        sut.storageManager.saveFile(imageFile)
+        OCFile("/1.png").apply {
+            mimeType = "image/png"
+            fileLength = 1024000
+            modificationTimestamp = 1188206955000
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            sut.storageManager.saveFile(this)
+        }
+
+        OCFile("/image.png").apply {
+            mimeType = "image/png"
+            isPreviewAvailable = false
+            fileLength = 3072000
+            modificationTimestamp = 746443755000
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            tags = listOf("Top secret")
+            sut.storageManager.saveFile(this)
+        }
 
         OCFile("/video.mp4").apply {
             mimeType = "video/mp4"
@@ -69,6 +72,7 @@ class OCFileListFragmentStaticServerIT : AbstractIT() {
             fileLength = 12092000
             modificationTimestamp = 746143952000
             parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            tags = listOf("Confidential", "+5")
             sut.storageManager.saveFile(this)
         }
 

+ 3 - 1
app/src/main/java/com/nextcloud/client/database/entity/FileEntity.kt

@@ -117,5 +117,7 @@ data class FileEntity(
     @ColumnInfo(name = ProviderTableMeta.FILE_LOCK_TIMEOUT)
     val lockTimeout: Int?,
     @ColumnInfo(name = ProviderTableMeta.FILE_LOCK_TOKEN)
-    val lockToken: String?
+    val lockToken: String?,
+    @ColumnInfo(name = ProviderTableMeta.FILE_TAGS)
+    val tags: String?
 )

+ 17 - 1
app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -440,6 +440,7 @@ public class FileDataStorageManager {
      */
     private ContentValues createContentValuesBase(OCFile fileOrFolder) {
         final ContentValues cv = new ContentValues();
+        final Gson gson = new Gson();
         cv.put(ProviderTableMeta.FILE_MODIFIED, fileOrFolder.getModificationTimestamp());
         cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, fileOrFolder.getModificationTimestampAtLastSyncForData());
         cv.put(ProviderTableMeta.FILE_PARENT, fileOrFolder.getParentId());
@@ -464,7 +465,8 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.FILE_OWNER_ID, fileOrFolder.getOwnerId());
         cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, fileOrFolder.getOwnerDisplayName());
         cv.put(ProviderTableMeta.FILE_NOTE, fileOrFolder.getNote());
-        cv.put(ProviderTableMeta.FILE_SHAREES, new Gson().toJson(fileOrFolder.getSharees()));
+        cv.put(ProviderTableMeta.FILE_SHAREES, gson.toJson(fileOrFolder.getSharees()));
+        cv.put(ProviderTableMeta.FILE_TAGS, gson.toJson(fileOrFolder.getTags()));
         cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, fileOrFolder.getRichWorkspace());
         return cv;
     }
@@ -952,6 +954,20 @@ public class FileDataStorageManager {
             }
         }
 
+        String tags = fileEntity.getTags();
+        if (tags == null || tags.isEmpty() ||
+            JSON_NULL_STRING.equals(tags) || JSON_EMPTY_ARRAY.equals(tags)) {
+            ocFile.setTags(new ArrayList<>());
+        } else {
+            try {
+                String[] tagsArray = gson.fromJson(tags, String[].class);
+                ocFile.setTags(new ArrayList<>(Arrays.asList(tagsArray)));
+            } catch (JsonSyntaxException e) {
+                // ignore saved value due to api change
+                ocFile.setTags(new ArrayList<>());
+            }
+        }
+
         String metadataSize = fileEntity.getMetadataSize();
         // Surprisingly JSON deserialization causes significant overhead.
         // Avoid it in common, trivial cases (null/empty).

+ 10 - 0
app/src/main/java/com/owncloud/android/datamodel/OCFile.java

@@ -39,6 +39,7 @@ import com.owncloud.android.lib.resources.shares.ShareeUser;
 import com.owncloud.android.utils.MimeType;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
 
 import androidx.annotation.NonNull;
@@ -114,6 +115,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
     private String lockToken;
     @Nullable
     private ImageDimension imageDimension;
+    private List<String> tags = new ArrayList<>();
 
     /**
      * URI to the local path of the file contents, if stored in the device; cached after first call to {@link
@@ -966,4 +968,12 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
     public ImageDimension getImageDimension() {
         return imageDimension;
     }
+
+    public List<String> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<String> tags) {
+        this.tags = tags;
+    }
 }

+ 4 - 2
app/src/main/java/com/owncloud/android/db/ProviderMeta.java

@@ -35,7 +35,7 @@ import java.util.List;
  */
 public class ProviderMeta {
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 68;
+    public static final int DB_VERSION = 69;
 
     private ProviderMeta() {
         // No instance
@@ -125,6 +125,7 @@ public class ProviderMeta {
         public static final String FILE_LOCK_TIMESTAMP = "lock_timestamp";
         public static final String FILE_LOCK_TIMEOUT = "lock_timeout";
         public static final String FILE_LOCK_TOKEN = "lock_token";
+        public static final String FILE_TAGS = "tags";
 
         public static final List<String> FILE_ALL_COLUMNS = Collections.unmodifiableList(Arrays.asList(
             _ID,
@@ -171,7 +172,8 @@ public class ProviderMeta {
             FILE_LOCK_TIMESTAMP,
             FILE_LOCK_TIMEOUT,
             FILE_LOCK_TOKEN,
-            FILE_METADATA_SIZE));
+            FILE_METADATA_SIZE,
+            FILE_TAGS));
         public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc";
 
         // Columns of ocshares table

+ 7 - 0
app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt

@@ -23,7 +23,9 @@ package com.owncloud.android.ui.adapter
 
 import android.view.View
 import android.widget.ImageView
+import android.widget.LinearLayout
 import android.widget.TextView
+import com.google.android.material.chip.ChipGroup
 import com.owncloud.android.ui.AvatarGroupLayout
 
 internal interface ListItemViewHolder : ListGridItemViewHolder {
@@ -32,4 +34,9 @@ internal interface ListItemViewHolder : ListGridItemViewHolder {
     val lastModification: TextView
     val overflowMenu: ImageView
     val sharedAvatars: AvatarGroupLayout
+    val tagsGroup: ChipGroup
+    val firstTag: TextView
+    val secondTag: TextView
+    val tagMore: TextView
+    val fileDetailGroup: LinearLayout
 }

+ 1 - 0
app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java

@@ -510,6 +510,7 @@ public class LocalFileListAdapter extends RecyclerView.Adapter<RecyclerView.View
 
             itemView.findViewById(R.id.sharedAvatars).setVisibility(View.GONE);
             itemView.findViewById(R.id.overflow_menu).setVisibility(View.GONE);
+            itemView.findViewById(R.id.tagsGroup).setVisibility(View.GONE);
         }
     }
 

+ 25 - 0
app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -418,6 +418,31 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
             holder.getSharedAvatars().removeAllViews();
         }
 
+        // tags
+        if (file.getTags().isEmpty()) {
+            holder.getTagsGroup().setVisibility(View.GONE);
+            holder.getFileDetailGroup().setVisibility(View.VISIBLE);
+        } else {
+            holder.getTagsGroup().setVisibility(View.VISIBLE);
+            holder.getFileDetailGroup().setVisibility(View.GONE);
+            holder.getFirstTag().setVisibility(View.VISIBLE);
+            holder.getSecondTag().setVisibility(View.GONE);
+            holder.getTagMore().setVisibility(View.GONE);
+
+            holder.getFirstTag().setText(file.getTags().get(0));
+            
+            if (file.getTags().size() > 1) {
+                holder.getSecondTag().setVisibility(View.VISIBLE);
+                holder.getSecondTag().setText(file.getTags().get(1));
+            }
+
+            if (file.getTags().size() > 2) {
+                holder.getTagMore().setVisibility(View.VISIBLE);
+                holder.getTagMore().setText(String.format(activity.getString(R.string.tags_more),
+                                                          (file.getTags().size() - 2)));
+            }
+        }
+
         // npe fix: looks like file without local storage path somehow get here
         final String storagePath = file.getStoragePath();
         if (onlyOnDevice && storagePath != null) {

+ 12 - 0
app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt

@@ -23,9 +23,11 @@ package com.owncloud.android.ui.adapter
 
 import android.view.View
 import android.widget.ImageView
+import android.widget.LinearLayout
 import android.widget.TextView
 import androidx.recyclerview.widget.RecyclerView
 import com.elyeproj.loaderviewlibrary.LoaderImageView
+import com.google.android.material.chip.ChipGroup
 import com.owncloud.android.databinding.ListItemBinding
 import com.owncloud.android.ui.AvatarGroupLayout
 
@@ -48,6 +50,16 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) :
         get() = binding.Filename
     override val thumbnail: ImageView
         get() = binding.thumbnailLayout.thumbnail
+    override val tagsGroup: ChipGroup
+        get() = binding.tagsGroup
+    override val firstTag: TextView
+        get() = binding.firstTag
+    override val secondTag: TextView
+        get() = binding.secondTag
+    override val tagMore: TextView
+        get() = binding.tagMore
+    override val fileDetailGroup: LinearLayout
+        get() = binding.fileDetailGroup
 
     override fun showVideoOverlay() {
         binding.thumbnailLayout.videoOverlay.visibility = View.VISIBLE

+ 29 - 4
app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -28,6 +28,7 @@
 package com.owncloud.android.ui.fragment;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.view.LayoutInflater;
@@ -37,6 +38,7 @@ import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.ProgressBar;
 
+import com.google.android.material.chip.Chip;
 import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.google.android.material.tabs.TabLayout;
 import com.nextcloud.client.account.User;
@@ -226,6 +228,25 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
         } else {
             binding.emptyList.emptyListView.setVisibility(View.GONE);
         }
+        
+        Context context = getContext();
+        if (context == null) {
+            return null;
+        }
+
+        if (getFile().getTags().isEmpty()) {
+            binding.tagsGroup.setVisibility(View.GONE);
+        } else {
+            for (String tag : getFile().getTags()) {
+                Chip chip = new Chip(context);
+                chip.setText(tag);
+                chip.setChipBackgroundColor(ColorStateList.valueOf(getResources().getColor(R.color.bg_default,
+                                                                                           context.getTheme())));
+                chip.setTextColor(getResources().getColor(R.color.list_item_lastmod_and_filesize_text,
+                                                          context.getTheme()));
+                binding.tagsGroup.addView(chip);
+            }
+        }
 
         return view;
     }
@@ -316,7 +337,10 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             }
         });
 
-        binding.tabLayout.getTabAt(activeTab).select();
+        TabLayout.Tab tab = binding.tabLayout.getTabAt(activeTab);
+        if (tab != null) {
+            tab.select();
+        }
     }
 
     @Override
@@ -559,7 +583,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
         Bitmap resizedImage;
 
         if (toolbarActivity != null && MimeTypeUtil.isImage(file)) {
-            String tagId = String.valueOf(ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId());
+            String tagId = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId();
             resizedImage = ThumbnailsCacheManager.getBitmapFromDiskCache(tagId);
 
             if (resizedImage != null && !file.isUpdateThumbnailNeeded()) {
@@ -568,7 +592,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             } else {
                 // show thumbnail while loading resized image
                 Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
-                        String.valueOf(ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId()));
+                    ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId());
 
                 if (thumbnail != null) {
                     toolbarActivity.setPreviewImageBitmap(thumbnail);
@@ -586,7 +610,8 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
                                                                               containerActivity.getStorageManager(),
                                                                               connectivityService,
                                                                               containerActivity.getStorageManager().getUser(),
-                                                                              getResources().getColor(R.color.background_color_inverse)
+                                                                              getResources().getColor(R.color.background_color_inverse,
+                                                                                                      requireContext().getTheme())
                         );
 
                     if (resizedImage == null) {

+ 3 - 1
app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java

@@ -34,6 +34,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.files.model.RemoteFile;
+import com.owncloud.android.lib.resources.shares.ShareeUser;
 import com.owncloud.android.ui.helpers.FileOperationsHelper;
 
 import java.io.File;
@@ -233,7 +234,7 @@ public final class FileStorageUtils {
         file.setOwnerId(remote.getOwnerId());
         file.setOwnerDisplayName(remote.getOwnerDisplayName());
         file.setNote(remote.getNote());
-        file.setSharees(new ArrayList<>(Arrays.asList(remote.getSharees())));
+        file.setSharees(new ArrayList<ShareeUser>(Arrays.asList(remote.getSharees())));
         file.setRichWorkspace(remote.getRichWorkspace());
         file.setLocked(remote.isLocked());
         file.setLockType(remote.getLockType());
@@ -243,6 +244,7 @@ public final class FileStorageUtils {
         file.setLockTimestamp(remote.getLockTimestamp());
         file.setLockTimeout(remote.getLockTimeout());
         file.setLockToken(remote.getLockToken());
+        file.setTags(new ArrayList<>(Arrays.asList(remote.getTags())));
 
         return file;
     }

+ 6 - 0
app/src/main/res/layout/file_details_fragment.xml

@@ -112,6 +112,12 @@
 
             </LinearLayout>
 
+            <com.google.android.material.chip.ChipGroup
+                android:id="@+id/tagsGroup"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                />
+
         </LinearLayout>
 
         <com.google.android.material.button.MaterialButton

+ 118 - 43
app/src/main/res/layout/list_item.xml

@@ -1,8 +1,10 @@
-<?xml version="1.0" encoding="UTF-8"?><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
   ownCloud Android client application
 
-  Copyright (C) 2012  Bartek Przybylski
   Copyright (C) 2015 ownCloud Inc.
+  Copyright (C) 2012 Bartek Przybylski
+  Copyright (C) 2023 Andy Scherzinger
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2,
@@ -18,19 +20,22 @@
  -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/ListItemLayout"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/standard_list_item_size"
+    android:layout_height="wrap_content"
     android:baselineAligned="false"
     android:descendantFocusability="blocksDescendants"
-    android:orientation="horizontal">
+    android:minHeight="@dimen/standard_list_item_size"
+    android:orientation="horizontal"
+    android:paddingTop="@dimen/standard_half_padding"
+    android:paddingBottom="@dimen/standard_half_padding">
 
     <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="@dimen/standard_list_item_size"
-        android:layout_height="@dimen/standard_list_item_size"
+        android:layout_width="@dimen/min_list_item_size"
+        android:layout_height="@dimen/min_list_item_size"
         android:layout_marginStart="@dimen/zero"
-        android:layout_marginEnd="@dimen/standard_quarter_padding"
-        android:layout_marginBottom="@dimen/standard_padding">
+        android:layout_marginEnd="@dimen/standard_quarter_padding">
 
         <FrameLayout
             android:id="@+id/thumbnail_container"
@@ -73,11 +78,12 @@
 
     <LinearLayout
         android:layout_width="0dp"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:layout_weight="1"
         android:gravity="top"
+        android:minHeight="@dimen/min_list_item_size"
         android:orientation="vertical"
-        android:paddingTop="@dimen/standard_padding">
+        android:paddingTop="@dimen/standard_half_padding">
 
         <TextView
             android:id="@+id/Filename"
@@ -86,42 +92,104 @@
             android:layout_gravity="center_vertical"
             android:ellipsize="middle"
             android:singleLine="true"
-            android:text="@string/placeholder_filename"
             android:textColor="@color/text_color"
-            android:textSize="@dimen/two_line_primary_text_size" />
+            android:textSize="@dimen/two_line_primary_text_size"
+            tools:text="@string/placeholder_filename" />
 
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal">
 
-            <TextView
-                android:id="@+id/file_size"
-                android:layout_width="wrap_content"
+            <com.google.android.material.chip.ChipGroup
+                android:id="@+id/tagsGroup"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/placeholder_fileSize"
-                android:textColor="@color/list_item_lastmod_and_filesize_text"
-                android:textSize="@dimen/two_line_secondary_text_size" />
+                android:clickable="false"
+                android:paddingTop="@dimen/standard_quarter_padding"
+                app:chipSpacingVertical="@dimen/standard_quarter_padding">
 
-            <TextView
-                android:id="@+id/file_separator"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="end"
-                android:paddingStart="@dimen/zero"
-                android:paddingEnd="@dimen/standard_quarter_padding"
-                android:text="@string/info_separator"
-                android:textColor="@color/list_item_lastmod_and_filesize_text"
-                android:textSize="@dimen/two_line_secondary_text_size" />
-
-            <TextView
-                android:id="@+id/last_mod"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="end"
-                android:text="@string/placeholder_media_time"
-                android:textColor="@color/list_item_lastmod_and_filesize_text"
-                android:textSize="@dimen/two_line_secondary_text_size" />
+                <com.google.android.material.chip.Chip
+                    android:id="@+id/firstTag"
+                    android:layout_width="wrap_content"
+                    android:layout_height="@dimen/tag_height"
+                    android:checkable="false"
+                    android:clickable="false"
+                    android:ellipsize="middle"
+                    android:textColor="@color/list_item_lastmod_and_filesize_text"
+                    android:textSize="@dimen/two_line_secondary_text_size"
+                    app:chipBackgroundColor="@color/bg_default"
+                    app:chipEndPadding="@dimen/zero"
+                    app:chipStartPadding="@dimen/zero"
+                    app:ensureMinTouchTargetSize="false"
+                    tools:text="tag1" />
+
+                <com.google.android.material.chip.Chip
+                    android:id="@+id/secondTag"
+                    android:layout_width="wrap_content"
+                    android:layout_height="@dimen/tag_height"
+                    android:checkable="false"
+                    android:clickable="false"
+                    android:ellipsize="middle"
+                    android:textColor="@color/list_item_lastmod_and_filesize_text"
+                    android:textSize="@dimen/two_line_secondary_text_size"
+                    app:chipBackgroundColor="@color/bg_default"
+                    app:chipEndPadding="@dimen/zero"
+                    app:chipStartPadding="@dimen/zero"
+                    app:ensureMinTouchTargetSize="false"
+                    tools:text="tag2" />
+
+                <com.google.android.material.chip.Chip
+                    android:id="@+id/tag_more"
+                    android:layout_width="wrap_content"
+                    android:layout_height="@dimen/tag_height"
+                    android:checkable="false"
+                    android:clickable="false"
+                    android:ellipsize="none"
+                    android:textColor="@color/list_item_lastmod_and_filesize_text"
+                    android:textSize="@dimen/two_line_secondary_text_size"
+                    app:chipBackgroundColor="@color/bg_default"
+                    app:chipEndPadding="@dimen/zero"
+                    app:chipStartPadding="@dimen/zero"
+                    app:ensureMinTouchTargetSize="false"
+                    tools:text="+3" />
+
+            </com.google.android.material.chip.ChipGroup>
+
+            <LinearLayout
+                android:id="@+id/file_detail_group"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:id="@+id/file_size"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/placeholder_fileSize"
+                    android:textColor="@color/list_item_lastmod_and_filesize_text"
+                    android:textSize="@dimen/two_line_secondary_text_size" />
+
+                <TextView
+                    android:id="@+id/file_separator"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="end"
+                    android:paddingStart="@dimen/zero"
+                    android:paddingEnd="@dimen/standard_quarter_padding"
+                    android:text="@string/info_separator"
+                    android:textColor="@color/list_item_lastmod_and_filesize_text"
+                    android:textSize="@dimen/two_line_secondary_text_size" />
+
+                <TextView
+                    android:id="@+id/last_mod"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="end"
+                    android:text="@string/placeholder_media_time"
+                    android:textColor="@color/list_item_lastmod_and_filesize_text"
+                    android:textSize="@dimen/two_line_secondary_text_size" />
+            </LinearLayout>
 
         </LinearLayout>
 
@@ -129,7 +197,7 @@
 
     <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:gravity="center_vertical|end"
         android:orientation="horizontal"
         android:paddingStart="@dimen/standard_half_padding"
@@ -150,10 +218,12 @@
         <ImageView
             android:id="@+id/sharedIcon"
             android:layout_width="48dp"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
             android:clickable="true"
             android:contentDescription="@string/shared_icon_share"
             android:focusable="true"
+            android:minHeight="@dimen/min_list_item_size"
             android:paddingStart="@dimen/list_item_share_right_margin"
             android:paddingEnd="@dimen/list_item_share_right_margin"
             android:src="@drawable/ic_unshared" />
@@ -161,18 +231,21 @@
         <com.owncloud.android.ui.AvatarGroupLayout
             android:id="@+id/sharedAvatars"
             android:layout_width="100dp"
-            android:gravity="center_vertical"
-            android:layout_height="@dimen/file_icon_size"
+            android:layout_height="@dimen/min_list_item_size"
+            android:layout_gravity="top"
             android:contentDescription="@string/shared_avatar_desc"
+            android:gravity="center_vertical"
             android:visibility="visible" />
 
         <ImageView
             android:id="@+id/custom_checkbox"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
             android:clickable="false"
             android:contentDescription="@string/checkbox"
             android:focusable="false"
+            android:minHeight="@dimen/min_list_item_size"
             android:paddingStart="@dimen/standard_half_padding"
             android:paddingEnd="@dimen/alternate_padding"
             android:src="@drawable/ic_checkbox_blank_outline" />
@@ -180,10 +253,12 @@
         <ImageView
             android:id="@+id/overflow_menu"
             android:layout_width="48dp"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
             android:clickable="true"
             android:contentDescription="@string/overflow_menu"
             android:focusable="true"
+            android:minHeight="@dimen/min_list_item_size"
             android:paddingStart="12dp"
             android:paddingEnd="12dp"
             android:src="@drawable/ic_dots_vertical" />

+ 1 - 0
app/src/main/res/values/dims.xml

@@ -151,4 +151,5 @@
     <!--Adaptive Icon size specified here:  https://developer.android.com/develop/ui/views/launch/icon_design_adaptive -->
     <dimen name="adaptive_icon_size">108dp</dimen>
     <dimen name="adaptive_icon_padding">18dp</dimen>
+    <dimen name="tag_height">18dp</dimen>
 </resources>

+ 1 - 1
app/src/main/res/values/setup.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <!-- Beta indicator -->
-    <bool name="is_beta">false</bool>
+    <bool name="is_beta">true</bool>
     <bool name="dev_version_direct_download_enabled">false</bool>
 
     <!-- App name  and other strings-->

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -1077,4 +1077,5 @@
     <string name="document_scan_export_dialog_images">Multiple images</string>
     <string name="download_cannot_create_file">Cannot create local file</string>
     <string name="download_download_invalid_local_file_name">Invalid filename for local file</string>
+    <string name="tags_more">+%1$d</string>
 </resources>