diff --git a/app/schemas/com.nextcloud.client.database.NextcloudDatabase/90.json b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/90.json new file mode 100644 index 000000000000..c0b53e5fd543 --- /dev/null +++ b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/90.json @@ -0,0 +1,1355 @@ +{ + "formatVersion": 1, + "database": { + "version": 90, + "identityHash": "93eb4d5fbf952984b6fc2df9f7c369e1", + "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, `assistant` INTEGER, `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, `end_to_end_encryption_api_version` TEXT, `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, `groupfolders` INTEGER, `drop_account` INTEGER, `security_guard` INTEGER, `forbidden_filename_characters` INTEGER, `forbidden_filenames` INTEGER, `forbidden_filename_extensions` INTEGER, `forbidden_filename_basenames` INTEGER, `files_download_limit` INTEGER, `files_download_limit_default` INTEGER, `recommendation` INTEGER, `notes_folder_path` TEXT, `default_permissions` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "assistant", + "columnName": "assistant", + "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": "endToEndEncryptionApiVersion", + "columnName": "end_to_end_encryption_api_version", + "affinity": "TEXT", + "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 + }, + { + "fieldPath": "groupfolders", + "columnName": "groupfolders", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dropAccount", + "columnName": "drop_account", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "securityGuard", + "columnName": "security_guard", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "forbiddenFileNameCharacters", + "columnName": "forbidden_filename_characters", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "forbiddenFileNames", + "columnName": "forbidden_filenames", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "forbiddenFileNameExtensions", + "columnName": "forbidden_filename_extensions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "forbiddenFilenameBaseNames", + "columnName": "forbidden_filename_basenames", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "filesDownloadLimit", + "columnName": "files_download_limit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "filesDownloadLimitDefault", + "columnName": "files_download_limit_default", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "recommendation", + "columnName": "recommendation", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "notesFolderPath", + "columnName": "notes_folder_path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "defaultPermissions", + "columnName": "default_permissions", + "affinity": "INTEGER", + "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, `hidden` 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, `metadata_live_photo` 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, `metadata_gps` TEXT, `e2e_counter` INTEGER, `internal_two_way_sync_timestamp` INTEGER, `internal_two_way_sync_result` TEXT, `uploaded` INTEGER)", + "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": "hidden", + "columnName": "hidden", + "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": "metadataLivePhoto", + "columnName": "metadata_live_photo", + "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 + }, + { + "fieldPath": "metadataGPS", + "columnName": "metadata_gps", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "e2eCounter", + "columnName": "e2e_counter", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "internalTwoWaySync", + "columnName": "internal_two_way_sync_timestamp", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "internalTwoWaySyncResult", + "columnName": "internal_two_way_sync_result", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaded", + "columnName": "uploaded", + "affinity": "INTEGER", + "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` TEXT, `id_remote_shared` INTEGER, `owner_share` TEXT, `is_password_protected` INTEGER, `note` TEXT, `hide_download` INTEGER, `share_link` TEXT, `share_label` TEXT, `download_limit_limit` INTEGER, `download_limit_count` INTEGER, `attributes` 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": "TEXT", + "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 + }, + { + "fieldPath": "downloadLimitLimit", + "columnName": "download_limit_limit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "downloadLimitCount", + "columnName": "download_limit_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attributes", + "columnName": "attributes", + "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, `sub_folder_rule` INTEGER, `exclude_hidden` INTEGER, `last_scan_timestamp_ms` 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 + }, + { + "fieldPath": "subFolderRule", + "columnName": "sub_folder_rule", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "excludeHidden", + "columnName": "exclude_hidden", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastScanTimestampMs", + "columnName": "last_scan_timestamp_ms", + "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": [] + }, + { + "tableName": "offline_operations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `offline_operations_parent_oc_file_id` INTEGER, `offline_operations_path` TEXT, `offline_operations_type` TEXT, `offline_operations_file_name` TEXT, `offline_operations_created_at` INTEGER, `offline_operations_modified_at` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "parentOCFileId", + "columnName": "offline_operations_parent_oc_file_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "path", + "columnName": "offline_operations_path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "offline_operations_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "filename", + "columnName": "offline_operations_file_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "offline_operations_created_at", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modifiedAt", + "columnName": "offline_operations_modified_at", + "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, '93eb4d5fbf952984b6fc2df9f7c369e1')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/nextcloud/utils/SharePermissionManagerTest.kt b/app/src/androidTest/java/com/nextcloud/utils/SharePermissionManagerTest.kt new file mode 100644 index 000000000000..071adc9915c3 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/utils/SharePermissionManagerTest.kt @@ -0,0 +1,268 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils + +import com.google.gson.Gson +import com.owncloud.android.datamodel.quickPermission.QuickPermissionType +import com.owncloud.android.lib.resources.shares.OCShare +import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.lib.resources.shares.attributes.ShareAttributes +import com.owncloud.android.ui.fragment.util.SharePermissionManager +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertNotNull +import junit.framework.TestCase.assertTrue +import org.junit.Test + +@Suppress("TooManyFunctions") +class SharePermissionManagerTest { + + private fun createShare(sharePermission: Int, isFolder: Boolean = false, attributesJson: String? = null): OCShare { + return if (isFolder) { + OCShare("/test") + .apply { + permissions = sharePermission + attributes = attributesJson + shareType = ShareType.INTERNAL + sharedDate = 1188206955 + shareWith = "User 1" + sharedWithDisplayName = "User 1" + } + } else { + OCShare("/test.png") + .apply { + permissions = sharePermission + attributes = attributesJson + shareType = ShareType.INTERNAL + sharedDate = 1188206955 + shareWith = "User 1" + sharedWithDisplayName = "User 1" + } + }.apply { + this.isFolder = isFolder + } + } + + // region Permission change tests + @Test + fun testTogglePermissionShouldAddPermissionFlagWhenChecked() { + val initialPermission = OCShare.READ_PERMISSION_FLAG + val updatedPermission = + SharePermissionManager.togglePermission(true, initialPermission, OCShare.UPDATE_PERMISSION_FLAG) + val updatedShare = createShare(updatedPermission) + assertTrue(SharePermissionManager.isCustomPermission(updatedShare)) + } + + @Test + fun testTogglePermissionShouldRemovePermissionFlagWhenUnchecked() { + val initialPermission = OCShare.READ_PERMISSION_FLAG + OCShare.UPDATE_PERMISSION_FLAG + val updatedPermission = + SharePermissionManager.togglePermission(false, initialPermission, OCShare.UPDATE_PERMISSION_FLAG) + val updatedShare = createShare(updatedPermission) + assertTrue(SharePermissionManager.isViewOnly(updatedShare)) + } + // endregion + + // region HasPermissions tests + @Test + fun testHasPermissionShouldReturnTrueIfPermissionPresent() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.UPDATE_PERMISSION_FLAG + assertTrue(SharePermissionManager.hasPermission(permission, OCShare.UPDATE_PERMISSION_FLAG)) + } + + @Test + fun testHasPermissionShouldReturnFalseIfPermissionNotPresent() { + val permission = OCShare.READ_PERMISSION_FLAG + assertFalse(SharePermissionManager.hasPermission(permission, OCShare.UPDATE_PERMISSION_FLAG)) + } + // endregion + + // region Helper Method Tests + @Test + fun testCanEditShouldReturnTrueIfAllPermissionsPresent() { + val share = createShare(OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER, isFolder = true) + assertTrue(SharePermissionManager.canEdit(share)) + } + + @Test + fun testCanEditShouldReturnFalseIfPermissionsAreInsufficient() { + val share = createShare(OCShare.READ_PERMISSION_FLAG) + assertFalse(SharePermissionManager.canEdit(share)) + } + + @Test + fun testIsViewOnlyShouldReturnTrueIfOnlyReadPermissionSet() { + val share = createShare(OCShare.READ_PERMISSION_FLAG) + assertTrue(SharePermissionManager.isViewOnly(share)) + } + + @Test + fun testIsFileRequestShouldReturnTrueIfOnlyCreatePermissionSetOnFolder() { + val share = createShare(OCShare.CREATE_PERMISSION_FLAG, isFolder = true) + assertTrue(SharePermissionManager.isFileRequest(share)) + } + + @Test + fun testIsFileRequestShouldReturnFalseIfOnlyCreatePermissionSetOnFile() { + val share = createShare(OCShare.CREATE_PERMISSION_FLAG) + assertFalse(SharePermissionManager.isFileRequest(share)) + } + + @Test + fun testIsSecureFileDropShouldReturnTrueIfReadAndCreatePermissionsPresent() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.CREATE_PERMISSION_FLAG + val share = createShare(permission) + assertTrue(SharePermissionManager.isSecureFileDrop(share)) + } + + @Test + fun testCanReshareShouldReturnTrueIfSharePermissionIsPresent() { + val share = createShare(OCShare.SHARE_PERMISSION_FLAG) + assertTrue(SharePermissionManager.canReshare(share)) + } + + @Test + fun testGetMaximumPermissionForFolder() { + assertEquals( + OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER, + SharePermissionManager.getMaximumPermission(isFolder = true) + ) + } + + @Test + fun testGetMaximumPermissionForFile() { + assertEquals( + OCShare.MAXIMUM_PERMISSIONS_FOR_FILE, + SharePermissionManager.getMaximumPermission(isFolder = false) + ) + } + // endregion + + // region GetSelectedTypeTests + @Test + fun testGetSelectedTypeShouldReturnCanEditWhenFullPermissionsGiven() { + val share = createShare(OCShare.MAXIMUM_PERMISSIONS_FOR_FILE) + assertEquals(QuickPermissionType.CAN_EDIT, SharePermissionManager.getSelectedType(share, encrypted = false)) + } + + @Test + fun testGetSelectedTypeShouldReturnSecureFileDropWhenEncryptedAndReadCreateGiven() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.CREATE_PERMISSION_FLAG + val share = createShare(permission) + assertEquals( + QuickPermissionType.SECURE_FILE_DROP, + SharePermissionManager.getSelectedType(share, encrypted = true) + ) + } + + @Test + fun testGetSelectedTypeShouldReturnFileRequestWhenCreatePermissionGiven() { + val share = createShare(OCShare.CREATE_PERMISSION_FLAG, isFolder = true) + assertEquals(QuickPermissionType.FILE_REQUEST, SharePermissionManager.getSelectedType(share, encrypted = false)) + } + + @Test + fun testGetSelectedTypeShouldReturnViewOnlyWhenReadPermissionGiven() { + val share = createShare(OCShare.READ_PERMISSION_FLAG) + assertEquals(QuickPermissionType.VIEW_ONLY, SharePermissionManager.getSelectedType(share, encrypted = false)) + } + + @Test + fun testGetSelectedTypeShouldReturnCustomPermissionOnlyWhenCustomPermissionGiven() { + val share = createShare(OCShare.READ_PERMISSION_FLAG + OCShare.UPDATE_PERMISSION_FLAG) + assertEquals( + QuickPermissionType.CUSTOM_PERMISSIONS, + SharePermissionManager.getSelectedType(share, encrypted = false) + ) + } + + @Test + fun testGetSelectedTypeShouldReturnNoneOnlyWhenNoPermissionGiven() { + val share = createShare(OCShare.NO_PERMISSION) + assertEquals( + QuickPermissionType.NONE, + SharePermissionManager.getSelectedType(share, encrypted = false) + ) + } + // endregion + + // region CustomPermissions Tests + @Test + fun testIsCustomPermissionShouldReturnFalseWhenNoPermissionsGiven() { + val permission = OCShare.NO_PERMISSION + val share = createShare(permission, isFolder = false) + assertFalse(SharePermissionManager.isCustomPermission(share)) + } + + @Test + fun testIsCustomPermissionShouldReturnFalseWhenNoReadPermissionsGiven() { + val permission = OCShare.SHARE_PERMISSION_FLAG + OCShare.UPDATE_PERMISSION_FLAG + val share = createShare(permission, isFolder = false) + assertFalse(SharePermissionManager.isCustomPermission(share)) + } + + @Test + fun testIsCustomPermissionShouldReturnTrueWhenUpdatePermissionsGivenOnFile() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.UPDATE_PERMISSION_FLAG + val share = createShare(permission, isFolder = false) + assertTrue(SharePermissionManager.isCustomPermission(share)) + } + + @Test + fun testIsCustomPermissionShouldReturnTrueWhenUpdateAndSharePermissionsGivenOnFile() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.UPDATE_PERMISSION_FLAG + OCShare.SHARE_PERMISSION_FLAG + val share = createShare(permission, isFolder = false) + assertTrue(SharePermissionManager.isCustomPermission(share)) + } + + @Test + fun testIsCustomPermissionShouldReturnFalseWhenCreatePermissionsGivenOnFile() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.CREATE_PERMISSION_FLAG + val share = createShare(permission, isFolder = false) + assertFalse(SharePermissionManager.isCustomPermission(share)) + } + + @Test + fun testIsCustomPermissionShouldReturnFalseWhenDeletePermissionsGivenOnFile() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.DELETE_PERMISSION_FLAG + val share = createShare(permission, isFolder = false) + assertFalse(SharePermissionManager.isCustomPermission(share)) + } + + @Test + fun testIsCustomPermissionShouldReturnTrueWhenCreatePermissionsGivenOnFolder() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.CREATE_PERMISSION_FLAG + val share = createShare(permission, isFolder = true) + assertTrue(SharePermissionManager.isCustomPermission(share)) + } + + @Test + fun testIsCustomPermissionShouldReturnTrueWhenMixedPermissionsOnFile() { + val permission = OCShare.READ_PERMISSION_FLAG + OCShare.UPDATE_PERMISSION_FLAG + val share = createShare(permission, isFolder = false) + assertTrue(SharePermissionManager.isCustomPermission(share)) + } + // endregion + + // region Attributes Tests + @Test + fun testToggleAllowDownloadAndSyncShouldCreateAttributeJsonIfNoneExists() { + val json = SharePermissionManager.toggleAllowDownloadAndSync(true, null) + assertNotNull(json) + val downloadAttribute = ShareAttributes.createDownloadAttributes(true) + val expectedJson = Gson().toJson(listOf(downloadAttribute)) + assertEquals(json, expectedJson) + } + + @Test + fun testIsAllowDownloadAndSyncEnabledShouldReturnFalseIfAttributeIsMissing() { + val share = createShare(OCShare.READ_PERMISSION_FLAG, attributesJson = null) + assertFalse(SharePermissionManager.isAllowDownloadAndSyncEnabled(share)) + } + // endregion +} diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt index 29c1e120ec32..c3a0db94d87c 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt @@ -9,15 +9,18 @@ package com.owncloud.android.ui.fragment import android.view.View +import androidx.annotation.UiThread +import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.intent.rule.IntentsTestRule import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isChecked import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isNotChecked +import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withText import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultBaseUtils.matchesCheckNames import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesViews @@ -38,55 +41,68 @@ import com.owncloud.android.lib.resources.shares.OCShare.Companion.READ_PERMISSI import com.owncloud.android.lib.resources.shares.OCShare.Companion.SHARE_PERMISSION_FLAG import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.ui.activity.FileDisplayActivity -import com.owncloud.android.ui.fragment.util.SharingMenuHelper +import com.owncloud.android.ui.fragment.util.SharePermissionManager +import com.owncloud.android.utils.EspressoIdlingResource import com.owncloud.android.utils.ScreenshotTest import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.anyOf import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.not import org.junit.After -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @Suppress("TooManyFunctions") class FileDetailSharingFragmentIT : AbstractIT() { - @get:Rule - val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false) + private val testClassName = "com.owncloud.android.ui.fragment.FileDetailSharingFragmentIT" @get:Rule val retryRule = RetryTestRule() lateinit var file: OCFile lateinit var folder: OCFile - lateinit var activity: TestActivity @Before - fun before() { - activity = testActivityRule.launchActivity(null) - file = OCFile("/test.md").apply { - remoteId = "00000001" - parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId - permissions = OCFile.PERMISSION_CAN_RESHARE - fileDataStorageManager.saveFile(this) - } + fun registerIdlingResource() { + IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource) + } - folder = OCFile("/test").apply { - setFolder() - parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId - permissions = OCFile.PERMISSION_CAN_RESHARE + @After + fun unregisterIdlingResource() { + IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource) + } + + @Before + fun before() { + launchActivity().use { scenario -> + scenario.onActivity { activity -> + file = OCFile("/test.md").apply { + remoteId = "00000001" + parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId + permissions = OCFile.PERMISSION_CAN_RESHARE + fileDataStorageManager.saveFile(this) + } + + folder = OCFile("/test").apply { + setFolder() + parentId = activity.storageManager.getFileByEncryptedRemotePath("/").fileId + permissions = OCFile.PERMISSION_CAN_RESHARE + } + } } } @Test + @UiThread @ScreenshotTest fun listSharesFileNone() { show(file) } @Test + @UiThread @ScreenshotTest fun listSharesFileResharingNotAllowed() { file.permissions = "" @@ -95,171 +111,196 @@ class FileDetailSharingFragmentIT : AbstractIT() { } @Test + @UiThread @ScreenshotTest fun listSharesDownloadLimit() { - OCShare(file.decryptedRemotePath).apply { - remoteId = 1 - shareType = ShareType.PUBLIC_LINK - token = "AAAAAAAAAAAAAAA" - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 2 - shareType = ShareType.PUBLIC_LINK - token = "BBBBBBBBBBBBBBB" - fileDownloadLimit = FileDownloadLimit("BBBBBBBBBBBBBBB", 0, 0) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 3 - shareType = ShareType.PUBLIC_LINK - token = "CCCCCCCCCCCCCCC" - fileDownloadLimit = FileDownloadLimit("CCCCCCCCCCCCCCC", 10, 0) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 4 - shareType = ShareType.PUBLIC_LINK - token = "DDDDDDDDDDDDDDD" - fileDownloadLimit = FileDownloadLimit("DDDDDDDDDDDDDDD", 10, 5) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 5 - shareType = ShareType.PUBLIC_LINK - token = "FFFFFFFFFFFFFFF" - fileDownloadLimit = FileDownloadLimit("FFFFFFFFFFFFFFF", 10, 10) - activity.storageManager.saveShare(this) + launchActivity().use { scenario -> + scenario.onActivity { activity -> + onIdleSync { + EspressoIdlingResource.increment() + OCShare(file.decryptedRemotePath).apply { + remoteId = 1 + shareType = ShareType.PUBLIC_LINK + token = "AAAAAAAAAAAAAAA" + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 2 + shareType = ShareType.PUBLIC_LINK + token = "BBBBBBBBBBBBBBB" + fileDownloadLimit = FileDownloadLimit("BBBBBBBBBBBBBBB", 0, 0) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 3 + shareType = ShareType.PUBLIC_LINK + token = "CCCCCCCCCCCCCCC" + fileDownloadLimit = FileDownloadLimit("CCCCCCCCCCCCCCC", 10, 0) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 4 + shareType = ShareType.PUBLIC_LINK + token = "DDDDDDDDDDDDDDD" + fileDownloadLimit = FileDownloadLimit("DDDDDDDDDDDDDDD", 10, 5) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 5 + shareType = ShareType.PUBLIC_LINK + token = "FFFFFFFFFFFFFFF" + fileDownloadLimit = FileDownloadLimit("FFFFFFFFFFFFFFF", 10, 10) + activity.storageManager.saveShare(this) + } + EspressoIdlingResource.decrement() + + show(file) + } + } } - - show(file) } /** * Use same values as {@link OCFileListFragmentStaticServerIT showSharedFiles } */ @Test + @UiThread @ScreenshotTest @Suppress("MagicNumber") fun listSharesFileAllShareTypes() { - OCShare(file.decryptedRemotePath).apply { - remoteId = 1 - shareType = ShareType.USER - sharedWithDisplayName = "Admin" - permissions = MAXIMUM_PERMISSIONS_FOR_FILE - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 2 - shareType = ShareType.GROUP - sharedWithDisplayName = "Group" - permissions = MAXIMUM_PERMISSIONS_FOR_FILE - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 3 - shareType = ShareType.EMAIL - sharedWithDisplayName = "admin@nextcloud.localhost" - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 4 - shareType = ShareType.PUBLIC_LINK - label = "Customer" - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 5 - shareType = ShareType.PUBLIC_LINK - label = "Colleagues" - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 6 - shareType = ShareType.FEDERATED - sharedWithDisplayName = "admin@nextcloud.localhost" - permissions = OCShare.FEDERATED_PERMISSIONS_FOR_FILE - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 7 - shareType = ShareType.CIRCLE - sharedWithDisplayName = "Personal team" - permissions = SHARE_PERMISSION_FLAG - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 8 - shareType = ShareType.CIRCLE - sharedWithDisplayName = "Public team" - permissions = SHARE_PERMISSION_FLAG - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 9 - shareType = ShareType.CIRCLE - sharedWithDisplayName = "Closed team" - permissions = SHARE_PERMISSION_FLAG - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 10 - shareType = ShareType.CIRCLE - sharedWithDisplayName = "Secret team" - permissions = SHARE_PERMISSION_FLAG - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 11 - shareType = ShareType.ROOM - sharedWithDisplayName = "Admin" - permissions = SHARE_PERMISSION_FLAG - userId = getUserId(user) - activity.storageManager.saveShare(this) - } - - OCShare(file.decryptedRemotePath).apply { - remoteId = 12 - shareType = ShareType.ROOM - sharedWithDisplayName = "Meeting" - permissions = SHARE_PERMISSION_FLAG - userId = getUserId(user) - activity.storageManager.saveShare(this) + launchActivity().use { scenario -> + scenario.onActivity { activity -> + onIdleSync { + EspressoIdlingResource.increment() + OCShare(file.decryptedRemotePath).apply { + remoteId = 1 + shareType = ShareType.USER + sharedWithDisplayName = "Admin" + permissions = MAXIMUM_PERMISSIONS_FOR_FILE + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 2 + shareType = ShareType.GROUP + sharedWithDisplayName = "Group" + permissions = MAXIMUM_PERMISSIONS_FOR_FILE + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 3 + shareType = ShareType.EMAIL + sharedWithDisplayName = "admin@nextcloud.localhost" + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 4 + shareType = ShareType.PUBLIC_LINK + label = "Customer" + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 5 + shareType = ShareType.PUBLIC_LINK + label = "Colleagues" + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 6 + shareType = ShareType.FEDERATED + sharedWithDisplayName = "admin@nextcloud.localhost" + permissions = OCShare.FEDERATED_PERMISSIONS_FOR_FILE + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 7 + shareType = ShareType.CIRCLE + sharedWithDisplayName = "Personal team" + permissions = SHARE_PERMISSION_FLAG + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 8 + shareType = ShareType.CIRCLE + sharedWithDisplayName = "Public team" + permissions = SHARE_PERMISSION_FLAG + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 9 + shareType = ShareType.CIRCLE + sharedWithDisplayName = "Closed team" + permissions = SHARE_PERMISSION_FLAG + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 10 + shareType = ShareType.CIRCLE + sharedWithDisplayName = "Secret team" + permissions = SHARE_PERMISSION_FLAG + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 11 + shareType = ShareType.ROOM + sharedWithDisplayName = "Admin" + permissions = SHARE_PERMISSION_FLAG + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + + OCShare(file.decryptedRemotePath).apply { + remoteId = 12 + shareType = ShareType.ROOM + sharedWithDisplayName = "Meeting" + permissions = SHARE_PERMISSION_FLAG + userId = getUserId(user) + activity.storageManager.saveShare(this) + } + EspressoIdlingResource.decrement() + + show(file) + } + } } - - show(file) } private fun show(file: OCFile) { - val fragment = FileDetailSharingFragment.newInstance(file, user) - - activity.addFragment(fragment) - - waitForIdleSync() - - screenshot(activity) + launchActivity().use { scenario -> + scenario.onActivity { sut -> + onIdleSync { + EspressoIdlingResource.increment() + val fragment = FileDetailSharingFragment.newInstance(file, user) + sut.addFragment(fragment) + EspressoIdlingResource.decrement() + + val screenShotName = createName(testClassName + "_" + "show", "") + onView(isRoot()).check(matches(isDisplayed())) + screenshotViaName(sut, screenShotName) + } + } + } } // public link and email are handled the same way @@ -267,95 +308,101 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test @Suppress("MagicNumber") fun publicLinkOptionMenuFolderAdvancePermission() { - val sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - setupSecondaryFragment() - shortSleep() - sut.refreshCapabilitiesFromDB() - - val publicShare = OCShare().apply { - isFolder = true - shareType = ShareType.PUBLIC_LINK - permissions = 17 + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) + onIdleSync { + EspressoIdlingResource.increment() + setupSecondaryFragment() + sut.refreshCapabilitiesFromDB() + + val publicShare = OCShare().apply { + isFolder = true + shareType = ShareType.PUBLIC_LINK + permissions = 17 + } + + EspressoIdlingResource.decrement() + activity.runOnUiThread { sut.showSharingMenuActionSheet(publicShare) } + + // check if items are visible + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + + // click event + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + + // validate view shown on screen + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(isDisplayed())) + + // read-only + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) + goBack() + + // upload and editing + publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) + goBack() + + // file request + publicShare.permissions = 4 + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isChecked())) + goBack() + + // password protection + publicShare.shareWith = "someValue" + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked())) + goBack() + + publicShare.shareWith = "" + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked())) + goBack() + + // hide download + publicShare.isHideFileDownload = true + publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) + goBack() + + publicShare.isHideFileDownload = false + openAdvancedPermissions(sut, publicShare) + onView( + ViewMatchers.withId(R.id.share_process_hide_download_checkbox) + ).check(matches(isNotChecked())) + goBack() + + publicShare.expirationDate = 1582019340000 + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) + goBack() + + publicShare.expirationDate = 0 + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) + } + } } - - activity.runOnUiThread { sut.showSharingMenuActionSheet(publicShare) } - shortSleep() - waitForIdleSync() - - // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) - - // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) - - // validate view shown on screen - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(not(isDisplayed()))) - - // read-only - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked())) - goBack() - - // upload and editing - publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked())) - goBack() - - // file drop - publicShare.permissions = 4 - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isChecked())) - goBack() - - // password protection - publicShare.shareWith = "someValue" - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked())) - goBack() - - publicShare.shareWith = "" - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked())) - goBack() - - // hide download - publicShare.isHideFileDownload = true - publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) - goBack() - - publicShare.isHideFileDownload = false - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isNotChecked())) - goBack() - - publicShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() - - publicShare.expirationDate = 0 - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } // public link and email are handled the same way @@ -363,32 +410,43 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test @Suppress("MagicNumber") fun publicLinkOptionMenuFolderSendNewEmail() { - val sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - setupSecondaryFragment() - shortSleep() - sut.refreshCapabilitiesFromDB() - - val publicShare = OCShare().apply { - isFolder = true - shareType = ShareType.PUBLIC_LINK - permissions = 17 + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) + onIdleSync { + EspressoIdlingResource.increment() + setupSecondaryFragment() + sut.refreshCapabilitiesFromDB() + EspressoIdlingResource.decrement() + + val publicShare = OCShare().apply { + isFolder = true + shareType = ShareType.PUBLIC_LINK + permissions = 17 + } + + verifySendNewEmail(sut, publicShare) + } + } } - - verifySendNewEmail(sut, publicShare) } private fun setupSecondaryFragment() { - val parentFolder = OCFile("/") - val secondary = FileDetailFragment.newInstance(file, parentFolder, user) - activity.addSecondaryFragment(secondary, FileDisplayActivity.TAG_LIST_OF_FILES) - activity.addView( - FloatingActionButton(activity).apply { - // needed for some reason - visibility = View.GONE - id = R.id.fab_main + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val parentFolder = OCFile("/") + val secondary = FileDetailFragment.newInstance(file, parentFolder, user) + activity.addSecondaryFragment(secondary, FileDisplayActivity.TAG_LIST_OF_FILES) + activity.addView( + FloatingActionButton(activity).apply { + // needed for some reason + visibility = View.GONE + id = R.id.fab_main + } + ) } - ) + } } // public link and email are handled the same way @@ -396,86 +454,97 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test @Suppress("MagicNumber") fun publicLinkOptionMenuFileAdvancePermission() { - val sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - setupSecondaryFragment() - shortSleep() - sut.refreshCapabilitiesFromDB() - - val publicShare = OCShare().apply { - isFolder = false - shareType = ShareType.PUBLIC_LINK - permissions = 17 + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) + + onIdleSync { + EspressoIdlingResource.increment() + setupSecondaryFragment() + sut.refreshCapabilitiesFromDB() + EspressoIdlingResource.decrement() + + val publicShare = OCShare().apply { + isFolder = false + shareType = ShareType.PUBLIC_LINK + permissions = 17 + } + activity.handler.post { sut.showSharingMenuActionSheet(publicShare) } + waitForIdleSync() + + // check if items are visible + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + + // click event + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + + // validate view shown on screen + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) + onView( + ViewMatchers.withId(R.id.file_request_radio_button) + ).check(matches(not(isDisplayed()))) + onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(isDisplayed())) + + // read-only + publicShare.permissions = 17 // from server + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) + goBack() + + // editing + publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) + goBack() + + // hide download + publicShare.isHideFileDownload = true + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) + goBack() + + publicShare.isHideFileDownload = false + openAdvancedPermissions(sut, publicShare) + onView( + ViewMatchers.withId(R.id.share_process_hide_download_checkbox) + ).check(matches(isNotChecked())) + goBack() + + // password protection + publicShare.isPasswordProtected = true + publicShare.shareWith = "someValue" + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked())) + goBack() + + publicShare.isPasswordProtected = false + publicShare.shareWith = "" + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked())) + goBack() + + // expires + publicShare.expirationDate = 1582019340 + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) + goBack() + + publicShare.expirationDate = 0 + openAdvancedPermissions(sut, publicShare) + onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) + } + } } - activity.handler.post { sut.showSharingMenuActionSheet(publicShare) } - waitForIdleSync() - - // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) - - // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) - - // validate view shown on screen - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(not(isDisplayed()))) - - // read-only - publicShare.permissions = 17 // from server - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked())) - goBack() - - // editing - publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked())) - goBack() - - // hide download - publicShare.isHideFileDownload = true - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) - goBack() - - publicShare.isHideFileDownload = false - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isNotChecked())) - goBack() - - // password protection - publicShare.isPasswordProtected = true - publicShare.shareWith = "someValue" - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked())) - goBack() - - publicShare.isPasswordProtected = false - publicShare.shareWith = "" - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked())) - goBack() - - // expires - publicShare.expirationDate = 1582019340 - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() - - publicShare.expirationDate = 0 - openAdvancedPermissions(sut, publicShare) - onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } // public link and email are handled the same way @@ -483,19 +552,26 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test @Suppress("MagicNumber") fun publicLinkOptionMenuFileSendNewEmail() { - val sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - setupSecondaryFragment() - shortSleep() - sut.refreshCapabilitiesFromDB() - - val publicShare = OCShare().apply { - isFolder = false - shareType = ShareType.PUBLIC_LINK - permissions = 17 + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) + onIdleSync { + EspressoIdlingResource.increment() + setupSecondaryFragment() + sut.refreshCapabilitiesFromDB() + EspressoIdlingResource.decrement() + + val publicShare = OCShare().apply { + isFolder = false + shareType = ShareType.PUBLIC_LINK + permissions = 17 + } + + verifySendNewEmail(sut, publicShare) + } + } } - - verifySendNewEmail(sut, publicShare) } // also applies for @@ -507,78 +583,80 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test @Suppress("MagicNumber") fun userOptionMenuFileAdvancePermission() { - val sut = FileDetailSharingFragment.newInstance(file, user) - suppressFDFAccessibilityChecks() - activity.addFragment(sut) - setupSecondaryFragment() - shortSleep() - sut.refreshCapabilitiesFromDB() - - val userShare = OCShare().apply { - isFolder = false - shareType = ShareType.USER - permissions = 17 + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + suppressFDFAccessibilityChecks() + activity.addFragment(sut) + + onIdleSync { + EspressoIdlingResource.increment() + setupSecondaryFragment() + sut.refreshCapabilitiesFromDB() + EspressoIdlingResource.decrement() + + val userShare = OCShare().apply { + isFolder = false + shareType = ShareType.USER + permissions = 17 + } + + activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } + + // check if items are visible + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed()))) + onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + + // click event + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + shortSleep() + waitForIdleSync() + + // validate view shown on screen + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) + onView( + ViewMatchers.withId(R.id.file_request_radio_button) + ).check(matches(not(isDisplayed()))) + onView( + ViewMatchers.withId(R.id.share_process_hide_download_checkbox) + ).check(matches(not(isDisplayed()))) + onView( + ViewMatchers.withId(R.id.share_process_set_password_switch) + ).check(matches(not(isDisplayed()))) + onView( + ViewMatchers.withId(R.id.share_process_change_name_switch) + ).check(matches(not(isDisplayed()))) + + // read-only + userShare.permissions = 17 // from server + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) + goBack() + + // editing + userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server + openAdvancedPermissions(sut, userShare) + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) + goBack() + + // set expiration date + userShare.expirationDate = 1582019340000 + openAdvancedPermissions(sut, userShare) + onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) + goBack() + + userShare.expirationDate = 0 + openAdvancedPermissions(sut, userShare) + onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) + } + } } - - activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } - shortSleep() - waitForIdleSync() - - // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) - - // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) - shortSleep() - waitForIdleSync() - - // validate view shown on screen - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isDisplayed())) - - // read-only - userShare.permissions = 17 // from server - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked())) - goBack() - - // editing - userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked())) - goBack() - - // allow reshare - userShare.permissions = 1 // from server - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isNotChecked())) - goBack() - - userShare.permissions = 17 // from server - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isChecked())) - goBack() - - // set expiration date - userShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() - - userShare.expirationDate = 0 - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } private fun suppressFDFAccessibilityChecks() { @@ -607,20 +685,28 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test @Suppress("MagicNumber") fun userOptionMenuFileSendNewEmail() { - val sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - setupSecondaryFragment() - shortSleep() - sut.refreshCapabilitiesFromDB() - - val userShare = OCShare().apply { - remoteId = 1001L - isFolder = false - shareType = ShareType.USER - permissions = 17 + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) + + onIdleSync { + EspressoIdlingResource.increment() + setupSecondaryFragment() + sut.refreshCapabilitiesFromDB() + EspressoIdlingResource.decrement() + + val userShare = OCShare().apply { + remoteId = 1001L + isFolder = false + shareType = ShareType.USER + permissions = 17 + } + + verifySendNewEmail(sut, userShare) + } + } } - - verifySendNewEmail(sut, userShare) } // also applies for @@ -632,108 +718,118 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test @Suppress("MagicNumber") fun userOptionMenuFolderAdvancePermission() { - val sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - setupSecondaryFragment() - suppressFDFAccessibilityChecks() - shortSleep() - sut.refreshCapabilitiesFromDB() - - val userShare = OCShare().apply { - isFolder = true - shareType = ShareType.USER - permissions = 17 + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) + + onIdleSync { + EspressoIdlingResource.increment() + setupSecondaryFragment() + suppressFDFAccessibilityChecks() + sut.refreshCapabilitiesFromDB() + EspressoIdlingResource.decrement() + + val userShare = OCShare().apply { + isFolder = true + shareType = ShareType.USER + permissions = 17 + } + + activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } + + // check if items are visible + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed()))) + onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + + // click event + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + + // validate view shown on screen + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isDisplayed())) + onView( + ViewMatchers.withId(R.id.share_process_hide_download_checkbox) + ).check(matches(not(isDisplayed()))) + onView( + ViewMatchers.withId(R.id.share_process_set_password_switch) + ).check(matches(not(isDisplayed()))) + onView( + ViewMatchers.withId(R.id.share_process_change_name_switch) + ).check(matches(not(isDisplayed()))) + + // read-only + userShare.permissions = 17 // from server + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) + goBack() + + // allow upload & editing + userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER // from server + openAdvancedPermissions(sut, userShare) + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) + goBack() + + // file request + userShare.permissions = 4 + openAdvancedPermissions(sut, userShare) + onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isChecked())) + goBack() + + // set expiration date + userShare.expirationDate = 1582019340000 + openAdvancedPermissions(sut, userShare) + onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) + onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) + goBack() + + userShare.expirationDate = 0 + openAdvancedPermissions(sut, userShare) + onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) + onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) + } + } } - - activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } - shortSleep() - waitForIdleSync() - - // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) - - // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) - - // validate view shown on screen - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.share_process_change_name_switch)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isDisplayed())) - - // read-only - userShare.permissions = 17 // from server - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked())) - goBack() - - // allow upload & editing - userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER // from server - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isNotChecked())) - goBack() - - // file drop - userShare.permissions = 4 - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_permission_read_only)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_upload_editing)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_permission_file_drop)).check(matches(isChecked())) - goBack() - - // allow reshare - userShare.permissions = 1 // from server - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isNotChecked())) - goBack() - - userShare.permissions = 17 // from server - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_allow_resharing_checkbox)).check(matches(isChecked())) - goBack() - - // set expiration date - userShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() - - userShare.expirationDate = 0 - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } // open bottom sheet with actions private fun openAdvancedPermissions(sut: FileDetailSharingFragment, userShare: OCShare) { - activity.handler.post { - sut.showSharingMenuActionSheet(userShare) + launchActivity().use { scenario -> + scenario.onActivity { activity -> + onIdleSync { + EspressoIdlingResource.increment() + activity.handler.post { + sut.showSharingMenuActionSheet(userShare) + } + EspressoIdlingResource.decrement() + onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + } + } } - shortSleep() - waitForIdleSync() - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) } // remove the fragment shown private fun goBack() { - activity.handler.post { - val processFragment = - activity.supportFragmentManager.findFragmentByTag(FileDetailsSharingProcessFragment.TAG) as - FileDetailsSharingProcessFragment - processFragment.onBackPressed() + launchActivity().use { scenario -> + scenario.onActivity { activity -> + onIdleSync { + activity.handler.post { + val processFragment = + activity.supportFragmentManager.findFragmentByTag(FileDetailsSharingProcessFragment.TAG) as + FileDetailsSharingProcessFragment + processFragment.onBackPressed() + } + } + } } - shortSleep() - waitForIdleSync() } // also applies for @@ -745,123 +841,105 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Test @Suppress("MagicNumber") fun userOptionMenuFolderSendNewEmail() { - val sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - setupSecondaryFragment() - shortSleep() - sut.refreshCapabilitiesFromDB() - - val userShare = OCShare().apply { - isFolder = true - shareType = ShareType.USER - permissions = 17 + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) + onIdleSync { + EspressoIdlingResource.increment() + setupSecondaryFragment() + sut.refreshCapabilitiesFromDB() + EspressoIdlingResource.decrement() + + val userShare = OCShare().apply { + isFolder = true + shareType = ShareType.USER + permissions = 17 + } + + verifySendNewEmail(sut, userShare) + } + } } - - verifySendNewEmail(sut, userShare) } /** * verify send new email note text */ private fun verifySendNewEmail(sut: FileDetailSharingFragment, userShare: OCShare) { - activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } - - waitForIdleSync() - // click event - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).perform(ViewActions.click()) - - // validate view shown on screen - onView(ViewMatchers.withId(R.id.note_text)).check(matches(isDisplayed())) + launchActivity().use { scenario -> + scenario.onActivity { activity -> + onIdleSync { + EspressoIdlingResource.increment() + activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } + EspressoIdlingResource.decrement() + + // click event + onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).perform(ViewActions.click()) + + // validate view shown on screen + onView(ViewMatchers.withId(R.id.note_text)).check(matches(isDisplayed())) + } + } + } } @Test fun testUploadAndEditingSharePermissions() { - val share = OCShare().apply { - permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - } - assertTrue(SharingMenuHelper.isUploadAndEditingAllowed(share)) - - share.permissions = NO_PERMISSION - assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share)) - - share.permissions = READ_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share)) - - share.permissions = CREATE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share)) - - share.permissions = DELETE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share)) + val testCases = mapOf( + MAXIMUM_PERMISSIONS_FOR_FOLDER to true, + NO_PERMISSION to false, + READ_PERMISSION_FLAG to false, + CREATE_PERMISSION_FLAG to false, + DELETE_PERMISSION_FLAG to false, + SHARE_PERMISSION_FLAG to false + ) - share.permissions = SHARE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share)) + val share = OCShare() + for ((permission, expected) in testCases) { + share.permissions = permission + assertEquals("Failed for permission: $permission", expected, SharePermissionManager.canEdit(share)) + } } @Test - @Suppress("MagicNumber") fun testReadOnlySharePermissions() { - val share = OCShare().apply { - permissions = 17 - } - assertTrue(SharingMenuHelper.isReadOnly(share)) - - share.permissions = NO_PERMISSION - assertFalse(SharingMenuHelper.isReadOnly(share)) - - share.permissions = READ_PERMISSION_FLAG - assertTrue(SharingMenuHelper.isReadOnly(share)) - - share.permissions = CREATE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isReadOnly(share)) - - share.permissions = DELETE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isReadOnly(share)) - - share.permissions = SHARE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isReadOnly(share)) - - share.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - assertFalse(SharingMenuHelper.isReadOnly(share)) + val testCases = mapOf( + READ_PERMISSION_FLAG to true, + NO_PERMISSION to false, + CREATE_PERMISSION_FLAG to false, + DELETE_PERMISSION_FLAG to false, + SHARE_PERMISSION_FLAG to false, + MAXIMUM_PERMISSIONS_FOR_FOLDER to false, + MAXIMUM_PERMISSIONS_FOR_FILE to false + ) - share.permissions = MAXIMUM_PERMISSIONS_FOR_FILE - assertFalse(SharingMenuHelper.isReadOnly(share)) + val share = OCShare() + for ((permission, expected) in testCases) { + share.permissions = permission + assertEquals("Failed for permission: $permission", expected, SharePermissionManager.isViewOnly(share)) + } } @Test - @Suppress("MagicNumber") - fun testFileDropSharePermissions() { + fun testFileRequestSharePermission() { + val testCases = mapOf( + CREATE_PERMISSION_FLAG to true, + NO_PERMISSION to false, + READ_PERMISSION_FLAG to false, + DELETE_PERMISSION_FLAG to false, + SHARE_PERMISSION_FLAG to false, + MAXIMUM_PERMISSIONS_FOR_FOLDER to false, + MAXIMUM_PERMISSIONS_FOR_FILE to false + ) + val share = OCShare().apply { - permissions = 4 + isFolder = true } - assertTrue(SharingMenuHelper.isFileDrop(share)) - - share.permissions = NO_PERMISSION - assertFalse(SharingMenuHelper.isFileDrop(share)) - - share.permissions = READ_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isFileDrop(share)) - - share.permissions = CREATE_PERMISSION_FLAG - assertTrue(SharingMenuHelper.isFileDrop(share)) - - share.permissions = DELETE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isFileDrop(share)) - share.permissions = SHARE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isFileDrop(share)) - - share.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - assertFalse(SharingMenuHelper.isFileDrop(share)) - - share.permissions = MAXIMUM_PERMISSIONS_FOR_FILE - assertFalse(SharingMenuHelper.isFileDrop(share)) - } - - @After - override fun after() { - activity.storageManager.cleanShares() - activity.finish() - - super.after() + for ((permission, expected) in testCases) { + share.permissions = permission + assertEquals("Failed for permission: $permission", expected, SharePermissionManager.isFileRequest(share)) + } } } diff --git a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt index 6f0dfca9ca9c..55177ef6e4cf 100644 --- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt +++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt @@ -72,7 +72,8 @@ import com.owncloud.android.db.ProviderMeta AutoMigration(from = 85, to = 86, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 86, to = 87, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 87, to = 88, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), - AutoMigration(from = 88, to = 89) + AutoMigration(from = 88, to = 89), + AutoMigration(from = 89, to = 90) ], exportSchema = true ) diff --git a/app/src/main/java/com/nextcloud/client/database/entity/ShareEntity.kt b/app/src/main/java/com/nextcloud/client/database/entity/ShareEntity.kt index 1c397ced030d..ad5005efef20 100644 --- a/app/src/main/java/com/nextcloud/client/database/entity/ShareEntity.kt +++ b/app/src/main/java/com/nextcloud/client/database/entity/ShareEntity.kt @@ -58,5 +58,7 @@ data class ShareEntity( @ColumnInfo(name = ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT) val downloadLimitLimit: Int?, @ColumnInfo(name = ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT) - val downloadLimitCount: Int? + val downloadLimitCount: Int?, + @ColumnInfo(name = ProviderTableMeta.OCSHARES_ATTRIBUTES) + val attributes: String? ) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/ImageViewExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/ImageViewExtensions.kt new file mode 100644 index 000000000000..896d26dca84e --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/ImageViewExtensions.kt @@ -0,0 +1,49 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import android.content.Context +import android.graphics.drawable.GradientDrawable +import android.util.TypedValue +import android.view.ViewOutlineProvider +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import com.owncloud.android.R + +@JvmOverloads +fun ImageView.makeRoundedWithIcon( + context: Context, + @DrawableRes icon: Int, + paddingDp: Int = 6, + @ColorInt backgroundColor: Int = ContextCompat.getColor(context, R.color.primary), + @ColorInt foregroundColor: Int = ContextCompat.getColor(context, R.color.white) +) { + setImageResource(icon) + + val drawable = GradientDrawable().apply { + shape = GradientDrawable.OVAL + setColor(backgroundColor) + } + + background = drawable + clipToOutline = true + scaleType = ImageView.ScaleType.CENTER_INSIDE + outlineProvider = ViewOutlineProvider.BACKGROUND + + setColorFilter(foregroundColor) + + val paddingPx = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + paddingDp.toFloat(), + context.resources.displayMetrics + ).toInt() + + setPadding(paddingPx, paddingPx, paddingPx, paddingPx) +} diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCShareExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCShareExtensions.kt new file mode 100644 index 000000000000..45cfa46dfd63 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCShareExtensions.kt @@ -0,0 +1,18 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Your Name + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import com.owncloud.android.lib.resources.shares.OCShare + +fun OCShare.hasFileRequestPermission(): Boolean { + return (isFolder && shareType?.isPublicOrMail() == true) +} + +fun List.mergeDistinctByToken(other: List): List { + return (this + other).distinctBy { it.token } +} diff --git a/app/src/main/java/com/nextcloud/utils/extensions/ShareTypeExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/ShareTypeExtensions.kt new file mode 100644 index 000000000000..4200fae86634 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/ShareTypeExtensions.kt @@ -0,0 +1,12 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import com.owncloud.android.lib.resources.shares.ShareType + +fun ShareType.isPublicOrMail(): Boolean = (this == ShareType.PUBLIC_LINK || this == ShareType.EMAIL) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt index 02cdbc3f91ee..a47cff281060 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt @@ -7,6 +7,8 @@ */ package com.nextcloud.utils.extensions +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.content.Context import android.graphics.Outline import android.util.TypedValue @@ -19,6 +21,30 @@ fun View?.setVisibleIf(condition: Boolean) { visibility = if (condition) View.VISIBLE else View.GONE } +fun View?.setVisibilityWithAnimation(condition: Boolean, duration: Long = 200L) { + this ?: return + + if (condition) { + this.apply { + alpha = 0f + visibility = View.VISIBLE + animate() + .alpha(1f) + .setDuration(duration) + .setListener(null) + } + } else { + animate() + .alpha(0f) + .setDuration(duration) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + visibility = View.GONE + } + }) + } +} + fun View?.makeRounded(context: Context, cornerRadius: Float) { this?.let { it.apply { diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 2f84154ede97..de005a984eba 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1569,6 +1569,8 @@ private ContentValues createContentValueForShare(OCShare share) { contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); } + contentValues.put(ProviderTableMeta.OCSHARES_ATTRIBUTES, share.getAttributes()); + return contentValues; } @@ -1599,6 +1601,8 @@ private OCShare createShareInstance(Cursor cursor) { getInt(cursor, ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT)); share.setFileDownloadLimit(downloadLimit); + share.setAttributes(getString(cursor, ProviderTableMeta.OCSHARES_ATTRIBUTES)); + return share; } diff --git a/app/src/main/java/com/owncloud/android/datamodel/QuickPermissionModel.kt b/app/src/main/java/com/owncloud/android/datamodel/QuickPermissionModel.kt deleted file mode 100644 index b24bfcd88abf..000000000000 --- a/app/src/main/java/com/owncloud/android/datamodel/QuickPermissionModel.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Nextcloud Android client application - * - * @author TSI-mc - * Copyright (C) 2021 TSI-mc - * Copyright (C) 2021 Nextcloud GmbH - * - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ - -package com.owncloud.android.datamodel - -data class QuickPermissionModel(val permissionName: String, val isSelected: Boolean) diff --git a/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermission.kt b/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermission.kt new file mode 100644 index 000000000000..460edeb33350 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermission.kt @@ -0,0 +1,10 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.datamodel.quickPermission + +data class QuickPermission(val type: QuickPermissionType, var isSelected: Boolean) diff --git a/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermissionType.kt b/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermissionType.kt new file mode 100644 index 000000000000..51dfbb5bf23c --- /dev/null +++ b/app/src/main/java/com/owncloud/android/datamodel/quickPermission/QuickPermissionType.kt @@ -0,0 +1,46 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.datamodel.quickPermission + +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.core.content.ContextCompat +import com.owncloud.android.R + +enum class QuickPermissionType( + val iconId: Int, + val textId: Int +) { + NONE(R.drawable.ic_unknown, R.string.unknown), + VIEW_ONLY(R.drawable.ic_eye, R.string.share_permission_view_only), + CAN_EDIT(R.drawable.ic_edit, R.string.share_permission_can_edit), + FILE_REQUEST(R.drawable.ic_file_request, R.string.share_permission_file_request), + SECURE_FILE_DROP(R.drawable.ic_file_request, R.string.share_permission_secure_file_drop), + CUSTOM_PERMISSIONS(R.drawable.ic_custom_permissions, R.string.share_custom_permission); + + fun getText(context: Context): String = context.getString(textId) + + fun getIcon(context: Context): Drawable? = ContextCompat.getDrawable(context, iconId) + + companion object { + fun getAvailablePermissions( + hasFileRequestPermission: Boolean, + selectedType: QuickPermissionType + ): List { + val permissions = listOf(VIEW_ONLY, CAN_EDIT, FILE_REQUEST, CUSTOM_PERMISSIONS) + val result = if (hasFileRequestPermission) permissions else permissions.filter { it != FILE_REQUEST } + + return result.map { type -> + QuickPermission( + type = type, + isSelected = (type == selectedType) + ) + } + } + } +} diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java index 955aeb1d9044..120da6ac9d8a 100644 --- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -25,7 +25,7 @@ */ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 89; + public static final int DB_VERSION = 90; private ProviderMeta() { // No instance @@ -203,6 +203,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String OCSHARES_SHARE_LABEL = "share_label"; public static final String OCSHARES_DOWNLOADLIMIT_LIMIT = "download_limit_limit"; public static final String OCSHARES_DOWNLOADLIMIT_COUNT = "download_limit_count"; + public static final String OCSHARES_ATTRIBUTES = "attributes"; public static final String OCSHARES_DEFAULT_SORT_ORDER = OCSHARES_FILE_SOURCE + " collate nocase asc"; diff --git a/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java index 469829bbaae5..68a5751938cb 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java @@ -56,6 +56,7 @@ public class CreateShareWithShareeOperation extends SyncOperation { private String label; private final Context context; private final User user; + private String attributes; private ArbitraryDataProvider arbitraryDataProvider; @@ -85,6 +86,7 @@ public CreateShareWithShareeOperation(String path, String sharePassword, long expirationDateInMillis, boolean hideFileDownload, + String attributes, FileDataStorageManager storageManager, Context context, User user, @@ -105,6 +107,7 @@ public CreateShareWithShareeOperation(String path, this.context = context; this.user = user; this.arbitraryDataProvider = arbitraryDataProvider; + this.attributes = attributes; } @Override @@ -156,7 +159,8 @@ protected RemoteOperationResult run(OwnCloudClient client) { false, sharePassword, permissions, - noteMessage + noteMessage, + attributes ); operation.setGetShareDetails(true); RemoteOperationResult shareResult = operation.execute(client); diff --git a/app/src/main/java/com/owncloud/android/operations/UnshareOperation.java b/app/src/main/java/com/owncloud/android/operations/UnshareOperation.java index bdfc32923a97..8168c0dd1197 100644 --- a/app/src/main/java/com/owncloud/android/operations/UnshareOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UnshareOperation.java @@ -113,6 +113,8 @@ protected RemoteOperationResult run(OwnCloudClient client) { RemoveShareRemoteOperation operation = new RemoveShareRemoteOperation(share.getRemoteId()); result = operation.execute(client); + boolean isFileExists = existsFile(client, file.getRemotePath()); + boolean isShareExists = getStorageManager().getShareById(shareId) != null; if (result.isSuccess()) { // E2E: unlock folder @@ -140,10 +142,12 @@ protected RemoteOperationResult run(OwnCloudClient client) { getStorageManager().saveFile(file); getStorageManager().removeShare(share); - - } else if (result.getCode() != ResultCode.MAINTENANCE_MODE && !existsFile(client, file.getRemotePath())) { - // unshare failed because file was deleted before + } else if (result.getCode() != ResultCode.MAINTENANCE_MODE && !isFileExists) { + // UnShare failed because file was deleted before getStorageManager().removeFile(file, true, true); + } else if (isShareExists && result.getCode() == ResultCode.FILE_NOT_FOUND) { + // UnShare failed because share was deleted before + getStorageManager().removeShare(share); } } else { diff --git a/app/src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java b/app/src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java index 918e5577a0cc..1c078c20045b 100644 --- a/app/src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UpdateShareInfoOperation.java @@ -35,6 +35,7 @@ public class UpdateShareInfoOperation extends SyncOperation { private int permissions = -1; private String password; private String label; + private String attributes; /** * Constructor @@ -78,7 +79,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (share == null) { // TODO try to get remote share before failing? - return new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); + return new RemoteOperationResult<>(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); } // Update remote share @@ -93,11 +94,12 @@ protected RemoteOperationResult run(OwnCloudClient client) { } updateOp.setPassword(password); updateOp.setLabel(label); + updateOp.setAttributes(attributes); - RemoteOperationResult result = updateOp.execute(client); + var result = updateOp.execute(client); if (result.isSuccess()) { - RemoteOperation getShareOp = new GetShareRemoteOperation(share.getRemoteId()); + final var getShareOp = new GetShareRemoteOperation(share.getRemoteId()); result = getShareOp.execute(client); //only update the share in storage if shareId is available @@ -125,6 +127,10 @@ public void setHideFileDownload(boolean hideFileDownload) { this.hideFileDownload = hideFileDownload; } + public void setAttributes(String attributes) { + this.attributes = attributes; + } + public void setPermissions(int permissions) { this.permissions = permissions; } diff --git a/app/src/main/java/com/owncloud/android/services/OperationsService.java b/app/src/main/java/com/owncloud/android/services/OperationsService.java index 4ccadf9d6b0a..d2b8a9c75dae 100644 --- a/app/src/main/java/com/owncloud/android/services/OperationsService.java +++ b/app/src/main/java/com/owncloud/android/services/OperationsService.java @@ -101,6 +101,7 @@ public class OperationsService extends Service { public static final String EXTRA_SHARE_NOTE = "SHARE_NOTE"; public static final String EXTRA_IN_BACKGROUND = "IN_BACKGROUND"; public static final String EXTRA_FILES_DOWNLOAD_LIMIT = "FILES_DOWNLOAD_LIMIT"; + public static final String EXTRA_SHARE_ATTRIBUTES = "SHARE_ATTRIBUTES"; public static final String ACTION_CREATE_SHARE_VIA_LINK = "CREATE_SHARE_VIA_LINK"; public static final String ACTION_CREATE_SECURE_FILE_DROP = "CREATE_SECURE_FILE_DROP"; @@ -437,6 +438,11 @@ private void nextOperation() { // perform the operation try { result = mCurrentOperation.execute(mOwnCloudClient); + if (!result.isSuccess()) { + final var code = "code: " + result.getCode(); + final var httpCode = "HTTP_CODE: " + result.getHttpCode(); + Log_OC.e(TAG,"Operation failed " + code + httpCode); + } } catch (UnsupportedOperationException e) { // TODO remove - added to aid in transition to NextcloudClient @@ -593,6 +599,8 @@ private Pair newOperation(Intent operationIntent) { .getLongExtra(EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, 0L); boolean hideFileDownload = operationIntent.getBooleanExtra(EXTRA_SHARE_HIDE_FILE_DOWNLOAD, false); + String attributes = operationIntent.getStringExtra(EXTRA_SHARE_ATTRIBUTES); + if (!TextUtils.isEmpty(remotePath)) { CreateShareWithShareeOperation createShareWithShareeOperation = new CreateShareWithShareeOperation(remotePath, @@ -603,6 +611,7 @@ private Pair newOperation(Intent operationIntent) { sharePassword, expirationDateInMillis, hideFileDownload, + attributes, fileDataStorageManager, getApplicationContext(), user, @@ -641,6 +650,9 @@ private Pair newOperation(Intent operationIntent) { updateShare.setLabel(operationIntent.getStringExtra(EXTRA_SHARE_PUBLIC_LABEL)); } + String shareAttributes = operationIntent.getStringExtra(EXTRA_SHARE_ATTRIBUTES); + updateShare.setAttributes(shareAttributes); + operation = updateShare; } break; diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index d78a98bddf46..b6c93efd0df4 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -426,8 +426,10 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe onCreateShareViaLinkOperationFinish((CreateShareViaLinkOperation) operation, result); } else if (operation instanceof CreateShareWithShareeOperation) { onUpdateShareInformation(result, R.string.sharee_add_failed); - } else if (operation instanceof UpdateShareViaLinkOperation || operation instanceof UpdateShareInfoOperation || operation instanceof SetFilesDownloadLimitOperation) { + } else if (operation instanceof UpdateShareViaLinkOperation || operation instanceof UpdateShareInfoOperation) { onUpdateShareInformation(result, R.string.updating_share_failed); + } else if (operation instanceof SetFilesDownloadLimitOperation) { + onUpdateShareInformation(result, R.string.set_download_limit_failed); } else if (operation instanceof UpdateSharePermissionsOperation) { onUpdateShareInformation(result, R.string.updating_share_failed); } else if (operation instanceof UnshareOperation) { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java index b2e70da44dd8..15e35a76093f 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java @@ -15,7 +15,6 @@ package com.owncloud.android.ui.adapter; import android.content.Context; -import android.graphics.PorterDuff; import android.text.TextUtils; import android.view.View; @@ -23,9 +22,10 @@ import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.R; import com.owncloud.android.databinding.FileDetailsShareLinkShareItemBinding; +import com.owncloud.android.datamodel.quickPermission.QuickPermissionType; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.lib.resources.shares.ShareType; -import com.owncloud.android.ui.fragment.util.SharingMenuHelper; +import com.owncloud.android.ui.fragment.util.SharePermissionManager; import com.owncloud.android.utils.theme.ViewThemeUtils; import androidx.annotation.NonNull; @@ -36,6 +36,7 @@ class LinkShareViewHolder extends RecyclerView.ViewHolder { private FileDetailsShareLinkShareItemBinding binding; private Context context; private ViewThemeUtils viewThemeUtils; + private boolean encrypted; public LinkShareViewHolder(@NonNull View itemView) { super(itemView); @@ -43,40 +44,42 @@ public LinkShareViewHolder(@NonNull View itemView) { public LinkShareViewHolder(FileDetailsShareLinkShareItemBinding binding, Context context, - final ViewThemeUtils viewThemeUtils) { + final ViewThemeUtils viewThemeUtils, + boolean encrypted) { this(binding.getRoot()); this.binding = binding; this.context = context; this.viewThemeUtils = viewThemeUtils; + this.encrypted = encrypted; } - public void bind(OCShare publicShare, ShareeListAdapterListener listener) { + public void bind(OCShare publicShare, ShareeListAdapterListener listener, int position) { if (ShareType.EMAIL == publicShare.getShareType()) { + final var res = context.getResources(); binding.name.setText(publicShare.getSharedWithDisplayName()); - binding.icon.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), - R.drawable.ic_email, - null)); - binding.copyLink.setVisibility(View.GONE); - binding.icon.getBackground().setColorFilter(context.getResources().getColor(R.color.nc_grey), - PorterDuff.Mode.SRC_IN); - binding.icon.getDrawable().mutate().setColorFilter(context.getResources().getColor(R.color.icon_on_nc_grey), - PorterDuff.Mode.SRC_IN); + final var emailDrawable = ResourcesCompat.getDrawable(res, R.drawable.ic_email, null); + binding.icon.setImageDrawable(emailDrawable); + binding.copyLink.setVisibility(View.GONE); } else { - if (!TextUtils.isEmpty(publicShare.getLabel())) { - String text = String.format(context.getString(R.string.share_link_with_label), publicShare.getLabel()); - binding.name.setText(text); + String label = publicShare.getLabel(); + + if (!TextUtils.isEmpty(label)) { + binding.name.setText(context.getString(R.string.share_link_with_label, label)); + } else if (SharePermissionManager.INSTANCE.isFileRequest(publicShare)) { + binding.name.setText(R.string.share_permission_file_request); + } else if (SharePermissionManager.INSTANCE.isSecureFileDrop(publicShare) && encrypted) { + binding.name.setText(R.string.share_permission_secure_file_drop); } else { - if (SharingMenuHelper.isSecureFileDrop(publicShare)) { - binding.name.setText(context.getResources().getString(R.string.share_permission_secure_file_drop)); - } else { - binding.name.setText(R.string.share_link); - } + int textRes = (position == 0) ? R.string.share_link : R.string.share_link_with_label; + Object arg = (position == 0) ? null : String.valueOf(position); + binding.name.setText((position == 0) ? context.getString(textRes) + : context.getString(textRes, arg)); } - - viewThemeUtils.platform.colorImageViewBackgroundAndIcon(binding.icon); } + viewThemeUtils.platform.colorImageViewBackgroundAndIcon(binding.icon); + FileDownloadLimit downloadLimit = publicShare.getFileDownloadLimit(); if (downloadLimit != null && downloadLimit.getLimit() > 0) { int remaining = downloadLimit.getLimit() - downloadLimit.getCount(); @@ -88,11 +91,11 @@ public void bind(OCShare publicShare, ShareeListAdapterListener listener) { binding.subline.setVisibility(View.GONE); } - String permissionName = SharingMenuHelper.getPermissionName(context, publicShare); - setPermissionName(publicShare, permissionName); + QuickPermissionType quickPermissionType = SharePermissionManager.INSTANCE.getSelectedType(publicShare, encrypted); + setPermissionName(publicShare, quickPermissionType.getText(context)); binding.overflowMenu.setOnClickListener(v -> listener.showSharingMenuActionSheet(publicShare)); - if (!SharingMenuHelper.isSecureFileDrop(publicShare)) { + if (!SharePermissionManager.INSTANCE.isSecureFileDrop(publicShare) && !encrypted) { binding.shareByLinkContainer.setOnClickListener(v -> listener.showPermissionsDialog(publicShare)); } @@ -104,12 +107,13 @@ public void bind(OCShare publicShare, ShareeListAdapterListener listener) { } private void setPermissionName(OCShare publicShare, String permissionName) { - if (!TextUtils.isEmpty(permissionName) && !SharingMenuHelper.isSecureFileDrop(publicShare)) { - binding.permissionName.setText(permissionName); - binding.permissionName.setVisibility(View.VISIBLE); - viewThemeUtils.androidx.colorPrimaryTextViewElement(binding.permissionName); - } else { + if (TextUtils.isEmpty(permissionName) || (SharePermissionManager.INSTANCE.isSecureFileDrop(publicShare) && encrypted)) { binding.permissionName.setVisibility(View.GONE); + return; } + + binding.permissionName.setText(permissionName); + binding.permissionName.setVisibility(View.VISIBLE); + viewThemeUtils.androidx.colorPrimaryTextViewElement(binding.permissionName); } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt index 64ffa0350002..06a444fb01c5 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt @@ -14,12 +14,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.button.MaterialButton +import com.owncloud.android.R import com.owncloud.android.databinding.ItemQuickSharePermissionsBinding -import com.owncloud.android.datamodel.QuickPermissionModel +import com.owncloud.android.datamodel.quickPermission.QuickPermission import com.owncloud.android.utils.theme.ViewThemeUtils class QuickSharingPermissionsAdapter( - private val quickPermissionList: MutableList, + private val quickPermissionList: MutableList, private val onPermissionChangeListener: QuickSharingPermissionViewHolder.OnPermissionChangeListener, private val viewThemeUtils: ViewThemeUtils ) : @@ -40,27 +42,35 @@ class QuickSharingPermissionsAdapter( } class QuickSharingPermissionViewHolder( - val binding: ItemQuickSharePermissionsBinding, + private val binding: ItemQuickSharePermissionsBinding, itemView: View, - val onPermissionChangeListener: OnPermissionChangeListener, + private val onPermissionChangeListener: OnPermissionChangeListener, private val viewThemeUtils: ViewThemeUtils - ) : - RecyclerView - .ViewHolder(itemView) { + ) : RecyclerView.ViewHolder(itemView) { - fun bindData(quickPermissionModel: QuickPermissionModel) { - binding.tvQuickShareName.text = quickPermissionModel.permissionName - if (quickPermissionModel.isSelected) { - viewThemeUtils.platform.colorImageView(binding.tvQuickShareCheckIcon) - binding.tvQuickShareCheckIcon.visibility = View.VISIBLE - } else { - binding.tvQuickShareCheckIcon.visibility = View.INVISIBLE + fun bindData(quickPermission: QuickPermission) { + val context = itemView.context + val permissionName = quickPermission.type.getText(context) + + binding.run { + quickPermissionButton.text = permissionName + quickPermissionButton.iconGravity = MaterialButton.ICON_GRAVITY_START + quickPermissionButton.icon = quickPermission.type.getIcon(context) + + if (quickPermission.isSelected) { + viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(quickPermissionButton) + } } + val customPermissionName = context.getString(R.string.share_custom_permission) + val isCustomPermission = permissionName.equals(customPermissionName, ignoreCase = true) + itemView.setOnClickListener { - // if user select different options then only update the permission - if (!quickPermissionModel.isSelected) { - onPermissionChangeListener.onPermissionChanged(adapterPosition) + if (isCustomPermission) { + onPermissionChangeListener.onCustomPermissionSelected() + } else if (!quickPermission.isSelected) { + // if user select different options then only update the permission + onPermissionChangeListener.onPermissionChanged(absoluteAdapterPosition) } else { // dismiss sheet on selection of same permission onPermissionChangeListener.onDismissSheet() @@ -70,6 +80,7 @@ class QuickSharingPermissionsAdapter( interface OnPermissionChangeListener { fun onPermissionChanged(position: Int) + fun onCustomPermissionSelected() fun onDismissSheet() } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java b/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java index 4c9b95471f29..974ea6d5655c 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java @@ -19,15 +19,16 @@ import android.widget.ImageView; import com.nextcloud.client.account.User; +import com.nextcloud.utils.extensions.ImageViewExtensionsKt; import com.owncloud.android.R; import com.owncloud.android.databinding.FileDetailsShareShareItemBinding; +import com.owncloud.android.datamodel.quickPermission.QuickPermissionType; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.ui.TextDrawable; -import com.owncloud.android.ui.fragment.util.SharingMenuHelper; +import com.owncloud.android.ui.fragment.util.SharePermissionManager; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.theme.ViewThemeUtils; -import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; @@ -37,6 +38,7 @@ class ShareViewHolder extends RecyclerView.ViewHolder { private User user; private Context context; private ViewThemeUtils viewThemeUtils; + private boolean encrypted; public ShareViewHolder(@NonNull View itemView) { super(itemView); @@ -45,12 +47,14 @@ public ShareViewHolder(@NonNull View itemView) { public ShareViewHolder(FileDetailsShareShareItemBinding binding, User user, Context context, - final ViewThemeUtils viewThemeUtils) { + final ViewThemeUtils viewThemeUtils, + boolean encrypted) { this(binding.getRoot()); this.binding = binding; this.user = user; this.context = context; this.viewThemeUtils = viewThemeUtils; + this.encrypted = encrypted; } public void bind(OCShare share, @@ -67,47 +71,53 @@ public void bind(OCShare share, binding.icon.setTag(null); - switch (share.getShareType()) { - case GROUP: - name = context.getString(R.string.share_group_clarification, name); - viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); - break; - case ROOM: - name = context.getString(R.string.share_room_clarification, name); - viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); - break; - case CIRCLE: - viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); - break; - case FEDERATED: - name = context.getString(R.string.share_remote_clarification, name); - setImage(binding.icon, share.getSharedWithDisplayName(), R.drawable.ic_user); - break; - case USER: - binding.icon.setTag(share.getShareWith()); - float avatarRadius = context.getResources().getDimension(R.dimen.list_item_avatar_icon_radius); - DisplayUtils.setAvatar(user, - share.getShareWith(), - share.getSharedWithDisplayName(), - avatarListener, - avatarRadius, - context.getResources(), - binding.icon, - context); - - binding.icon.setOnClickListener(v -> listener.showProfileBottomSheet(user, share.getShareWith())); - default: - setImage(binding.icon, name, R.drawable.ic_user); - break; + if (share.getShareType() != null) { + switch (share.getShareType()) { + case GROUP: + name = context.getString(R.string.share_group_clarification, name); + viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); + break; + case ROOM: + name = context.getString(R.string.share_room_clarification, name); + viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); + break; + case CIRCLE: + viewThemeUtils.files.createAvatar(share.getShareType(), binding.icon, context); + break; + case FEDERATED: + name = context.getString(R.string.share_remote_clarification, name); + setImage(binding.icon, share.getSharedWithDisplayName()); + break; + case USER: + binding.icon.setTag(share.getShareWith()); + float avatarRadius = context.getResources().getDimension(R.dimen.list_item_avatar_icon_radius); + + if (share.getShareWith() != null) { + DisplayUtils.setAvatar(user, + share.getShareWith(), + share.getSharedWithDisplayName(), + avatarListener, + avatarRadius, + context.getResources(), + binding.icon, + context); + } + + binding.icon.setOnClickListener(v -> listener.showProfileBottomSheet(user, share.getShareWith())); + default: + setImage(binding.icon, name); + break; + } } binding.name.setText(name); - if (share.getShareWith().equalsIgnoreCase(userId) || share.getUserId().equalsIgnoreCase(userId)) { + if (share.getShareWith() != null && share.getShareWith().equalsIgnoreCase(userId) || + share.getUserId() != null && share.getUserId().equalsIgnoreCase(userId)) { binding.overflowMenu.setVisibility(View.VISIBLE); - String permissionName = SharingMenuHelper.getPermissionName(context, share); - setPermissionName(permissionName); + QuickPermissionType quickPermissionType = SharePermissionManager.INSTANCE.getSelectedType(share, encrypted); + setPermissionName(quickPermissionType.getText(context)); // bind listener to edit privileges binding.overflowMenu.setOnClickListener(v -> listener.showSharingMenuActionSheet(share)); @@ -126,11 +136,21 @@ private void setPermissionName(String permissionName) { } } - private void setImage(ImageView avatar, String name, @DrawableRes int fallback) { + private void setImage(ImageView avatar, String name) { + if (TextUtils.isEmpty(name)) { + setUserImage(avatar); + return; + } + try { avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension)); } catch (StringIndexOutOfBoundsException e) { - avatar.setImageResource(fallback); + setUserImage(avatar); } } + + private void setUserImage(ImageView avatar) { + ImageViewExtensionsKt.makeRoundedWithIcon(avatar, context, R.drawable.ic_user); + viewThemeUtils.platform.colorImageViewBackgroundAndIcon(avatar); + } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java index 2b22b72050b9..16be239081ff 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java @@ -79,57 +79,60 @@ public ShareeListAdapter(FileActivity fileActivity, @Override public int getItemViewType(int position) { - return shares.get(position).getShareType().getValue(); + if (shares == null) { + return 0; + } + + if (position < 0 || position >= shares.size()) { + return 0; + } + + final var share = shares.get(position); + if (share == null) { + return 0; + } + + final var shareType = share.getShareType(); + if (shareType == null) { + return 0; + } + + return shareType.getValue(); } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { boolean shareViaLink = MDMConfig.INSTANCE.shareViaLink(fileActivity); + final var parentViewGroup = LayoutInflater.from(fileActivity); - if (shareViaLink) { - switch (ShareType.fromValue(viewType)) { - case PUBLIC_LINK, EMAIL -> { - return new LinkShareViewHolder( - FileDetailsShareLinkShareItemBinding.inflate(LayoutInflater.from(fileActivity), - parent, - false), - fileActivity, - viewThemeUtils); - } - case NEW_PUBLIC_LINK -> { - if (encrypted) { - return new NewSecureFileDropViewHolder( - FileDetailsShareSecureFileDropAddNewItemBinding.inflate(LayoutInflater.from(fileActivity), - parent, - false) - ); - } else { - return new NewLinkShareViewHolder( - FileDetailsSharePublicLinkAddNewItemBinding.inflate(LayoutInflater.from(fileActivity), - parent, - false) - ); - } - } - case INTERNAL -> { - return new InternalShareViewHolder( - FileDetailsShareInternalShareLinkBinding.inflate(LayoutInflater.from(fileActivity), parent, false), - fileActivity); - } - default -> { - return new ShareViewHolder(FileDetailsShareShareItemBinding.inflate(LayoutInflater.from(fileActivity), - parent, - false), - user, - fileActivity, - viewThemeUtils); + if (!shareViaLink) { + final var binding = FileDetailsShareInternalShareLinkBinding.inflate(parentViewGroup, parent, false); + return new InternalShareViewHolder(binding, fileActivity); + } + + switch (ShareType.fromValue(viewType)) { + case PUBLIC_LINK, EMAIL -> { + final var binding = FileDetailsShareLinkShareItemBinding.inflate(parentViewGroup, parent, false); + return new LinkShareViewHolder(binding, fileActivity, viewThemeUtils, encrypted); + } + case NEW_PUBLIC_LINK -> { + if (encrypted) { + final var binding = FileDetailsShareSecureFileDropAddNewItemBinding.inflate(parentViewGroup, parent, false); + return new NewSecureFileDropViewHolder(binding); + } else { + final var binding = FileDetailsSharePublicLinkAddNewItemBinding.inflate(parentViewGroup, parent, false); + return new NewLinkShareViewHolder(binding); } } - } else { - return new InternalShareViewHolder( - FileDetailsShareInternalShareLinkBinding.inflate(LayoutInflater.from(fileActivity), parent, false), - fileActivity); + case INTERNAL -> { + final var binding = FileDetailsShareInternalShareLinkBinding.inflate(parentViewGroup, parent, false); + return new InternalShareViewHolder(binding, fileActivity); + } + default -> { + final var binding = FileDetailsShareShareItemBinding.inflate(parentViewGroup, parent, false); + return new ShareViewHolder(binding, user, fileActivity, viewThemeUtils, encrypted); + } } } @@ -152,7 +155,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi } if (holder instanceof LinkShareViewHolder publicShareViewHolder) { - publicShareViewHolder.bind(share, listener); + publicShareViewHolder.bind(share, listener, position); } else if (holder instanceof InternalShareViewHolder internalShareViewHolder) { internalShareViewHolder.bind(share, listener); } else if (holder instanceof NewLinkShareViewHolder newLinkShareViewHolder) { @@ -186,7 +189,7 @@ public int getItemCount() { @SuppressLint("NotifyDataSetChanged") public void toggleShowAll() { - this.showAll = !this.showAll; + showAll = !showAll; notifyDataSetChanged(); } @@ -201,6 +204,12 @@ public void addShares(List sharesToAdd) { notifyDataSetChanged(); } + @SuppressLint("NotifyDataSetChanged") + public void removeAll() { + shares.clear(); + notifyDataSetChanged(); + } + @Override public void avatarGenerated(Drawable avatarDrawable, Object callContext) { if (callContext instanceof ImageView iv) { @@ -217,10 +226,12 @@ public boolean shouldCallGeneratedCallback(String tag, Object callContext) { return false; } - @SuppressLint("NotifyDataSetChanged") public void remove(OCShare share) { - shares.remove(share); - notifyDataSetChanged(); + int position = shares.indexOf(share); + if (position != -1) { + shares.remove(position); + notifyItemRemoved(position); + } } /** @@ -255,13 +266,4 @@ protected final void sortShares() { public List getShares() { return shares; } - - public void removeNewPublicShare() { - for (OCShare share : shares) { - if (share.getShareType() == ShareType.NEW_PUBLIC_LINK) { - shares.remove(share); - break; - } - } - } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index b5f567d34424..3ad6b09a1386 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -72,6 +72,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import javax.inject.Inject; @@ -509,11 +510,11 @@ public void onClick(View v) { setFileModificationTimestamp(getFile(), showDetailedTimestamp); } else if (id == R.id.folder_sync_button) { if (binding.folderSyncButton.isChecked()) { - getFile().setInternalFolderSyncTimestamp(0L); + getFile().setInternalFolderSyncTimestamp(0L); } else { getFile().setInternalFolderSyncTimestamp(-1L); } - + storageManager.saveFile(getFile()); } else { Log_OC.e(TAG, "Incorrect view clicked!"); @@ -598,11 +599,11 @@ public void updateFileDetails(boolean transferring, boolean refresh) { if (fabMain != null) { fabMain.hide(); } - + binding.syncBlock.setVisibility(file.isFolder() ? View.VISIBLE : View.GONE); - + if (file.isInternalFolderSync()) { - binding.folderSyncButton.setChecked(file.isInternalFolderSync()); + binding.folderSyncButton.setChecked(file.isInternalFolderSync()); } else { if (storageManager.isPartOfInternalTwoWaySync(file)) { binding.folderSyncButton.setChecked(true); @@ -814,18 +815,27 @@ private void showEmptyContent() { /** * open the sharing process fragment for creating new share * - * @param shareeName - * @param shareType */ public void initiateSharingProcess(String shareeName, ShareType shareType, boolean secureShare) { - requireActivity().getSupportFragmentManager().beginTransaction().add(R.id.sharing_frame_container, - FileDetailsSharingProcessFragment.newInstance(getFile(), - shareeName, - shareType, - secureShare), - FileDetailsSharingProcessFragment.TAG) + if (getFile() == null) { + DisplayUtils.showSnackMessage(requireView(), R.string.file_not_found_cannot_share); + return; + } + + final var file = getFile(); + if (Objects.equals(file.getOwnerId(), shareeName)) { + DisplayUtils.showSnackMessage(requireView(), R.string.file_detail_share_already_active); + return; + } + + final var fileShareDetailFragment = FileDetailsSharingProcessFragment.newInstance(file, shareeName, shareType, secureShare); + + requireActivity() + .getSupportFragmentManager() + .beginTransaction() + .add(R.id.sharing_frame_container, fileShareDetailFragment, FileDetailsSharingProcessFragment.TAG) .commit(); showHideFragmentView(true); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 1d6b78a4361a..bc4bc399b938 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -37,6 +37,7 @@ import com.nextcloud.client.network.ClientFactory; import com.nextcloud.utils.extensions.BundleExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; +import com.nextcloud.utils.extensions.OCShareExtensionsKt; import com.nextcloud.utils.extensions.ViewExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.R; @@ -178,6 +179,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, file.isEncrypted(), SharesType.INTERNAL); + internalShareeListAdapter.setHasStableIds(true); + binding.sharesListInternal.setAdapter(internalShareeListAdapter); binding.sharesListInternal.setLayoutManager(new LinearLayoutManager(requireContext())); @@ -190,6 +193,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, viewThemeUtils, file.isEncrypted(), SharesType.EXTERNAL); + + externalShareeListAdapter.setHasStableIds(true); binding.sharesListExternal.setAdapter(externalShareeListAdapter); @@ -214,10 +219,11 @@ public void onAttach(@NonNull Context context) { if (!(getActivity() instanceof FileActivity)) { throw new IllegalArgumentException("Calling activity must be of type FileActivity"); } + try { onEditShareListener = (OnEditShareListener) context; - } catch (Exception ignored) { - throw new IllegalArgumentException("Calling activity must implement the interface", ignored); + } catch (Exception e) { + throw new IllegalArgumentException("Calling activity must implement the interface" + e); } } @@ -247,23 +253,15 @@ private void setupView() { viewThemeUtils.material.colorMaterialTextButton(binding.sharesListInternalShowAll); binding.sharesListInternalShowAll.setOnClickListener(view -> { internalShareeListAdapter.toggleShowAll(); - - if (internalShareeListAdapter.isShowAll()) { - binding.sharesListInternalShowAll.setText(R.string.show_less); - } else { - binding.sharesListInternalShowAll.setText(R.string.show_all); - } + int textRes = internalShareeListAdapter.isShowAll() ? R.string.show_less : R.string.show_all; + binding.sharesListInternalShowAll.setText(textRes); }); - + viewThemeUtils.material.colorMaterialTextButton(binding.sharesListExternalShowAll); binding.sharesListExternalShowAll.setOnClickListener(view -> { externalShareeListAdapter.toggleShowAll(); - - if (internalShareeListAdapter.isShowAll()) { - binding.sharesListExternalShowAll.setText(R.string.show_less); - } else { - binding.sharesListExternalShowAll.setText(R.string.show_all); - } + int textRes = externalShareeListAdapter.isShowAll() ? R.string.show_less : R.string.show_all; + binding.sharesListExternalShowAll.setText(textRes); }); if (file.canReshare() && !FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities)) { @@ -409,7 +407,7 @@ public void copyLink(OCShare share) { @VisibleForTesting public void showSharingMenuActionSheet(OCShare share) { if (fileActivity != null && !fileActivity.isFinishing()) { - new FileDetailSharingMenuBottomSheetDialog(fileActivity, this, share, viewThemeUtils).show(); + new FileDetailSharingMenuBottomSheetDialog(fileActivity, this, share, viewThemeUtils, file.isEncrypted()).show(); } } @@ -420,7 +418,7 @@ public void showSharingMenuActionSheet(OCShare share) { */ @Override public void showPermissionsDialog(OCShare share) { - new QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils).show(); + new QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils, file.isEncrypted()).show(); } /** @@ -460,8 +458,8 @@ private void refreshUiFromDB() { setupView(); } - private void unshareWith(OCShare share) { - fileOperationsHelper.unshareShare(file, share); + private void unShareWith(OCShare share) { + fileOperationsHelper.unShareShare(file, share); } /** @@ -517,7 +515,8 @@ public void refreshSharesFromDB() { DisplayUtils.showSnackMessage(getView(), getString(R.string.could_not_retrieve_shares)); return; } - internalShareeListAdapter.getShares().clear(); + + internalShareeListAdapter.removeAll(); // to show share with users/groups info List shares = fileDataStorageManager.getSharesWithForAFile(file.getRemotePath(), @@ -544,25 +543,17 @@ public void refreshSharesFromDB() { } internalShareeListAdapter.addShares(internalShares); + ViewExtensionsKt.setVisibleIf(binding.sharesListInternalShowAll, internalShareeListAdapter.getShares().size() > 3); - ViewExtensionsKt.setVisibleIf(binding.sharesListInternalShowAll, - internalShareeListAdapter.getShares().size() > 3 - ); - - externalShareeListAdapter.getShares().clear(); - - // Get public share - List publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), - ShareType.PUBLIC_LINK, - ""); - - externalShareeListAdapter.addShares(externalShares); - - externalShareeListAdapter.addShares(publicShares); + addExternalAndPublicShares(externalShares); + ViewExtensionsKt.setVisibleIf(binding.sharesListExternalShowAll, externalShareeListAdapter.getShares().size() > 3); + } - ViewExtensionsKt.setVisibleIf(binding.sharesListExternalShowAll, - externalShareeListAdapter.getShares().size() > 3 - ); + private void addExternalAndPublicShares(List externalShares) { + final var publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), ShareType.PUBLIC_LINK, ""); + externalShareeListAdapter.removeAll(); + final var shares = OCShareExtensionsKt.mergeDistinctByToken(externalShares, publicShares); + externalShareeListAdapter.addShares(shares); } private void checkContactPermission() { @@ -650,7 +641,6 @@ public void advancedPermissions(OCShare share) { modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); } - @Override public void sendNewEmail(OCShare share) { modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); @@ -658,13 +648,15 @@ public void sendNewEmail(OCShare share) { @Override public void unShare(OCShare share) { - unshareWith(share); - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesListInternal.getAdapter(); - if (adapter == null) { + unShareWith(share); + + if (binding.sharesListInternal.getAdapter() instanceof ShareeListAdapter adapter) { + adapter.remove(share); + } else if (binding.sharesListExternal.getAdapter() instanceof ShareeListAdapter adapter) { + adapter.remove(share); + } else { DisplayUtils.showSnackMessage(getView(), getString(R.string.failed_update_ui)); - return; } - adapter.remove(share); } @Override @@ -691,6 +683,11 @@ public void onQuickPermissionChanged(OCShare share, int permission) { fileOperationsHelper.setPermissionsToShare(share, permission); } + @Override + public void openShareDetailWithCustomPermissions(OCShare share) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION); + } + //launcher for contact permission private final ActivityResultLauncher requestContactPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java index be6779b9573a..86b21f332872 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java @@ -16,12 +16,13 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.databinding.FileDetailsSharingMenuBottomSheetFragmentBinding; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.lib.resources.shares.ShareType; import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.fragment.util.SharingMenuHelper; +import com.owncloud.android.ui.fragment.util.SharePermissionManager; import com.owncloud.android.utils.theme.ViewThemeUtils; /** @@ -32,14 +33,18 @@ public class FileDetailSharingMenuBottomSheetDialog extends BottomSheetDialog { private final FileDetailsSharingMenuBottomSheetActions actions; private final OCShare ocShare; private final ViewThemeUtils viewThemeUtils; + private final boolean encrypted; + public FileDetailSharingMenuBottomSheetDialog(FileActivity fileActivity, FileDetailsSharingMenuBottomSheetActions actions, OCShare ocShare, - ViewThemeUtils viewThemeUtils) { + ViewThemeUtils viewThemeUtils, + boolean encrypted) { super(fileActivity); this.actions = actions; this.ocShare = ocShare; this.viewThemeUtils = viewThemeUtils; + this.encrypted = encrypted; } @Override @@ -54,10 +59,10 @@ protected void onCreate(Bundle savedInstanceState) { viewThemeUtils.platform.themeDialog(binding.getRoot()); - viewThemeUtils.platform.colorImageView(binding.menuIconAdvancedPermissions); - viewThemeUtils.platform.colorImageView(binding.menuIconSendLink); - viewThemeUtils.platform.colorImageView(binding.menuIconUnshare); - viewThemeUtils.platform.colorImageView(binding.menuIconSendNewEmail); + viewThemeUtils.platform.colorImageView(binding.menuIconAdvancedPermissions, ColorRole.PRIMARY); + viewThemeUtils.platform.colorImageView(binding.menuIconSendLink, ColorRole.PRIMARY); + viewThemeUtils.platform.colorImageView(binding.menuIconUnshare, ColorRole.PRIMARY); + viewThemeUtils.platform.colorImageView(binding.menuIconSendNewEmail, ColorRole.PRIMARY); updateUI(); @@ -78,7 +83,7 @@ private void updateUI() { binding.menuShareSendLink.setVisibility(View.GONE); } - if (SharingMenuHelper.isSecureFileDrop(ocShare)) { + if (SharePermissionManager.INSTANCE.isSecureFileDrop(ocShare) && encrypted) { binding.menuShareAdvancedPermissions.setVisibility(View.GONE); } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt index 144d3f5bf72c..7479c07467c0 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt @@ -1,11 +1,8 @@ /* - * Nextcloud Android client application + * Nextcloud - Android Client * - * @author TSI-mc - * Copyright (C) 2021 TSI-mc - * Copyright (C) 2021 Nextcloud GmbH - * - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later */ package com.owncloud.android.ui.fragment @@ -21,17 +18,20 @@ import androidx.fragment.app.Fragment import com.nextcloud.client.di.Injectable import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSerializableArgument +import com.nextcloud.utils.extensions.isPublicOrMail +import com.nextcloud.utils.extensions.setVisibilityWithAnimation import com.nextcloud.utils.extensions.setVisibleIf import com.owncloud.android.R import com.owncloud.android.databinding.FileDetailsSharingProcessFragmentBinding import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.quickPermission.QuickPermissionType +import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.shares.OCShare -import com.owncloud.android.lib.resources.shares.SharePermissionsBuilder import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment -import com.owncloud.android.ui.fragment.util.SharingMenuHelper +import com.owncloud.android.ui.fragment.util.SharePermissionManager import com.owncloud.android.ui.helpers.FileOperationsHelper import com.owncloud.android.utils.ClipboardUtil import com.owncloud.android.utils.DisplayUtils @@ -69,6 +69,7 @@ class FileDetailsSharingProcessFragment : // types of screens to be displayed const val SCREEN_TYPE_PERMISSION = 1 // permissions screen const val SCREEN_TYPE_NOTE = 2 // note screen + const val SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION = 3 // permissions screen with custom permission /** * fragment instance to be called while creating new share for internal and external share @@ -80,14 +81,16 @@ class FileDetailsSharingProcessFragment : shareType: ShareType, secureShare: Boolean ): FileDetailsSharingProcessFragment { - val args = Bundle() - args.putParcelable(ARG_OCFILE, file) - args.putSerializable(ARG_SHARE_TYPE, shareType) - args.putString(ARG_SHAREE_NAME, shareeName) - args.putBoolean(ARG_SECURE_SHARE, secureShare) - val fragment = FileDetailsSharingProcessFragment() - fragment.arguments = args - return fragment + val bundle = Bundle().apply { + putParcelable(ARG_OCFILE, file) + putSerializable(ARG_SHARE_TYPE, shareType) + putString(ARG_SHAREE_NAME, shareeName) + putBoolean(ARG_SECURE_SHARE, secureShare) + } + + return FileDetailsSharingProcessFragment().apply { + arguments = bundle + } } /** @@ -100,14 +103,16 @@ class FileDetailsSharingProcessFragment : isReshareShown: Boolean, isExpirationDateShown: Boolean ): FileDetailsSharingProcessFragment { - val args = Bundle() - args.putParcelable(ARG_OCSHARE, share) - args.putInt(ARG_SCREEN_TYPE, screenType) - args.putBoolean(ARG_RESHARE_SHOWN, isReshareShown) - args.putBoolean(ARG_EXP_DATE_SHOWN, isExpirationDateShown) - val fragment = FileDetailsSharingProcessFragment() - fragment.arguments = args - return fragment + val bundle = Bundle().apply { + putParcelable(ARG_OCSHARE, share) + putInt(ARG_SCREEN_TYPE, screenType) + putBoolean(ARG_RESHARE_SHOWN, isReshareShown) + putBoolean(ARG_EXP_DATE_SHOWN, isExpirationDateShown) + } + + return FileDetailsSharingProcessFragment().apply { + arguments = bundle + } } } @@ -135,12 +140,13 @@ class FileDetailsSharingProcessFragment : private lateinit var capabilities: OCCapability private var expirationDatePickerFragment: ExpirationDatePickerDialogFragment? = null + private var downloadAttribute: String? = null override fun onAttach(context: Context) { super.onAttach(context) try { onEditShareListener = context as FileDetailSharingFragment.OnEditShareListener - } catch (e: ClassCastException) { + } catch (_: ClassCastException) { throw IllegalStateException("Calling activity must implement the interface") } } @@ -148,6 +154,18 @@ class FileDetailsSharingProcessFragment : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + initArguments() + fileActivity = activity as FileActivity? + capabilities = CapabilityUtils.getCapability(context) + + requireNotNull(fileActivity) { "FileActivity may not be null" } + + permission = share?.permissions + ?: capabilities.defaultPermissions + ?: SharePermissionManager.getMaximumPermission(isFolder()) + } + + private fun initArguments() { arguments?.let { file = it.getParcelableArgument(ARG_OCFILE, OCFile::class.java) shareeName = it.getString(ARG_SHAREE_NAME) @@ -164,13 +182,6 @@ class FileDetailsSharingProcessFragment : isExpDateShown = it.getBoolean(ARG_EXP_DATE_SHOWN, true) isSecureShare = it.getBoolean(ARG_SECURE_SHARE, false) } - - fileActivity = activity as FileActivity? - capabilities = CapabilityUtils.getCapability(context) - - requireNotNull(fileActivity) { "FileActivity may not be null" } - - permission = capabilities.defaultPermissions ?: OCShare.NO_PERMISSION } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -181,39 +192,84 @@ class FileDetailsSharingProcessFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if (shareProcessStep == SCREEN_TYPE_PERMISSION) { - showShareProcessFirst() + if (isShareProcessStepIsPermission()) { + setupUI() } else { - showShareProcessSecond() + updateViewForNoteScreenType() } - implementClickEvents() + implementClickEvents() + setCheckboxStates() themeView() + setVisibilitiesOfShareOption() + toggleNextButtonAvailability(isAnyShareOptionChecked()) + logShareInfo() + } + + private fun logShareInfo() { + share?.run { + Log_OC.i(TAG, "-----BEFORE UPDATE SHARE-----") + Log_OC.i(TAG, "ID: $id") + Log_OC.i(TAG, "Permission: $permissions") + Log_OC.i(TAG, "Hide File Download: $isHideFileDownload") + Log_OC.i(TAG, "Label: $label") + Log_OC.i(TAG, "Attributes: $attributes") + } + } + + private fun setVisibilitiesOfShareOption() { + binding.run { + shareAllowDownloadAndSyncCheckbox.setVisibleIf(!isPublicShare()) + fileRequestRadioButton.setVisibleIf(canSetFileRequest()) + } } private fun themeView() { - viewThemeUtils.platform.colorTextView(binding.shareProcessEditShareLink) - viewThemeUtils.platform.colorTextView(binding.shareProcessAdvancePermissionTitle) + viewThemeUtils.platform.run { + binding.run { + colorTextView(shareProcessEditShareLink) + colorTextView(shareCustomPermissionsText) - viewThemeUtils.platform.themeRadioButton(binding.shareProcessPermissionReadOnly) - viewThemeUtils.platform.themeRadioButton(binding.shareProcessPermissionUploadEditing) - viewThemeUtils.platform.themeRadioButton(binding.shareProcessPermissionFileDrop) + themeRadioButton(viewOnlyRadioButton) + themeRadioButton(canEditRadioButton) + themeRadioButton(customPermissionRadioButton) - viewThemeUtils.platform.themeCheckbox(binding.shareProcessAllowResharingCheckbox) + if (!isPublicShare()) { + themeCheckbox(shareAllowDownloadAndSyncCheckbox) + } + + if (canSetFileRequest()) { + themeRadioButton(fileRequestRadioButton) + } - viewThemeUtils.androidx.colorSwitchCompat(binding.shareProcessSetPasswordSwitch) - viewThemeUtils.androidx.colorSwitchCompat(binding.shareProcessSetExpDateSwitch) - viewThemeUtils.androidx.colorSwitchCompat(binding.shareProcessSetDownloadLimitSwitch) - viewThemeUtils.androidx.colorSwitchCompat(binding.shareProcessHideDownloadCheckbox) - viewThemeUtils.androidx.colorSwitchCompat(binding.shareProcessChangeNameSwitch) + themeCheckbox(shareReadCheckbox) + themeCheckbox(shareCreateCheckbox) + themeCheckbox(shareEditCheckbox) + themeCheckbox(shareCheckbox) + themeCheckbox(shareDeleteCheckbox) + } + } - viewThemeUtils.material.colorTextInputLayout(binding.shareProcessEnterPasswordContainer) - viewThemeUtils.material.colorTextInputLayout(binding.shareProcessSetDownloadLimitInputContainer) - viewThemeUtils.material.colorTextInputLayout(binding.shareProcessChangeNameContainer) - viewThemeUtils.material.colorTextInputLayout(binding.noteContainer) + viewThemeUtils.androidx.run { + binding.run { + colorSwitchCompat(shareProcessSetPasswordSwitch) + colorSwitchCompat(shareProcessSetExpDateSwitch) + colorSwitchCompat(shareProcessSetDownloadLimitSwitch) + colorSwitchCompat(shareProcessHideDownloadCheckbox) + colorSwitchCompat(shareProcessChangeNameSwitch) + } + } - viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.shareProcessBtnNext) - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.shareProcessBtnCancel) + viewThemeUtils.material.run { + binding.run { + colorTextInputLayout(shareProcessEnterPasswordContainer) + colorTextInputLayout(shareProcessSetDownloadLimitInputContainer) + colorTextInputLayout(shareProcessChangeNameContainer) + colorTextInputLayout(noteContainer) + colorMaterialButtonPrimaryFilled(shareProcessBtnNext) + colorMaterialButtonPrimaryOutlined(shareProcessBtnCancel) + } + } } override fun onConfigurationChanged(newConfig: Configuration) { @@ -229,40 +285,63 @@ class FileDetailsSharingProcessFragment : } } - private fun showShareProcessFirst() { - binding.shareProcessGroupOne.visibility = View.VISIBLE - binding.shareProcessEditShareLink.visibility = View.VISIBLE - binding.shareProcessGroupTwo.visibility = View.GONE + private fun setupUI() { + binding.run { + shareProcessGroupOne.visibility = View.VISIBLE + shareProcessEditShareLink.visibility = View.VISIBLE + shareProcessGroupTwo.visibility = View.GONE + } + + updateView() + + // show or hide expiry date + binding.shareProcessSetExpDateSwitch.setVisibleIf(isExpDateShown && !isSecureShare) + shareProcessStep = SCREEN_TYPE_PERMISSION + } + private fun updateView() { if (share != null) { - setupModificationUI() + updateViewForUpdate() } else { - setupUpdateUI() + updateViewForCreate() } + } - if (isSecureShare) { - binding.shareProcessAdvancePermissionTitle.visibility = View.GONE + private fun setMaxPermissionsIfDefaultPermissionExists() { + if (capabilities.defaultPermissions != null) { + binding.canEditRadioButton.isChecked = true + permission = SharePermissionManager.getMaximumPermission(isFolder()) } + } - // show or hide expiry date - if (isExpDateShown && !isSecureShare) { - binding.shareProcessSetExpDateSwitch.visibility = View.VISIBLE - } else { - binding.shareProcessSetExpDateSwitch.visibility = View.GONE + // region ViewUpdates + private fun updateViewForCreate() { + binding.shareProcessBtnNext.text = getString(R.string.common_next) + updateViewAccordingToFile() + showPasswordInput(binding.shareProcessSetPasswordSwitch.isChecked) + showExpirationDateInput(binding.shareProcessSetExpDateSwitch.isChecked) + showFileDownloadLimitInput(binding.shareProcessSetDownloadLimitSwitch.isChecked) + setMaxPermissionsIfDefaultPermissionExists() + } + + private fun updateViewAccordingToFile() { + file?.run { + if (isFolder == true) { + updateViewForFolder() + } else { + updateViewForFile() + } + updateViewForShareType() } - shareProcessStep = SCREEN_TYPE_PERMISSION } - private fun setupModificationUI() { + private fun updateViewForUpdate() { if (share?.isFolder == true) updateViewForFolder() else updateViewForFile() - // read only / allow upload and editing / file drop - if (SharingMenuHelper.isUploadAndEditingAllowed(share)) { - binding.shareProcessPermissionUploadEditing.isChecked = true - } else if (SharingMenuHelper.isFileDrop(share) && share?.isFolder == true) { - binding.shareProcessPermissionFileDrop.isChecked = true - } else if (SharingMenuHelper.isReadOnly(share)) { - binding.shareProcessPermissionReadOnly.isChecked = true + selectRadioButtonAccordingToPermission() + + if (isShareProcessStepIsCustomPermission()) { + selectCustomPermissionLayout() } shareType = share?.shareType ?: ShareType.NO_SHARED @@ -270,7 +349,7 @@ class FileDetailsSharingProcessFragment : // show different text for link share and other shares // because we have link to share in Public Link binding.shareProcessBtnNext.text = getString( - if (shareType == ShareType.PUBLIC_LINK) { + if (isPublicShare()) { R.string.share_copy_link } else { R.string.common_confirm @@ -286,19 +365,34 @@ class FileDetailsSharingProcessFragment : showFileDownloadLimitInput(binding.shareProcessSetDownloadLimitSwitch.isChecked) } - private fun setupUpdateUI() { - binding.shareProcessBtnNext.text = getString(R.string.common_next) - file.let { - if (file?.isFolder == true) { - updateViewForFolder() - } else { - updateViewForFile() + private fun selectRadioButtonAccordingToPermission() { + val selectedType = SharePermissionManager.getSelectedType(share, encrypted = file?.isEncrypted == true) + binding.run { + when (selectedType) { + QuickPermissionType.VIEW_ONLY -> { + viewOnlyRadioButton.isChecked = true + } + + QuickPermissionType.CAN_EDIT -> { + canEditRadioButton.isChecked = true + } + + QuickPermissionType.FILE_REQUEST -> { + fileRequestRadioButton.isChecked = true + } + + QuickPermissionType.CUSTOM_PERMISSIONS -> { + selectCustomPermissionLayout() + } + + else -> Unit } - updateViewForShareType() } - showPasswordInput(binding.shareProcessSetPasswordSwitch.isChecked) - showExpirationDateInput(binding.shareProcessSetExpDateSwitch.isChecked) - showFileDownloadLimitInput(binding.shareProcessSetDownloadLimitSwitch.isChecked) + } + + private fun selectCustomPermissionLayout() { + binding.customPermissionRadioButton.isChecked = true + binding.customPermissionLayout.setVisibilityWithAnimation(true) } private fun updateViewForShareType() { @@ -318,61 +412,60 @@ class FileDetailsSharingProcessFragment : } private fun updateViewForExternalShare() { - binding.shareProcessChangeNameSwitch.visibility = View.GONE - binding.shareProcessChangeNameContainer.visibility = View.GONE - updateViewForExternalAndLinkShare() + binding.run { + shareProcessChangeNameSwitch.visibility = View.GONE + shareProcessChangeNameContainer.visibility = View.GONE + updateViewForExternalAndLinkShare() + } } private fun updateViewForLinkShare() { updateViewForExternalAndLinkShare() - binding.shareProcessChangeNameSwitch.visibility = View.VISIBLE - if (share != null) { - binding.shareProcessChangeName.setText(share?.label) - binding.shareProcessChangeNameSwitch.isChecked = !TextUtils.isEmpty(share?.label) + binding.run { + shareProcessChangeNameSwitch.visibility = View.VISIBLE + if (share != null) { + shareProcessChangeName.setText(share?.label) + shareProcessChangeNameSwitch.isChecked = !TextUtils.isEmpty(share?.label) + } + shareReadCheckbox.isEnabled = isFolder() + showChangeNameInput(shareProcessChangeNameSwitch.isChecked) } - showChangeNameInput(binding.shareProcessChangeNameSwitch.isChecked) } private fun updateViewForInternalShare() { - binding.shareProcessChangeNameSwitch.visibility = View.GONE - binding.shareProcessChangeNameContainer.visibility = View.GONE - binding.shareProcessHideDownloadCheckbox.visibility = View.GONE - if (isSecureShare) { - binding.shareProcessAllowResharingCheckbox.visibility = View.GONE - } else { - binding.shareProcessAllowResharingCheckbox.visibility = View.VISIBLE - } - binding.shareProcessSetPasswordSwitch.visibility = View.GONE - - if (share != null) { - if (!isReShareShown) { - binding.shareProcessAllowResharingCheckbox.visibility = View.GONE + binding.run { + shareProcessChangeNameSwitch.visibility = View.GONE + shareProcessChangeNameContainer.visibility = View.GONE + shareProcessHideDownloadCheckbox.visibility = View.GONE + shareCheckbox.setVisibleIf(!isSecureShare) + shareProcessSetPasswordSwitch.visibility = View.GONE + + if (share != null) { + if (!isReShareShown) { + shareCheckbox.visibility = View.GONE + } + shareCheckbox.isChecked = SharePermissionManager.canReshare(share) } - binding.shareProcessAllowResharingCheckbox.isChecked = SharingMenuHelper.canReshare(share) } } - /** - * update views where share type external or link share - */ private fun updateViewForExternalAndLinkShare() { - binding.shareProcessHideDownloadCheckbox.visibility = View.VISIBLE - binding.shareProcessAllowResharingCheckbox.visibility = View.GONE - binding.shareProcessSetPasswordSwitch.visibility = View.VISIBLE - - if (share != null) { - if (SharingMenuHelper.isFileDrop(share)) { - binding.shareProcessHideDownloadCheckbox.visibility = View.GONE - } else { - binding.shareProcessHideDownloadCheckbox.visibility = View.VISIBLE - binding.shareProcessHideDownloadCheckbox.isChecked = share?.isHideFileDownload == true + binding.run { + shareProcessHideDownloadCheckbox.visibility = View.VISIBLE + shareCheckbox.visibility = View.GONE + shareProcessSetPasswordSwitch.visibility = View.VISIBLE + + if (share != null) { + if (SharePermissionManager.isFileRequest(share)) { + shareProcessHideDownloadCheckbox.visibility = View.GONE + } else { + shareProcessHideDownloadCheckbox.visibility = View.VISIBLE + shareProcessHideDownloadCheckbox.isChecked = share?.isHideFileDownload == true + } } } } - /** - * update expiration date view while modifying the share - */ private fun updateExpirationDateView() { share?.let { share -> if (share.expirationDate > 0) { @@ -387,7 +480,7 @@ class FileDetailsSharingProcessFragment : } private fun updateFileDownloadLimitView() { - if (capabilities.filesDownloadLimit.isTrue && share?.isFolder == false) { + if (canSetDownloadLimit()) { binding.shareProcessSetDownloadLimitSwitch.visibility = View.VISIBLE val currentDownloadLimit = share?.fileDownloadLimit?.limit ?: capabilities.filesDownloadLimitDefault @@ -400,65 +493,177 @@ class FileDetailsSharingProcessFragment : } private fun updateViewForFile() { - binding.shareProcessPermissionUploadEditing.text = getString(R.string.link_share_editing) - binding.shareProcessPermissionFileDrop.visibility = View.GONE + binding.run { + canEditRadioButton.text = getString(R.string.link_share_editing) + } } private fun updateViewForFolder() { - binding.shareProcessPermissionUploadEditing.text = getString(R.string.link_share_allow_upload_and_editing) - binding.shareProcessPermissionFileDrop.visibility = View.VISIBLE - if (isSecureShare) { - binding.shareProcessPermissionFileDrop.visibility = View.GONE - binding.shareProcessAllowResharingCheckbox.visibility = View.GONE - binding.shareProcessSetExpDateSwitch.visibility = View.GONE + binding.run { + canEditRadioButton.text = getString(R.string.share_permission_can_edit) + + if (isSecureShare) { + shareCheckbox.visibility = View.GONE + shareProcessSetExpDateSwitch.visibility = View.GONE + } } } - /** - * update views for screen type Note - */ - private fun showShareProcessSecond() { - binding.shareProcessGroupOne.visibility = View.GONE - binding.shareProcessEditShareLink.visibility = View.GONE - binding.shareProcessGroupTwo.visibility = View.VISIBLE - if (share != null) { - binding.shareProcessBtnNext.text = getString(R.string.set_note) - binding.noteText.setText(share?.note) - } else { - binding.shareProcessBtnNext.text = getString(R.string.send_share) - binding.noteText.setText(R.string.empty) + private fun updateViewForNoteScreenType() { + binding.run { + shareProcessGroupOne.visibility = View.GONE + shareProcessEditShareLink.visibility = View.GONE + shareProcessGroupTwo.visibility = View.VISIBLE + if (share != null) { + shareProcessBtnNext.text = getString(R.string.set_note) + noteText.setText(share?.note) + } else { + shareProcessBtnNext.text = getString(R.string.send_share) + noteText.setText(R.string.empty) + } + shareProcessStep = SCREEN_TYPE_NOTE + shareProcessBtnNext.performClick() } - shareProcessStep = SCREEN_TYPE_NOTE } + // endregion + @Suppress("LongMethod") private fun implementClickEvents() { - binding.shareProcessBtnCancel.setOnClickListener { - onCancelClick() - } - binding.shareProcessBtnNext.setOnClickListener { - if (shareProcessStep == SCREEN_TYPE_PERMISSION) { - validateShareProcessFirst() - } else { - validateShareProcessSecond() + binding.run { + shareProcessBtnCancel.setOnClickListener { + onCancelClick() + } + shareProcessBtnNext.setOnClickListener { + if (isShareProcessStepIsPermission()) { + validateShareProcessFirst() + } else { + createShareOrUpdateNoteShare() + } + } + shareProcessSetPasswordSwitch.setOnCheckedChangeListener { _, isChecked -> + showPasswordInput(isChecked) } + shareProcessSetExpDateSwitch.setOnCheckedChangeListener { _, isChecked -> + showExpirationDateInput(isChecked) + } + shareProcessSetDownloadLimitSwitch.setOnCheckedChangeListener { _, isChecked -> + showFileDownloadLimitInput(isChecked) + } + shareProcessChangeNameSwitch.setOnCheckedChangeListener { _, isChecked -> + showChangeNameInput(isChecked) + } + shareProcessSelectExpDate.setOnClickListener { + showExpirationDateDialog() + } + + // region RadioButtons + shareRadioGroup.setOnCheckedChangeListener { _, optionId -> + when (optionId) { + R.id.view_only_radio_button -> { + permission = OCShare.READ_PERMISSION_FLAG + } + + R.id.can_edit_radio_button -> { + permission = SharePermissionManager.getMaximumPermission(isFolder()) + } + + R.id.file_request_radio_button -> { + permission = OCShare.CREATE_PERMISSION_FLAG + } + } + + val isCustomPermissionSelected = (optionId == R.id.custom_permission_radio_button) + customPermissionLayout.setVisibilityWithAnimation(isCustomPermissionSelected) + toggleNextButtonAvailability(true) + } + // endregion } - binding.shareProcessSetPasswordSwitch.setOnCheckedChangeListener { _, isChecked -> - showPasswordInput(isChecked) + } + + private fun isAnyShareOptionChecked(): Boolean { + return binding.run { + val isCustomPermissionChecked = customPermissionRadioButton.isChecked && + ( + shareReadCheckbox.isChecked || + shareCreateCheckbox.isChecked || + shareEditCheckbox.isChecked || + shareCheckbox.isChecked || + shareDeleteCheckbox.isChecked + ) + + viewOnlyRadioButton.isChecked || + canEditRadioButton.isChecked || + fileRequestRadioButton.isChecked || + isCustomPermissionChecked } - binding.shareProcessSetExpDateSwitch.setOnCheckedChangeListener { _, isChecked -> - showExpirationDateInput(isChecked) + } + + private fun toggleNextButtonAvailability(value: Boolean) { + binding.run { + shareProcessBtnNext.isEnabled = value + shareProcessBtnNext.isClickable = value } - binding.shareProcessSetDownloadLimitSwitch.setOnCheckedChangeListener { _, isChecked -> - showFileDownloadLimitInput(isChecked) + } + + @Suppress("NestedBlockDepth") + private fun setCheckboxStates() { + val currentPermissions = share?.permissions ?: permission + + binding.run { + SharePermissionManager.run { + shareReadCheckbox.isChecked = hasPermission(currentPermissions, OCShare.READ_PERMISSION_FLAG) + shareEditCheckbox.isChecked = hasPermission(currentPermissions, OCShare.UPDATE_PERMISSION_FLAG) + shareCheckbox.isChecked = hasPermission(currentPermissions, OCShare.SHARE_PERMISSION_FLAG) + + if (isFolder()) { + // Only for the folder makes sense to have create permission + // so that user can create files in the shared folder + shareCreateCheckbox.isChecked = hasPermission(currentPermissions, OCShare.CREATE_PERMISSION_FLAG) + shareDeleteCheckbox.isChecked = hasPermission(currentPermissions, OCShare.DELETE_PERMISSION_FLAG) + } else { + shareCreateCheckbox.visibility = View.GONE + shareDeleteCheckbox.apply { + isChecked = false + isEnabled = false + } + } + + if (!isPublicShare()) { + shareAllowDownloadAndSyncCheckbox.isChecked = isAllowDownloadAndSyncEnabled(share) + } + } } - binding.shareProcessChangeNameSwitch.setOnCheckedChangeListener { _, isChecked -> - showChangeNameInput(isChecked) + + setCheckboxesListeners() + } + + private fun setCheckboxesListeners() { + val checkboxes = mapOf( + binding.shareReadCheckbox to OCShare.READ_PERMISSION_FLAG, + binding.shareCreateCheckbox to OCShare.CREATE_PERMISSION_FLAG, + binding.shareEditCheckbox to OCShare.UPDATE_PERMISSION_FLAG, + binding.shareCheckbox to OCShare.SHARE_PERMISSION_FLAG, + binding.shareDeleteCheckbox to OCShare.DELETE_PERMISSION_FLAG + ) + + checkboxes.forEach { (checkbox, flag) -> + checkbox.setOnCheckedChangeListener { _, isChecked -> togglePermission(isChecked, flag) } } - binding.shareProcessSelectExpDate.setOnClickListener { - showExpirationDateDialog() + + if (!isPublicShare()) { + binding.shareAllowDownloadAndSyncCheckbox.setOnCheckedChangeListener { _, isChecked -> + val result = SharePermissionManager.toggleAllowDownloadAndSync(isChecked, share) + share?.attributes = result + downloadAttribute = result + } } } + private fun togglePermission(isChecked: Boolean, permissionFlag: Int) { + permission = SharePermissionManager.togglePermission(isChecked, permission, permissionFlag) + toggleNextButtonAvailability(true) + } + private fun showExpirationDateDialog(chosenDateInMillis: Long = chosenExpDateInMills) { val dialog = ExpirationDatePickerDialogFragment.newInstance(chosenDateInMillis) dialog.setOnExpiryDateListener(this) @@ -472,7 +677,8 @@ class FileDetailsSharingProcessFragment : } private fun showChangeNameInput(isChecked: Boolean) { - binding.shareProcessChangeNameContainer.visibility = if (isChecked) View.VISIBLE else View.GONE + binding.shareProcessChangeNameContainer.setVisibleIf(isChecked) + if (!isChecked) { binding.shareProcessChangeName.setText(R.string.empty) } @@ -483,11 +689,12 @@ class FileDetailsSharingProcessFragment : if (share != null) { removeCurrentFragment() } + // else we have to check if user is in step 2(note screen) then show step 1 (permission screen) // and if user is in step 1 (permission screen) then remove the fragment else { - if (shareProcessStep == SCREEN_TYPE_NOTE) { - showShareProcessFirst() + if (isShareProcessStepIsNote()) { + setupUI() } else { removeCurrentFragment() } @@ -495,8 +702,8 @@ class FileDetailsSharingProcessFragment : } private fun showExpirationDateInput(isChecked: Boolean) { - binding.shareProcessSelectExpDate.visibility = if (isChecked) View.VISIBLE else View.GONE - binding.shareProcessExpDateDivider.visibility = if (isChecked) View.VISIBLE else View.GONE + binding.shareProcessSelectExpDate.setVisibleIf(isChecked) + binding.shareProcessExpDateDivider.setVisibleIf(isChecked) // reset the expiration date if switch is unchecked if (!isChecked) { @@ -515,7 +722,7 @@ class FileDetailsSharingProcessFragment : } private fun showPasswordInput(isChecked: Boolean) { - binding.shareProcessEnterPasswordContainer.visibility = if (isChecked) View.VISIBLE else View.GONE + binding.shareProcessEnterPasswordContainer.setVisibleIf(isChecked) // reset the password if switch is unchecked if (!isChecked) { @@ -528,18 +735,11 @@ class FileDetailsSharingProcessFragment : fileActivity?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit() } - private fun getReSharePermission(): Int { - val spb = SharePermissionsBuilder() - spb.setSharePermission(true) - return spb.build() - } - /** * method to validate the step 1 screen information */ @Suppress("ReturnCount") private fun validateShareProcessFirst() { - permission = getSelectedPermission() if (permission == OCShare.NO_PERMISSION) { DisplayUtils.showSnackMessage(binding.root, R.string.no_share_permission_selected) return @@ -572,26 +772,48 @@ class FileDetailsSharingProcessFragment : removeCurrentFragment() } else { // else show step 2 (note screen) - showShareProcessSecond() + updateViewForNoteScreenType() } } - /** - * get the permissions on the basis of selection - */ - private fun getSelectedPermission() = when { - binding.shareProcessAllowResharingCheckbox.isChecked -> getReSharePermission() - binding.shareProcessPermissionReadOnly.isChecked -> OCShare.READ_PERMISSION_FLAG - binding.shareProcessPermissionUploadEditing.isChecked -> when { - file?.isFolder == true || share?.isFolder == true -> OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER - else -> OCShare.MAXIMUM_PERMISSIONS_FOR_FILE + @Suppress("ReturnCount") + private fun createShareOrUpdateNoteShare() { + if (!isAnyShareOptionChecked()) { + DisplayUtils.showSnackMessage(requireActivity(), R.string.share_option_required) + return } - binding.shareProcessPermissionFileDrop.isChecked -> OCShare.CREATE_PERMISSION_FLAG - else -> permission + val noteText = binding.noteText.text.toString().trim() + if (file == null && (share != null && share?.note == noteText)) { + DisplayUtils.showSnackMessage(requireActivity(), R.string.share_cannot_update_empty_note) + return + } + + when { + // if modifying existing share then directly update the note and send email + share != null && share?.note != noteText -> { + fileOperationsHelper?.updateNoteToShare(share, noteText) + } + + file == null -> { + DisplayUtils.showSnackMessage(requireActivity(), R.string.file_not_found_cannot_share) + return + } + + else -> { + createShare(noteText) + } + } + + removeCurrentFragment() } private fun updateShare() { + // empty string causing fails + if (share?.attributes?.isEmpty() == true) { + share?.attributes = null + } + fileOperationsHelper?.updateShareInformation( share, permission, @@ -601,16 +823,8 @@ class FileDetailsSharingProcessFragment : binding.shareProcessChangeName.text.toString().trim() ) - if (capabilities.filesDownloadLimit.isTrue) { - val downloadLimitInput = binding.shareProcessSetDownloadLimitInput.text.toString().trim() - val downloadLimit = - if (binding.shareProcessSetDownloadLimitSwitch.isChecked && downloadLimitInput.isNotEmpty()) { - downloadLimitInput.toInt() - } else { - 0 - } - - fileOperationsHelper?.updateFilesDownloadLimit(share, downloadLimit) + if (canSetDownloadLimit()) { + setDownloadLimit() } // copy the share link if available @@ -619,31 +833,32 @@ class FileDetailsSharingProcessFragment : } } - /** - * method to validate step 2 (note screen) information - */ - private fun validateShareProcessSecond() { - val noteText = binding.noteText.text.toString().trim() - // if modifying existing share then directly update the note and send email - if (share != null && share?.note != noteText) { - fileOperationsHelper?.updateNoteToShare(share, noteText) - } else { - // else create new share - fileOperationsHelper?.shareFileWithSharee( - file, - shareeName, - shareType, - permission, - binding - .shareProcessHideDownloadCheckbox.isChecked, - binding.shareProcessEnterPassword.text.toString().trim(), - chosenExpDateInMills, - noteText, - binding.shareProcessChangeName.text.toString().trim(), - true - ) - } - removeCurrentFragment() + private fun setDownloadLimit() { + val downloadLimitInput = binding.shareProcessSetDownloadLimitInput.text.toString().trim() + val downloadLimit = + if (binding.shareProcessSetDownloadLimitSwitch.isChecked && downloadLimitInput.isNotEmpty()) { + downloadLimitInput.toInt() + } else { + 0 + } + + fileOperationsHelper?.updateFilesDownloadLimit(share, downloadLimit) + } + + private fun createShare(noteText: String) { + fileOperationsHelper?.shareFileWithSharee( + file, + shareeName, + shareType, + permission, + binding.shareProcessHideDownloadCheckbox.isChecked, + binding.shareProcessEnterPassword.text.toString().trim(), + chosenExpDateInMills, + noteText, + downloadAttribute, + binding.shareProcessChangeName.text.toString().trim(), + true + ) } /** @@ -664,4 +879,25 @@ class FileDetailsSharingProcessFragment : override fun onDateUnSet() { binding.shareProcessSetExpDateSwitch.isChecked = false } + + // region Helpers + private fun isShareProcessStepIsPermission(): Boolean = ( + shareProcessStep == SCREEN_TYPE_PERMISSION || + isShareProcessStepIsCustomPermission() + ) + + private fun isShareProcessStepIsCustomPermission(): Boolean = + (shareProcessStep == SCREEN_TYPE_PERMISSION_WITH_CUSTOM_PERMISSION) + + private fun isShareProcessStepIsNote(): Boolean = (shareProcessStep == SCREEN_TYPE_NOTE) + + private fun isFolder(): Boolean = (file?.isFolder == true || share?.isFolder == true) + + private fun canSetFileRequest(): Boolean = isFolder() && shareType.isPublicOrMail() + + private fun canSetDownloadLimit(): Boolean = + (isPublicShare() && capabilities.filesDownloadLimit.isTrue && share?.isFolder == false) + + private fun isPublicShare(): Boolean = (shareType == ShareType.PUBLIC_LINK) + // endregion } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java index 820a6b0a7f22..f662522ccfb2 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java @@ -10,22 +10,24 @@ package com.owncloud.android.ui.fragment; +import android.app.Dialog; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.nextcloud.utils.extensions.OCShareExtensionsKt; import com.owncloud.android.R; import com.owncloud.android.databinding.QuickSharingPermissionsBottomSheetFragmentBinding; -import com.owncloud.android.datamodel.QuickPermissionModel; +import com.owncloud.android.datamodel.quickPermission.QuickPermission; +import com.owncloud.android.datamodel.quickPermission.QuickPermissionType; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.adapter.QuickSharingPermissionsAdapter; -import com.owncloud.android.ui.fragment.util.SharingMenuHelper; +import com.owncloud.android.ui.fragment.util.SharePermissionManager; import com.owncloud.android.utils.theme.ViewThemeUtils; -import java.util.ArrayList; import java.util.List; import androidx.recyclerview.widget.LinearLayoutManager; @@ -36,7 +38,7 @@ import static com.owncloud.android.lib.resources.shares.OCShare.READ_PERMISSION_FLAG; /** - * File Details Quick Sharing permissions options {@link android.app.Dialog} styled as a bottom sheet for main actions. + * File Details Quick Sharing permissions options {@link Dialog} styled as a bottom sheet for main actions. */ public class QuickSharingPermissionsBottomSheetDialog extends BottomSheetDialog { private QuickSharingPermissionsBottomSheetFragmentBinding binding; @@ -44,16 +46,19 @@ public class QuickSharingPermissionsBottomSheetDialog extends BottomSheetDialog private final FileActivity fileActivity; private final OCShare ocShare; private final ViewThemeUtils viewThemeUtils; + private final boolean encrypted; public QuickSharingPermissionsBottomSheetDialog(FileActivity fileActivity, QuickPermissionSharingBottomSheetActions actions, OCShare ocShare, - ViewThemeUtils viewThemeUtils) { + ViewThemeUtils viewThemeUtils, + boolean encrypted) { super(fileActivity); this.actions = actions; this.ocShare = ocShare; this.fileActivity = fileActivity; this.viewThemeUtils = viewThemeUtils; + this.encrypted = encrypted; } @Override @@ -76,13 +81,19 @@ protected void onCreate(Bundle savedInstanceState) { } private void setUpRecyclerView() { - List quickPermissionModelList = getQuickPermissionList(); + List quickPermissionList = getQuickPermissionList(); QuickSharingPermissionsAdapter adapter = new QuickSharingPermissionsAdapter( - quickPermissionModelList, + quickPermissionList, new QuickSharingPermissionsAdapter.QuickSharingPermissionViewHolder.OnPermissionChangeListener() { + @Override + public void onCustomPermissionSelected() { + dismiss(); + actions.openShareDetailWithCustomPermissions(ocShare); + } + @Override public void onPermissionChanged(int position) { - handlePermissionChanged(quickPermissionModelList, position); + handlePermissionChanged(quickPermissionList, position); } @Override @@ -98,60 +109,35 @@ public void onDismissSheet() { } /** - * handle permission changed on click of selected permission - * @param quickPermissionModelList - * @param position + * Handle permission changed on click of selected permission */ - private void handlePermissionChanged(List quickPermissionModelList, int position) { - if (quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string.link_share_allow_upload_and_editing)) - || quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string.link_share_editing))) { - if (ocShare.isFolder()) { - actions.onQuickPermissionChanged(ocShare, - MAXIMUM_PERMISSIONS_FOR_FOLDER); - } else { - actions.onQuickPermissionChanged(ocShare, - MAXIMUM_PERMISSIONS_FOR_FILE); - } - } else if (quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string - .link_share_view_only))) { - actions.onQuickPermissionChanged(ocShare, - READ_PERMISSION_FLAG); - - } else if (quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string - .link_share_file_drop))) { - actions.onQuickPermissionChanged(ocShare, - CREATE_PERMISSION_FLAG); + private void handlePermissionChanged(List quickPermissionList, int position) { + final var permissionName = quickPermissionList.get(position).getType().getText(getContext()); + final var res = fileActivity.getResources(); + + int permissionFlag = 0; + if (permissionName.equalsIgnoreCase(res.getString(R.string.share_permission_can_edit)) || permissionName.equalsIgnoreCase(res.getString(R.string.link_share_editing))) { + permissionFlag = ocShare.isFolder() ? MAXIMUM_PERMISSIONS_FOR_FOLDER : MAXIMUM_PERMISSIONS_FOR_FILE; + } else if (permissionName.equalsIgnoreCase(res.getString(R.string.share_permission_view_only))) { + permissionFlag = READ_PERMISSION_FLAG; + } else if (permissionName.equalsIgnoreCase(res.getString(R.string.share_permission_file_request))) { + permissionFlag = CREATE_PERMISSION_FLAG + READ_PERMISSION_FLAG; } + + actions.onQuickPermissionChanged(ocShare, permissionFlag); + dismiss(); } /** - * prepare the list of permissions needs to be displayed on recyclerview - * @return + * Prepare the list of permissions needs to be displayed on recyclerview */ - private List getQuickPermissionList() { - - String[] permissionArray; - if (ocShare.isFolder()) { - permissionArray = - fileActivity.getResources().getStringArray(R.array.folder_share_permission_dialog_values); - } else { - permissionArray = - fileActivity.getResources().getStringArray(R.array.file_share_permission_dialog_values); - } - //get the checked item position - int checkedItem = SharingMenuHelper.getPermissionCheckedItem(fileActivity, ocShare, permissionArray); - - - final List quickPermissionModelList = new ArrayList<>(permissionArray.length); - for (int i = 0; i < permissionArray.length; i++) { - QuickPermissionModel quickPermissionModel = new QuickPermissionModel(permissionArray[i], checkedItem == i); - quickPermissionModelList.add(quickPermissionModel); - } - return quickPermissionModelList; + private List getQuickPermissionList() { + final var selectedType = SharePermissionManager.INSTANCE.getSelectedType(ocShare, encrypted); + final var hasFileRequestPermission = OCShareExtensionsKt.hasFileRequestPermission(ocShare); + return QuickPermissionType.Companion.getAvailablePermissions(hasFileRequestPermission, selectedType); } - @Override protected void onStop() { super.onStop(); @@ -160,5 +146,7 @@ protected void onStop() { public interface QuickPermissionSharingBottomSheetActions { void onQuickPermissionChanged(OCShare share, int permission); + + void openShareDetailWithCustomPermissions(OCShare share); } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/util/SharePermissionManager.kt b/app/src/main/java/com/owncloud/android/ui/fragment/util/SharePermissionManager.kt new file mode 100644 index 000000000000..14e8a7dbddc0 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/fragment/util/SharePermissionManager.kt @@ -0,0 +1,177 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.fragment.util + +import com.owncloud.android.datamodel.quickPermission.QuickPermissionType +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.shares.OCShare +import com.owncloud.android.lib.resources.shares.attributes.ShareAttributes +import com.owncloud.android.lib.resources.shares.attributes.ShareAttributesJsonHandler +import com.owncloud.android.lib.resources.shares.attributes.getDownloadAttribute +import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment.Companion.TAG + +object SharePermissionManager { + + // region Permission change + fun togglePermission(isChecked: Boolean, permission: Int, permissionFlag: Int): Int { + Log_OC.d(TAG, "togglePermission before: $permission") + + if (!isPermissionValid(permission)) { + Log_OC.d(TAG, "permission is not valid, togglePermission cancelled") + return permission + } + + val result = if (isChecked) { + permission or permissionFlag + } else { + permission and permissionFlag.inv() + } + + Log_OC.d(TAG, "togglePermission after: $result") + + return result + } + // endregion + + // region Permission check + fun hasPermission(permission: Int, permissionFlag: Int): Boolean { + return permission != OCShare.NO_PERMISSION && (permission and permissionFlag) == permissionFlag + } + + @Suppress("ReturnCount") + private fun isPermissionValid(permission: Int): Boolean { + // Must have at least READ or CREATE permission. + if (!hasPermission(permission, OCShare.READ_PERMISSION_FLAG) && + !hasPermission(permission, OCShare.CREATE_PERMISSION_FLAG) + ) { + return false + } + + // Must have READ permission if have UPDATE or DELETE. + if (!hasPermission(permission, OCShare.READ_PERMISSION_FLAG) && + ( + hasPermission(permission, OCShare.UPDATE_PERMISSION_FLAG) || + hasPermission(permission, OCShare.DELETE_PERMISSION_FLAG) + ) + ) { + return false + } + + return true + } + // endregion + + // region DownloadAttribute + fun toggleAllowDownloadAndSync(isChecked: Boolean, share: OCShare?): String? { + val shareAttributes = getShareAttributes(share)?.toMutableList() + if (shareAttributes == null) { + val downloadAttribute = ShareAttributes.createDownloadAttributes(isChecked) + val updatedShareAttributes = listOf(downloadAttribute) + return ShareAttributesJsonHandler.toJson(updatedShareAttributes) + } + + val downloadAttributeIndex = shareAttributes.indexOf(shareAttributes.getDownloadAttribute()) + if (downloadAttributeIndex >= 0) { + val updatedAttribute = shareAttributes[downloadAttributeIndex].copy(value = isChecked) + shareAttributes[downloadAttributeIndex] = updatedAttribute + } + + return ShareAttributesJsonHandler.toJson(shareAttributes) + } + + fun isAllowDownloadAndSyncEnabled(share: OCShare?): Boolean { + return getShareAttributes(share).getDownloadAttribute()?.value == true + } + + private fun getShareAttributes(share: OCShare?): List? { + return share?.attributes?.let { ShareAttributesJsonHandler.toList(it) } + } + // endregion + + // region Helper Methods + fun canEdit(share: OCShare?): Boolean { + if (share == null) { + return false + } + + return hasPermission(share.permissions, getMaximumPermission(share.isFolder)) + } + + fun isViewOnly(share: OCShare?): Boolean { + return share?.permissions != OCShare.NO_PERMISSION && share?.permissions == OCShare.READ_PERMISSION_FLAG + } + + fun isFileRequest(share: OCShare?): Boolean { + if (share?.isFolder == false) { + return false + } + + return share?.permissions != OCShare.NO_PERMISSION && share?.permissions == OCShare.CREATE_PERMISSION_FLAG + } + + fun isSecureFileDrop(share: OCShare?): Boolean { + if (share == null) { + return false + } + + return hasPermission(share.permissions, OCShare.CREATE_PERMISSION_FLAG + OCShare.READ_PERMISSION_FLAG) + } + + fun canReshare(share: OCShare?): Boolean { + if (share == null) { + return false + } + + return (share.permissions and OCShare.Companion.SHARE_PERMISSION_FLAG) > 0 + } + + fun getSelectedType(share: OCShare?, encrypted: Boolean): QuickPermissionType { + return if (canEdit(share)) { + QuickPermissionType.CAN_EDIT + } else if (encrypted && isSecureFileDrop(share)) { + QuickPermissionType.SECURE_FILE_DROP + } else if (isFileRequest(share)) { + QuickPermissionType.FILE_REQUEST + } else if (isViewOnly(share)) { + QuickPermissionType.VIEW_ONLY + } else if (isCustomPermission(share)) { + QuickPermissionType.CUSTOM_PERMISSIONS + } else { + QuickPermissionType.NONE + } + } + + @Suppress("ReturnCount") + fun isCustomPermission(share: OCShare?): Boolean { + if (share == null) return false + val permissions = share.permissions + if (permissions == OCShare.NO_PERMISSION) return false + + val hasRead = hasPermission(permissions, OCShare.READ_PERMISSION_FLAG) + if (!hasRead) return false + + val hasCreate = hasPermission(permissions, OCShare.CREATE_PERMISSION_FLAG) + val hasUpdate = hasPermission(permissions, OCShare.UPDATE_PERMISSION_FLAG) + val hasDelete = hasPermission(permissions, OCShare.DELETE_PERMISSION_FLAG) + val hasShare = hasPermission(permissions, OCShare.SHARE_PERMISSION_FLAG) + + return when { + share.isFolder -> hasCreate || hasUpdate || hasDelete || hasShare + else -> hasUpdate || hasShare + } + } + + fun getMaximumPermission(isFolder: Boolean): Int { + return if (isFolder) { + OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER + } else { + OCShare.MAXIMUM_PERMISSIONS_FOR_FILE + } + } + // endregion +} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java b/app/src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java deleted file mode 100644 index 899ff9cd73af..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Nextcloud Android client application - * - * @author Andy Scherzinger - * @author TSI-mc - * Copyright (C) 2018 Andy Scherzinger - * Copyright (C) 2021 TSI-mc - * - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ - -package com.owncloud.android.ui.fragment.util; - -import android.content.Context; -import android.content.res.Resources; -import android.view.MenuItem; - -import com.owncloud.android.R; -import com.owncloud.android.lib.resources.shares.OCShare; - -import java.text.SimpleDateFormat; -import java.util.Date; - -import static com.owncloud.android.lib.resources.shares.OCShare.CREATE_PERMISSION_FLAG; -import static com.owncloud.android.lib.resources.shares.OCShare.MAXIMUM_PERMISSIONS_FOR_FILE; -import static com.owncloud.android.lib.resources.shares.OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER; -import static com.owncloud.android.lib.resources.shares.OCShare.NO_PERMISSION; -import static com.owncloud.android.lib.resources.shares.OCShare.READ_PERMISSION_FLAG; -import static com.owncloud.android.lib.resources.shares.OCShare.SHARE_PERMISSION_FLAG; - -/** - * Helper calls for visibility logic of the sharing menu. - */ -public final class SharingMenuHelper { - - private SharingMenuHelper() { - // utility class -> private constructor - } - - /** - * Sets checked/visibility state on the given {@link MenuItem} based on the given criteria. - * - * @param menuItem the {@link MenuItem} to be setup - */ - public static void setupHideFileDownload(MenuItem menuItem, - boolean hideFileDownload, - boolean isFileDrop) { - if (isFileDrop) { - menuItem.setVisible(false); - } else { - menuItem.setVisible(true); - menuItem.setChecked(hideFileDownload); - } - } - - /** - * sets up the password {@link MenuItem}'s title based on the fact if a password is present. - * - * @param password the password {@link MenuItem} - * @param isPasswordProtected flag is a password is present - */ - public static void setupPasswordMenuItem(MenuItem password, boolean isPasswordProtected) { - if (isPasswordProtected) { - password.setTitle(R.string.share_password_title); - } else { - password.setTitle(R.string.share_no_password_title); - } - } - - /** - * sets up the expiration date {@link MenuItem}'s title based on the fact if an expiration date is present. - * - * @param expirationDate the expiration date {@link MenuItem} - * @param expirationDateValue the expiration date - * @param res Resources to load the corresponding strings. - */ - public static void setupExpirationDateMenuItem(MenuItem expirationDate, long expirationDateValue, Resources res) { - if (expirationDateValue > 0) { - expirationDate.setTitle(res.getString( - R.string.share_expiration_date_label, - SimpleDateFormat.getDateInstance().format(new Date(expirationDateValue)) - )); - } else { - expirationDate.setTitle(R.string.share_no_expiration_date_label); - } - } - - public static boolean isUploadAndEditingAllowed(OCShare share) { - if (share.getPermissions() == NO_PERMISSION) { - return false; - } - - return (share.getPermissions() & (share.isFolder() ? MAXIMUM_PERMISSIONS_FOR_FOLDER : - MAXIMUM_PERMISSIONS_FOR_FILE)) == (share.isFolder() ? MAXIMUM_PERMISSIONS_FOR_FOLDER : - MAXIMUM_PERMISSIONS_FOR_FILE); - } - - public static boolean isReadOnly(OCShare share) { - if (share.getPermissions() == NO_PERMISSION) { - return false; - } - - return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == READ_PERMISSION_FLAG; - } - - public static boolean isFileDrop(OCShare share) { - if (share.getPermissions() == NO_PERMISSION) { - return false; - } - - return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == CREATE_PERMISSION_FLAG; - } - - public static boolean isSecureFileDrop(OCShare share) { - if (share.getPermissions() == NO_PERMISSION) { - return false; - } - - return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == CREATE_PERMISSION_FLAG + READ_PERMISSION_FLAG; - } - - public static String getPermissionName(Context context, OCShare share) { - if (SharingMenuHelper.isUploadAndEditingAllowed(share)) { - return context.getResources().getString(R.string.share_permission_can_edit); - } else if (SharingMenuHelper.isReadOnly(share)) { - return context.getResources().getString(R.string.share_permission_view_only); - } else if (SharingMenuHelper.isSecureFileDrop(share)) { - return context.getResources().getString(R.string.share_permission_secure_file_drop); - } else if (SharingMenuHelper.isFileDrop(share)) { - return context.getResources().getString(R.string.share_permission_file_drop); - } - return null; - } - - /** - * method to get the current checked index from the list of permissions - * - */ - public static int getPermissionCheckedItem(Context context, OCShare share, String[] permissionArray) { - if (SharingMenuHelper.isUploadAndEditingAllowed(share)) { - if (share.isFolder()) { - return getPermissionIndexFromArray(context, permissionArray, R.string.link_share_allow_upload_and_editing); - } else { - return getPermissionIndexFromArray(context, permissionArray, R.string.link_share_editing); - } - } else if (SharingMenuHelper.isReadOnly(share)) { - return getPermissionIndexFromArray(context, permissionArray, R.string.link_share_view_only); - } else if (SharingMenuHelper.isFileDrop(share)) { - return getPermissionIndexFromArray(context, permissionArray, R.string.link_share_file_drop); - } - return 0;//default first item selected - } - - private static int getPermissionIndexFromArray(Context context, String[] permissionArray, int permissionName) { - for (int i = 0; i < permissionArray.length; i++) { - if (permissionArray[i].equalsIgnoreCase(context.getResources().getString(permissionName))) { - return i; - } - } - return 0; - } - - public static boolean canReshare(OCShare share) { - return (share.getPermissions() & SHARE_PERMISSION_FLAG) > 0; - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 9d4de1321383..5acc339d4949 100755 --- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -534,6 +534,7 @@ public void shareFileWithSharee(OCFile file, String password, long expirationTimeInMillis, String note, + String attributes, String label, boolean showLoadingDialog) { if (file != null) { @@ -555,6 +556,7 @@ public void shareFileWithSharee(OCFile file, service.putExtra(OperationsService.EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, expirationTimeInMillis); service.putExtra(OperationsService.EXTRA_SHARE_NOTE, (note == null) ? "" : note); service.putExtra(OperationsService.EXTRA_SHARE_PUBLIC_LABEL, (label == null) ? "" : label); + service.putExtra(OperationsService.EXTRA_SHARE_ATTRIBUTES, attributes); mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); @@ -588,16 +590,13 @@ public void restoreFileVersion(FileVersion fileVersion) { * * @param file The file to unshare. */ - public void unshareShare(OCFile file, OCShare share) { - - // Unshare the file: Create the intent - Intent unshareService = new Intent(fileActivity, OperationsService.class); - unshareService.setAction(OperationsService.ACTION_UNSHARE); - unshareService.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); - unshareService.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); - unshareService.putExtra(OperationsService.EXTRA_SHARE_ID, share.getId()); - - queueShareIntent(unshareService); + public void unShareShare(OCFile file, OCShare share) { + Intent intent = new Intent(fileActivity, OperationsService.class); + intent.setAction(OperationsService.ACTION_UNSHARE); + intent.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); + intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); + intent.putExtra(OperationsService.EXTRA_SHARE_ID, share.getId()); + queueShareIntent(intent); } private void queueShareIntent(Intent shareIntent) { @@ -751,18 +750,33 @@ public void updateFilesDownloadLimit(OCShare share, int newLimit) { * leaving the link unrestricted. Zero makes no change. * @param label new label */ - public void updateShareInformation(OCShare share, int permissions, - boolean hideFileDownload, String password, long expirationTimeInMillis, + public void updateShareInformation(OCShare share, + int permissions, + boolean hideFileDownload, + String password, + long expirationTimeInMillis, String label) { + final var id = share.getId(); + final var attributes = share.getAttributes(); + + Log_OC.i(TAG, "-----AFTER UPDATE SHARE-----"); + Log_OC.i(TAG, "ID: " + id); + Log_OC.i(TAG, "Permission: " + permissions); + Log_OC.i(TAG, "Hide File Download: " + hideFileDownload); + Log_OC.i(TAG, "Label: " + label); + Log_OC.i(TAG, "Attributes: " + attributes); + + Intent updateShareIntent = new Intent(fileActivity, OperationsService.class); updateShareIntent.setAction(OperationsService.ACTION_UPDATE_SHARE_INFO); updateShareIntent.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); - updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_ID, share.getId()); + updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_ID, id); updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_PERMISSIONS, permissions); updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_HIDE_FILE_DOWNLOAD, hideFileDownload); updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, (password == null) ? "" : password); updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, expirationTimeInMillis); updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_PUBLIC_LABEL, (label == null) ? "" : label); + updateShareIntent.putExtra(OperationsService.EXTRA_SHARE_ATTRIBUTES, attributes); queueShareIntent(updateShareIntent); } diff --git a/app/src/main/res/drawable/ic_custom_permissions.xml b/app/src/main/res/drawable/ic_custom_permissions.xml new file mode 100644 index 000000000000..b02f8b939490 --- /dev/null +++ b/app/src/main/res/drawable/ic_custom_permissions.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/ic_eye.xml b/app/src/main/res/drawable/ic_eye.xml new file mode 100644 index 000000000000..9ea4987e52ac --- /dev/null +++ b/app/src/main/res/drawable/ic_eye.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_file_request.xml b/app/src/main/res/drawable/ic_file_request.xml new file mode 100644 index 000000000000..470d7d30b469 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_request.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/layout/file_details_sharing_process_fragment.xml b/app/src/main/res/layout/file_details_sharing_process_fragment.xml index 14af3dff721c..60ac7edbebd0 100644 --- a/app/src/main/res/layout/file_details_sharing_process_fragment.xml +++ b/app/src/main/res/layout/file_details_sharing_process_fragment.xml @@ -39,52 +39,112 @@ android:textStyle="bold" /> + android:text="@string/share_permission_view_only" /> + android:text="@string/share_permission_can_edit" /> + android:text="@string/share_permission_file_request" /> - + - + + + + + + + + + + + + + + + + + - + ~ SPDX-License-Identifier: AGPL-3.0-or-later + --> + - - - - - - + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:insetTop="0dp" + android:insetBottom="0dp" + android:minHeight="56dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + app:iconGravity="textStart" + app:iconPadding="12dp" /> \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 108443d3e383..8641f4e9de73 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -26,16 +26,6 @@ @string/pref_instant_name_collision_policy_entries_cancel - - @string/link_share_view_only - @string/link_share_allow_upload_and_editing - @string/link_share_file_drop - - - - @string/link_share_view_only - @string/link_share_editing - @string/sub_folder_rule_month @string/sub_folder_rule_year diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2cc9290035cc..512d252ab328 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -616,12 +616,10 @@ Sharing Share %1$s - Expires %1$s %1$s Set expiration date Share link Send link - Password-protected Set password Share with… Unset @@ -838,6 +836,7 @@ Background image of drawer header Account icon No app available to select contacts + You cannot create a share, sharing is already active from this user. Failed to verify public key Unable to retrieve public key @@ -1073,6 +1072,11 @@ New %1$s %2$s + File request + View only + Can edit + Secure file drop + Choose what to sync Free up space %1$s is %2$s, but there is only %3$s available on device. @@ -1092,11 +1096,17 @@ New name Share link (%1$s) Share link - Allow resharing - View only + Custom permissions + Read + File not found. Unable to create a share. + We couldn’t update the share. Please add a note and try again. + Please select at least one sharing option before continuing. + Allow download and sync + Create + Edit + Share + Delete Editing - Allow upload and editing - File drop (upload only) Could not retrieve shares Failed to update UI Failed to pick email address. @@ -1162,12 +1172,7 @@ Create Please select one template Please choose a template and enter a file name. - View only - Can edit - File drop - Secure file drop Share permissions - Advanced settings Next Send share Please select at least one permission to share. @@ -1330,6 +1335,7 @@ Due to new restrictions imposed by Google, we have been forced to remove an important permission. We are currently working with Google to resolve this issue and restore full functionality.\n\nTo re-enable auto upload for new photos and videos:\nSelect \"Allow all\" in the following dialogue or the system settings.\nAllow media location when prompted, as this allows Nextcloud to store location data when uploading images.\n\nThe permissions dialogue is only displayed when necessary. If in doubt, check the system settings.\n\nAuto upload will only be able to upload image and video files when using the Google Play version of the Nextcloud app.\n\nPlease check for any files that may not have been uploaded since December 2024. Manual intervention required to re-enable auto-upload Set download limit + Unable to set download limit. Please check capabilities. Download limit %1$d download remaining diff --git a/build.gradle b/build.gradle index 7555c7e33b26..09c2ba8dd76c 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ */ buildscript { ext { - androidLibraryVersion ="8e561c7e65f208492c20b106eb25cfbe43dc7189" + androidLibraryVersion ="d862794d794a7e8d8b53da98aa801753e684bf52" androidCommonLibraryVersion = "0.25.0" androidPluginVersion = "8.9.2" androidxMediaVersion = "1.5.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index d7da67a84957..863002282126 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -11215,6 +11215,14 @@ + + + + + + + + @@ -11447,6 +11455,14 @@ + + + + + + + + @@ -11503,6 +11519,14 @@ + + + + + + + + @@ -11543,6 +11567,14 @@ + + + + + + + + @@ -11583,6 +11615,14 @@ + + + + + + + +