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 0000000000..c0b53e5fd5 --- /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 0000000000..071adc9915 --- /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 29c1e120ec..c3a0db94d8 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 registerIdlingResource() { + IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource) + } + + @After + fun unregisterIdlingResource() { + IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource) + } @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) - } + 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 + 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) - } + 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 = 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 = 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 = 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) - } + 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) - } + 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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) - } + 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) + launchActivity().use { scenario -> + scenario.onActivity { sut -> + onIdleSync { + EspressoIdlingResource.increment() + val fragment = FileDetailSharingFragment.newInstance(file, user) + sut.addFragment(fragment) + EspressoIdlingResource.decrement() - activity.addFragment(fragment) - - waitForIdleSync() - - screenshot(activity) + 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() + 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 + 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() + 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 + 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() + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) - val publicShare = OCShare().apply { - isFolder = false - shareType = ShareType.PUBLIC_LINK - permissions = 17 + 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() + 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 + 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() + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + suppressFDFAccessibilityChecks() + activity.addFragment(sut) - val userShare = OCShare().apply { - isFolder = false - shareType = ShareType.USER - permissions = 17 + 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() + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) - val userShare = OCShare().apply { - remoteId = 1001L - isFolder = false - shareType = ShareType.USER - permissions = 17 + 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() + launchActivity().use { scenario -> + scenario.onActivity { activity -> + val sut = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(sut) - val userShare = OCShare().apply { - isFolder = true - shareType = ShareType.USER - permissions = 17 + 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() + 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 + 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) } + launchActivity().use { scenario -> + scenario.onActivity { activity -> + onIdleSync { + EspressoIdlingResource.increment() + activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } + EspressoIdlingResource.decrement() - waitForIdleSync() - // click event - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).perform(ViewActions.click()) + // 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())) + // 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 + 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 + ) + + val share = OCShare() + for ((permission, expected) in testCases) { + share.permissions = permission + assertEquals("Failed for permission: $permission", expected, SharePermissionManager.canEdit(share)) } - 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)) - - share.permissions = SHARE_PERMISSION_FLAG - assertFalse(SharingMenuHelper.isUploadAndEditingAllowed(share)) } @Test - @Suppress("MagicNumber") fun testReadOnlySharePermissions() { - val share = OCShare().apply { - permissions = 17 + 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 + ) + + val share = OCShare() + for ((permission, expected) in testCases) { + share.permissions = permission + assertEquals("Failed for permission: $permission", expected, SharePermissionManager.isViewOnly(share)) } - 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)) - - share.permissions = MAXIMUM_PERMISSIONS_FOR_FILE - assertFalse(SharingMenuHelper.isReadOnly(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 6f0dfca9ca..55177ef6e4 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 1c397ced03..ad5005efef 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 0000000000..896d26dca8 --- /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 0000000000..45cfa46dfd --- /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 0000000000..4200fae866 --- /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 02cdbc3f91..a47cff2810 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 2f84154ede..de005a984e 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 @@ public class FileDataStorageManager { contentValues.putNull(ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT); } + contentValues.put(ProviderTableMeta.OCSHARES_ATTRIBUTES, share.getAttributes()); + return contentValues; } @@ -1599,6 +1601,8 @@ public class FileDataStorageManager { 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 b24bfcd88a..0000000000 --- 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 0000000000..460edeb333 --- /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 0000000000..51dfbb5bf2 --- /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 955aeb1d90..120da6ac9d 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 @@ import java.util.List; */ 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 @@ public class ProviderMeta { 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 469829bbaa..68a5751938 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 class CreateShareWithShareeOperation extends SyncOperation { String sharePassword, long expirationDateInMillis, boolean hideFileDownload, + String attributes, FileDataStorageManager storageManager, Context context, User user, @@ -105,6 +107,7 @@ public class CreateShareWithShareeOperation extends SyncOperation { this.context = context; this.user = user; this.arbitraryDataProvider = arbitraryDataProvider; + this.attributes = attributes; } @Override @@ -156,7 +159,8 @@ public class CreateShareWithShareeOperation extends SyncOperation { 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 bdfc32923a..8168c0dd11 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 @@ public class UnshareOperation extends SyncOperation { 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 @@ public class UnshareOperation extends SyncOperation { 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 918e5577a0..1c078c2004 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 @@ public class UpdateShareInfoOperation extends SyncOperation { 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 @@ public class UpdateShareInfoOperation extends SyncOperation { } 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 class UpdateShareInfoOperation extends SyncOperation { 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 4ccadf9d6b..d2b8a9c75d 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 @@ public class OperationsService extends Service { // 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 @@ public class OperationsService extends Service { .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 @@ public class OperationsService extends Service { sharePassword, expirationDateInMillis, hideFileDownload, + attributes, fileDataStorageManager, getApplicationContext(), user, @@ -641,6 +650,9 @@ public class OperationsService extends Service { 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 d78a98bddf..b6c93efd0d 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 abstract class FileActivity extends DrawerActivity 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 b2e70da44d..15e35a7609 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.android.lib.resources.files.FileDownloadLimit; 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 @@ class LinkShareViewHolder extends RecyclerView.ViewHolder { 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)); + + final var emailDrawable = ResourcesCompat.getDrawable(res, R.drawable.ic_email, null); + binding.icon.setImageDrawable(emailDrawable); 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); } else { - if (!TextUtils.isEmpty(publicShare.getLabel())) { - String text = String.format(context.getString(R.string.share_link_with_label), publicShare.getLabel()); - binding.name.setText(text); - } 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); - } - } + String label = publicShare.getLabel(); - viewThemeUtils.platform.colorImageViewBackgroundAndIcon(binding.icon); + 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 { + 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); + FileDownloadLimit downloadLimit = publicShare.getFileDownloadLimit(); if (downloadLimit != null && downloadLimit.getLimit() > 0) { int remaining = downloadLimit.getLimit() - downloadLimit.getCount(); @@ -88,11 +91,11 @@ class LinkShareViewHolder extends RecyclerView.ViewHolder { 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 @@ class LinkShareViewHolder extends RecyclerView.ViewHolder { } 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 64ffa03500..06a444fb01 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 4c9b95471f..974ea6d565 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.view.View; 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 @@ class ShareViewHolder extends RecyclerView.ViewHolder { 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 @@ class ShareViewHolder extends RecyclerView.ViewHolder { 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); + 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); - binding.icon.setOnClickListener(v -> listener.showProfileBottomSheet(user, share.getShareWith())); - default: - setImage(binding.icon, name, R.drawable.ic_user); - break; + 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 @@ class ShareViewHolder extends RecyclerView.ViewHolder { } } - 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 2b22b72050..16be239081 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 class ShareeListAdapter extends RecyclerView.Adapter= 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 class ShareeListAdapter extends RecyclerView.Adapter 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 b5f567d344..3ad6b09a13 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 org.greenrobot.eventbus.ThreadMode; 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 class FileDetailFragment extends FileFragment implements OnClickListener, 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 class FileDetailFragment extends FileFragment implements OnClickListener, 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 @@ public class FileDetailFragment extends FileFragment implements OnClickListener, /** * 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 1d6b78a436..bc4bc399b9 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.di.Injectable; 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 class FileDetailSharingFragment extends Fragment implements ShareeListAda file.isEncrypted(), SharesType.INTERNAL); + internalShareeListAdapter.setHasStableIds(true); + binding.sharesListInternal.setAdapter(internalShareeListAdapter); binding.sharesListInternal.setLayoutManager(new LinearLayoutManager(requireContext())); @@ -190,6 +193,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda viewThemeUtils, file.isEncrypted(), SharesType.EXTERNAL); + + externalShareeListAdapter.setHasStableIds(true); binding.sharesListExternal.setAdapter(externalShareeListAdapter); @@ -214,10 +219,11 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda 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 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda 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 class FileDetailSharingFragment extends Fragment implements ShareeListAda @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 class FileDetailSharingFragment extends Fragment implements ShareeListAda */ @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 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda setupView(); } - private void unshareWith(OCShare share) { - fileOperationsHelper.unshareShare(file, share); + private void unShareWith(OCShare share) { + fileOperationsHelper.unShareShare(file, share); } /** @@ -517,7 +515,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda 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 class FileDetailSharingFragment extends Fragment implements ShareeListAda } internalShareeListAdapter.addShares(internalShares); + ViewExtensionsKt.setVisibleIf(binding.sharesListInternalShowAll, internalShareeListAdapter.getShares().size() > 3); - ViewExtensionsKt.setVisibleIf(binding.sharesListInternalShowAll, - internalShareeListAdapter.getShares().size() > 3 - ); + addExternalAndPublicShares(externalShares); + ViewExtensionsKt.setVisibleIf(binding.sharesListExternalShowAll, externalShareeListAdapter.getShares().size() > 3); + } - externalShareeListAdapter.getShares().clear(); - - // Get public share - List publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), - ShareType.PUBLIC_LINK, - ""); - - externalShareeListAdapter.addShares(externalShares); - - externalShareeListAdapter.addShares(publicShares); - - 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 class FileDetailSharingFragment extends Fragment implements ShareeListAda modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); } - @Override public void sendNewEmail(OCShare share) { modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); @@ -658,13 +648,15 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda @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 class FileDetailSharingFragment extends Fragment implements ShareeListAda 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 be6779b957..86b21f3328 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 android.view.ViewGroup; 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 @@ public class FileDetailSharingMenuBottomSheetDialog extends BottomSheetDialog { 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 @@ public class FileDetailSharingMenuBottomSheetDialog extends BottomSheetDialog { 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 144d3f5bf7..7479c07467 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) + } - 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) + if (canSetFileRequest()) { + themeRadioButton(fileRequestRadioButton) + } - viewThemeUtils.material.colorTextInputLayout(binding.shareProcessEnterPasswordContainer) - viewThemeUtils.material.colorTextInputLayout(binding.shareProcessSetDownloadLimitInputContainer) - viewThemeUtils.material.colorTextInputLayout(binding.shareProcessChangeNameContainer) - viewThemeUtils.material.colorTextInputLayout(binding.noteContainer) + themeCheckbox(shareReadCheckbox) + themeCheckbox(shareCreateCheckbox) + themeCheckbox(shareEditCheckbox) + themeCheckbox(shareCheckbox) + themeCheckbox(shareDeleteCheckbox) + } + } - viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.shareProcessBtnNext) - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.shareProcessBtnCancel) + viewThemeUtils.androidx.run { + binding.run { + colorSwitchCompat(shareProcessSetPasswordSwitch) + colorSwitchCompat(shareProcessSetExpDateSwitch) + colorSwitchCompat(shareProcessSetDownloadLimitSwitch) + colorSwitchCompat(shareProcessHideDownloadCheckbox) + colorSwitchCompat(shareProcessChangeNameSwitch) + } + } + + 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 - - if (share != null) { - setupModificationUI() - } else { - setupUpdateUI() + private fun setupUI() { + binding.run { + shareProcessGroupOne.visibility = View.VISIBLE + shareProcessEditShareLink.visibility = View.VISIBLE + shareProcessGroupTwo.visibility = View.GONE } - if (isSecureShare) { - binding.shareProcessAdvancePermissionTitle.visibility = View.GONE - } + updateView() // show or hide expiry date - if (isExpDateShown && !isSecureShare) { - binding.shareProcessSetExpDateSwitch.visibility = View.VISIBLE - } else { - binding.shareProcessSetExpDateSwitch.visibility = View.GONE - } + binding.shareProcessSetExpDateSwitch.setVisibleIf(isExpDateShown && !isSecureShare) shareProcessStep = SCREEN_TYPE_PERMISSION } - private fun setupModificationUI() { + private fun updateView() { + if (share != null) { + updateViewForUpdate() + } else { + updateViewForCreate() + } + } + + private fun setMaxPermissionsIfDefaultPermissionExists() { + if (capabilities.defaultPermissions != null) { + binding.canEditRadioButton.isChecked = true + permission = SharePermissionManager.getMaximumPermission(isFolder()) + } + } + + // 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() + } + } + + 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 + 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) { - binding.shareProcessAllowResharingCheckbox.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 + binding.run { + shareProcessHideDownloadCheckbox.visibility = View.VISIBLE + shareCheckbox.visibility = View.GONE + 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 + 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,63 +493,175 @@ 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) - /** - * 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) - } - shareProcessStep = SCREEN_TYPE_NOTE - } - - private fun implementClickEvents() { - binding.shareProcessBtnCancel.setOnClickListener { - onCancelClick() - } - binding.shareProcessBtnNext.setOnClickListener { - if (shareProcessStep == SCREEN_TYPE_PERMISSION) { - validateShareProcessFirst() - } else { - validateShareProcessSecond() + if (isSecureShare) { + shareCheckbox.visibility = View.GONE + shareProcessSetExpDateSwitch.visibility = View.GONE } } - binding.shareProcessSetPasswordSwitch.setOnCheckedChangeListener { _, isChecked -> - showPasswordInput(isChecked) + } + + 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() } - binding.shareProcessSetExpDateSwitch.setOnCheckedChangeListener { _, isChecked -> - showExpirationDateInput(isChecked) + } + // endregion + + @Suppress("LongMethod") + private fun implementClickEvents() { + 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.shareProcessSetDownloadLimitSwitch.setOnCheckedChangeListener { _, isChecked -> - showFileDownloadLimitInput(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.shareProcessChangeNameSwitch.setOnCheckedChangeListener { _, isChecked -> - showChangeNameInput(isChecked) + } + + private fun toggleNextButtonAvailability(value: Boolean) { + binding.run { + shareProcessBtnNext.isEnabled = value + shareProcessBtnNext.isClickable = value } - binding.shareProcessSelectExpDate.setOnClickListener { - showExpirationDateDialog() + } + + @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) + } + } } + + 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) } + } + + 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) { @@ -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 820a6b0a7f..f662522ccf 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.MAXIMUM_PERMISSI 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 @@ public class QuickSharingPermissionsBottomSheetDialog extends BottomSheetDialog } 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 class QuickSharingPermissionsBottomSheetDialog extends BottomSheetDialog } /** - * 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); + private void handlePermissionChanged(List quickPermissionList, int position) { + final var permissionName = quickPermissionList.get(position).getType().getText(getContext()); + final var res = fileActivity.getResources(); - } else if (quickPermissionModelList.get(position).getPermissionName().equalsIgnoreCase(fileActivity.getResources().getString(R.string - .link_share_file_drop))) { - actions.onQuickPermissionChanged(ocShare, - CREATE_PERMISSION_FLAG); + 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 @@ public class QuickSharingPermissionsBottomSheetDialog extends BottomSheetDialog 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 0000000000..14e8a7dbdd --- /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 899ff9cd73..0000000000 --- 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 9d4de13213..5acc339d49 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 class FileOperationsHelper { String password, long expirationTimeInMillis, String note, + String attributes, String label, boolean showLoadingDialog) { if (file != null) { @@ -555,6 +556,7 @@ public class FileOperationsHelper { 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 class FileOperationsHelper { * * @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 class FileOperationsHelper { * 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 0000000000..b02f8b9394 --- /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 0000000000..9ea4987e52 --- /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 0000000000..470d7d30b4 --- /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 14af3dff72..60ac7edbeb 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_custom_permission" /> - - - + + + + + + + + + + + + + + + + - + ~ 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-da/strings.xml b/app/src/main/res/values-da/strings.xml index 5f82bdc2dd..7e4ea21d88 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -787,7 +787,7 @@ Automatisk upload for dine billeder og videoer Kalender og kontakter - Synk med DAVx⁵ + Synkronisér med DAVx⁵ Fejl ved indhenting af søgeresultater Sikker deling er ikke sat op for denne bruger Sikker deling... @@ -1119,12 +1119,12 @@ %d sekunder siden - %d minut sidenago + %d minut siden %d minutter siden - %d time sidenago - %d timer sidenago + %d time siden + %d timer siden Kunne ikke synkronisere %1$d fil (konflikter: %2$d) diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index e12e895aed..573eb5ec9c 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -269,6 +269,7 @@ Pašreizējais mapes nosaukums ir nederīgs, lūgums pārdēvēt mapi. Pārvirza uz sākumu... %s. Lūgums pirms pārvietošanas vai kopēšanas pārdēvēt datni Datne nav atrasta + Datni nevarēja sinhronizēt. Rāda pēdējo pieejamo versiju. Pārdēvēt Detaļas Lejupielādēt diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 108443d3e3..8641f4e9de 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 2cc9290035..512d252ab3 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 9c0de33bbe..09c2ba8dd7 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ */ buildscript { ext { - androidLibraryVersion ="13fb6e486d82696073cc1a56a8ff8371bba690de" + 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 c8e8a2ea7a..f107b4b1b8 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -11279,6 +11279,14 @@ + + + + + + + + @@ -11583,12 +11591,12 @@ - - - + + + - - + + @@ -11655,6 +11663,14 @@ + + + + + + + + @@ -11743,6 +11759,14 @@ + + + + + + + + @@ -11791,6 +11815,14 @@ + + + + + + + +