瀏覽代碼

Merge pull request #5622 from nextcloud/feature/bring-back-missing-share-options

Implement missing share settings
allexzander 2 年之前
父節點
當前提交
6eb248b37f

+ 1 - 0
resources.qrc

@@ -51,5 +51,6 @@
         <file>src/gui/tray/EnforcedPlainTextLabel.qml</file>
         <file>theme/Style/Style.qml</file>
         <file>theme/Style/qmldir</file>
+        <file>src/gui/filedetails/NCRadioButton.qml</file>
     </qresource>
 </RCC>

+ 43 - 0
src/gui/filedetails/NCRadioButton.qml

@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import Style 1.0
+
+RadioButton {
+    id: root
+    property int indicatorItemWidth: Style.radioButtonIndicatorSize
+    property int indicatorItemHeight: Style.radioButtonIndicatorSize
+    property string color: Style.ncTextColor
+    readonly property int radius: Style.radioButtonCustomRadius
+
+    indicator: Rectangle {
+        implicitWidth: root.indicatorItemWidth
+        implicitHeight: root.indicatorItemHeight
+        anchors.verticalCenter: parent.verticalCenter
+        anchors.left: parent.left
+        anchors.leftMargin: Style.radioButtonCustomMarginLeftOuter
+        radius: root.radius
+        border.color: root.color
+        border.width: Style.normalBorderWidth
+        Rectangle {
+            anchors.fill: parent
+            visible: root.checked
+            color: root.color
+            radius: root.radius
+            anchors.margins: Style.radioButtonCustomMarginLeftInner
+        }
+    }
+}

+ 4 - 0
src/gui/filedetails/ShareDelegate.qml

@@ -33,11 +33,13 @@ GridLayout {
     signal resetPasswordField
     signal showPasswordSetError(string errorMessage);
 
+    signal toggleHideDownload(bool enable)
     signal toggleAllowEditing(bool enable)
     signal toggleAllowResharing(bool enable)
     signal togglePasswordProtect(bool enable)
     signal toggleExpirationDate(bool enable)
     signal toggleNoteToRecipient(bool enable)
+    signal permissionModeChanged(int permissionMode)
 
     signal setLinkShareLabel(string label)
     signal setExpireDate(var milliseconds) // Since QML ints are only 32 bits, use a variant
@@ -236,9 +238,11 @@ GridLayout {
 
                     onToggleAllowEditing: root.toggleAllowEditing(enable)
                     onToggleAllowResharing: root.toggleAllowResharing(enable)
+                    onToggleHideDownload: root.toggleHideDownload(enable)
                     onTogglePasswordProtect: root.togglePasswordProtect(enable)
                     onToggleExpirationDate: root.toggleExpirationDate(enable)
                     onToggleNoteToRecipient: root.toggleNoteToRecipient(enable)
+                    onPermissionModeChanged: root.permissionModeChanged(permissionMode)
 
                     onSetLinkShareLabel: root.setLinkShareLabel(label)
                     onSetExpireDate: root.setExpireDate(milliseconds) // Since QML ints are only 32 bits, use a variant

+ 120 - 32
src/gui/filedetails/ShareDetailsPage.qml

@@ -32,9 +32,11 @@ Page {
 
     signal toggleAllowEditing(bool enable)
     signal toggleAllowResharing(bool enable)
+    signal toggleHideDownload(bool enable)
     signal togglePasswordProtect(bool enable)
     signal toggleExpirationDate(bool enable)
     signal toggleNoteToRecipient(bool enable)
+    signal permissionModeChanged(int permissionMode)
 
     signal setLinkShareLabel(string label)
     signal setExpireDate(var milliseconds) // Since QML ints are only 32 bits, use a variant
@@ -65,16 +67,21 @@ Page {
     readonly property string linkShareLabel: shareModelData.linkShareLabel ?? ""
 
     readonly property bool editingAllowed: shareModelData.editingAllowed
+    readonly property bool hideDownload: shareModelData.hideDownload
     readonly property bool noteEnabled: shareModelData.noteEnabled
     readonly property bool expireDateEnabled: shareModelData.expireDateEnabled
     readonly property bool expireDateEnforced: shareModelData.expireDateEnforced
     readonly property bool passwordProtectEnabled: shareModelData.passwordProtectEnabled
     readonly property bool passwordEnforced: shareModelData.passwordEnforced
-    readonly property bool isSecureFileDropLink: shareModelData.isSecureFileDropLink
+    readonly property bool isSharePermissionChangeInProgress: shareModelData.isSharePermissionChangeInProgress
+    readonly property bool isHideDownloadInProgress: shareModelData.isHideDownloadInProgress
+    readonly property int  currentPermissionMode: shareModelData.currentPermissionMode
 
     readonly property bool isLinkShare: shareModelData.shareType === ShareModel.ShareTypeLink
 
-    property bool waitingForEditingAllowedChange: false
+    readonly property bool isFolderItem: shareModelData.sharedItemType === ShareModel.SharedItemTypeFolder
+    readonly property bool isEncryptedItem: shareModelData.sharedItemType === ShareModel.SharedItemTypeEncryptedFile || shareModelData.sharedItemType === ShareModel.SharedItemTypeEncryptedFolder || shareModelData.sharedItemType === ShareModel.SharedItemTypeEncryptedTopLevelFolder
+
     property bool waitingForNoteEnabledChange: false
     property bool waitingForExpireDateEnabledChange: false
     property bool waitingForPasswordProtectEnabledChange: false
@@ -108,11 +115,6 @@ Page {
         waitingForExpireDateChange = false;
     }
 
-    function resetEditingAllowedField() {
-        editingAllowedMenuItem.checked = editingAllowed;
-        waitingForEditingAllowedChange = false;
-    }
-
     function resetNoteEnabledField() {
         noteEnabledMenuItem.checked = noteEnabled;
         waitingForNoteEnabledChange = false;
@@ -135,8 +137,6 @@ Page {
         resetPasswordField();
         resetLinkShareLabelField();
         resetExpireDateField();
-
-        resetEditingAllowedField();
         resetNoteEnabledField();
         resetExpireDateEnabledField();
         resetPasswordProtectEnabledField();
@@ -154,8 +154,6 @@ Page {
     onPasswordChanged: resetPasswordField()
     onLinkShareLabelChanged: resetLinkShareLabelField()
     onExpireDateChanged: resetExpireDateField()
-
-    onEditingAllowedChanged: resetEditingAllowedField()
     onNoteEnabledChanged: resetNoteEnabledField()
     onExpireDateEnabledChanged: resetExpireDateEnabledField()
     onPasswordProtectEnabledChanged: resetPasswordProtectEnabledField()
@@ -313,34 +311,124 @@ Page {
                 }
             }
 
-            // On these checkables, the clicked() signal is called after
-            // the check state changes.
-            CheckBox {
-                id: editingAllowedMenuItem
+            Loader {
+                Layout.fillWidth: true
+                active: !root.isFolderItem && !root.isEncryptedItem
+                visible: active
+                sourceComponent: CheckBox {
+                    spacing: moreMenu.indicatorSpacing
+                    padding: moreMenu.itemPadding
+                    indicator.width: moreMenu.indicatorItemWidth
+                    indicator.height: moreMenu.indicatorItemWidth
+
+                    checkable: true
+                    checked: root.editingAllowed
+                    text: qsTr("Allow upload and editing")
+                    enabled: !root.isSharePermissionChangeInProgress
+
+                    onClicked: root.toggleAllowEditing(checked)
+
+                    NCBusyIndicator {
+                        anchors.fill: parent
+                        visible: root.isSharePermissionChangeInProgress
+                        running: visible
+                        z: 1
+                    }
+                }
+            }
 
+            Loader {
                 Layout.fillWidth: true
+                active: root.isFolderItem && !root.isEncryptedItem
+                visible: active
+                sourceComponent: ColumnLayout {
+                    id: permissionRadioButtonsLayout
+                    spacing: 0
+                    width: parent.width
 
-                spacing: moreMenu.indicatorSpacing
-                padding: moreMenu.itemPadding
-                indicator.width: moreMenu.indicatorItemWidth
-                indicator.height: moreMenu.indicatorItemWidth
+                    ButtonGroup {
+                        id: permissionModeRadioButtonsGroup
+                    }
 
-                checkable: true
-                checked: root.editingAllowed
-                text: qsTr("Allow editing")
-                enabled: !root.waitingForEditingAllowedChange
-                visible: !root.isSecureFileDropLink
+                    NCRadioButton {
+                        readonly property int permissionMode: ShareModel.ModeViewOnly
+                        Layout.fillWidth: true
+                        ButtonGroup.group: permissionModeRadioButtonsGroup
+                        enabled: !root.isSharePermissionChangeInProgress
+                        checked: root.currentPermissionMode === permissionMode
+                        text: qsTr("View only")
+                        indicatorItemWidth: moreMenu.indicatorItemWidth
+                        indicatorItemHeight: moreMenu.indicatorItemWidth
+                        spacing: moreMenu.indicatorSpacing
+                        padding: moreMenu.itemPadding
+                        onClicked: root.permissionModeChanged(permissionMode)
+                    }
 
-                onClicked: {
-                    root.toggleAllowEditing(checked);
-                    root.waitingForEditingAllowedChange = true;
+                    NCRadioButton {
+                        readonly property int permissionMode: ShareModel.ModeUploadAndEditing
+                        Layout.fillWidth: true
+                        ButtonGroup.group: permissionModeRadioButtonsGroup
+                        enabled: !root.isSharePermissionChangeInProgress
+                        checked: root.currentPermissionMode === permissionMode
+                        text: qsTr("Allow upload and editing")
+                        indicatorItemWidth: moreMenu.indicatorItemWidth
+                        indicatorItemHeight: moreMenu.indicatorItemWidth
+                        spacing: moreMenu.indicatorSpacing
+                        padding: moreMenu.itemPadding
+                        onClicked: root.permissionModeChanged(permissionMode)
+
+                        NCBusyIndicator {
+                            anchors.fill: parent
+                            visible: root.isSharePermissionChangeInProgress
+                            running: visible
+                            z: 1
+                        }
+                    }
+
+                    NCRadioButton {
+                        readonly property int permissionMode: ShareModel.ModeFileDropOnly
+                        Layout.fillWidth: true
+                        ButtonGroup.group: permissionModeRadioButtonsGroup
+                        enabled: !root.isSharePermissionChangeInProgress
+                        checked: root.currentPermissionMode === permissionMode
+                        text: qsTr("File drop (upload only)")
+                        indicatorItemWidth: moreMenu.indicatorItemWidth
+                        indicatorItemHeight: moreMenu.indicatorItemWidth
+                        spacing: moreMenu.indicatorSpacing
+                        padding: moreMenu.itemPadding
+                        onClicked: root.permissionModeChanged(permissionMode)
+                    }
                 }
+            }
 
-                NCBusyIndicator {
-                    anchors.fill: parent
-                    visible: root.waitingForEditingAllowedChange
-                    running: visible
-                    z: 1
+            Loader {
+                Layout.fillWidth: true
+
+                active: root.isLinkShare
+                visible: active
+                sourceComponent: ColumnLayout {
+                    CheckBox {
+                        id: hideDownloadEnabledMenuItem
+
+                        anchors.left: parent.left
+                        anchors.right: parent.right
+
+                        spacing: moreMenu.indicatorSpacing
+                        padding: moreMenu.itemPadding
+                        indicator.width: moreMenu.indicatorItemWidth
+                        indicator.height: moreMenu.indicatorItemWidth
+                        checked: root.hideDownload
+                        text: qsTr("Hide download")
+                        enabled: !root.isHideDownloadInProgress
+                        onClicked: root.toggleHideDownload(checked);
+
+                        NCBusyIndicator {
+                            anchors.fill: parent
+                            visible: root.isHideDownloadInProgress
+                            running: visible
+                            z: 1
+                        }
+                    }
                 }
             }
 

+ 2 - 0
src/gui/filedetails/ShareView.qml

@@ -241,9 +241,11 @@ ColumnLayout {
 
                     onToggleAllowEditing: shareModel.toggleShareAllowEditingFromQml(model.share, enable)
                     onToggleAllowResharing: shareModel.toggleShareAllowResharingFromQml(model.share, enable)
+                    onToggleHideDownload: shareModel.toggleHideDownloadFromQml(model.share, enable)
                     onTogglePasswordProtect: shareModel.toggleSharePasswordProtectFromQml(model.share, enable)
                     onToggleExpirationDate: shareModel.toggleShareExpirationDateFromQml(model.share, enable)
                     onToggleNoteToRecipient: shareModel.toggleShareNoteToRecipientFromQml(model.share, enable)
+                    onPermissionModeChanged: shareModel.changePermissionModeFromQml(model.share, permissionMode)
 
                     onSetLinkShareLabel: shareModel.setLinkShareLabelFromQml(model.share, label)
                     onSetExpireDate: shareModel.setShareExpireDateFromQml(model.share, milliseconds)

+ 149 - 19
src/gui/filedetails/sharemodel.cpp

@@ -81,7 +81,11 @@ QHash<int, QByteArray> ShareModel::roleNames() const
     roles[PasswordRole] = "password";
     roles[PasswordEnforcedRole] = "passwordEnforced";
     roles[EditingAllowedRole] = "editingAllowed";
-    roles[IsSecureFileDropLinkRole] = "isSecureFileDropLink";
+    roles[CurrentPermissionModeRole] = "currentPermissionMode";
+    roles[SharedItemTypeRole] = "sharedItemType";
+    roles[IsSharePermissionsChangeInProgress] = "isSharePermissionChangeInProgress";
+    roles[HideDownloadEnabledRole] = "hideDownload";
+    roles[IsHideDownloadEnabledChangeInProgress] = "isHideDownloadInProgress";
 
     return roles;
 }
@@ -107,6 +111,8 @@ QVariant ShareModel::data(const QModelIndex &index, const int role) const
             return linkShare->getLabel();
         case NoteEnabledRole:
             return !linkShare->getNote().isEmpty();
+        case HideDownloadEnabledRole:
+            return linkShare->getHideDownload();
         case NoteRole:
             return linkShare->getNote();
         case ExpireDateEnabledRole:
@@ -151,8 +157,22 @@ QVariant ShareModel::data(const QModelIndex &index, const int role) const
         return expireDateEnforcedForShare(share);
     case EnforcedMaximumExpireDateRole:
         return enforcedMaxExpireDateForShare(share);
-    case IsSecureFileDropLinkRole:
-        return _isSecureFileDropSupportedFolder && share->getPermissions().testFlag(OCC::SharePermission::SharePermissionCreate);
+    case CurrentPermissionModeRole: {
+        if (share->getPermissions() == OCC::SharePermission::SharePermissionCreate) {
+            return QVariant::fromValue(SharePermissionsMode::ModeFileDropOnly);
+        } else if ((share->getPermissions() & SharePermissionRead) && (share->getPermissions() & SharePermissionCreate)
+                   && (share->getPermissions() & SharePermissionUpdate) && (share->getPermissions() & SharePermissionDelete)) {
+            return QVariant::fromValue(SharePermissionsMode::ModeUploadAndEditing);
+        } else {
+            return QVariant::fromValue(SharePermissionsMode::ModeViewOnly);
+        }
+    }
+    case SharedItemTypeRole:
+        return static_cast<int>(_sharedItemType);
+    case IsSharePermissionsChangeInProgress:
+        return _sharePermissionsChangeInProgress;
+    case IsHideDownloadEnabledChangeInProgress:
+        return _hideDownloadEnabledChangeInProgress;
     case PasswordProtectEnabledRole:
         return share->isPasswordSet();
     case PasswordRole:
@@ -250,9 +270,15 @@ void ShareModel::updateData()
 
     _numericFileId = fileRecord.numericFileId();
 
-    _isEncryptedItem = fileRecord.isE2eEncrypted();
-    _isSecureFileDropSupportedFolder =
-        fileRecord.isE2eEncrypted() && fileRecord.e2eMangledName().isEmpty() && _accountState->account()->secureFileDropSupported();
+    if (fileRecord.isDirectory()) {
+        if (fileRecord.isE2eEncrypted()) {
+            _sharedItemType = fileRecord.e2eMangledName().isEmpty() ? SharedItemType::SharedItemTypeEncryptedTopLevelFolder : SharedItemType::SharedItemTypeEncryptedFolder;
+        } else {
+            _sharedItemType = SharedItemType::SharedItemTypeFolder;
+        }
+    } else {
+        _sharedItemType = fileRecord.isE2eEncrypted() ? SharedItemType::SharedItemTypeEncryptedFile : SharedItemType::SharedItemTypeFile;
+    }
 
     // Will get added when shares are fetched if no link shares are fetched
     _placeholderLinkShare.reset(new Share(_accountState->account(),
@@ -386,9 +412,9 @@ void ShareModel::handleSecureFileDropLinkShare()
 
 void ShareModel::handleLinkShare()
 {
-    if (!_isEncryptedItem) {
+    if (!isEncryptedItem()) {
         handlePlaceholderLinkShare();
-    } else if (_isSecureFileDropSupportedFolder) {
+    } else if (isSecureFileDropSupportedFolder()) {
         handleSecureFileDropLinkShare();
     }
 }
@@ -456,7 +482,7 @@ void ShareModel::setupInternalLinkShare()
         _accountState->account().isNull() ||
         _localPath.isEmpty() ||
         _privateLinkUrl.isEmpty() ||
-        _isEncryptedItem) {
+        isEncryptedItem()) {
         return;
     }
 
@@ -466,6 +492,30 @@ void ShareModel::setupInternalLinkShare()
     Q_EMIT internalLinkReady();
 }
 
+void ShareModel::setSharePermissionChangeInProgress(const QString &shareId, const bool isInProgress)
+{
+    if (isInProgress == _sharePermissionsChangeInProgress) {
+        return;
+    }
+
+    _sharePermissionsChangeInProgress = isInProgress;
+    
+    const auto shareIndex = _shareIdIndexHash.value(shareId);
+    Q_EMIT dataChanged(shareIndex, shareIndex, {IsSharePermissionsChangeInProgress});
+}
+
+void ShareModel::setHideDownloadEnabledChangeInProgress(const QString &shareId, const bool isInProgress)
+{
+    if (isInProgress == _hideDownloadEnabledChangeInProgress) {
+        return;
+    }
+
+    _hideDownloadEnabledChangeInProgress = isInProgress;
+
+    const auto shareIndex = _shareIdIndexHash.value(shareId);
+    Q_EMIT dataChanged(shareIndex, shareIndex, {IsHideDownloadEnabledChangeInProgress});
+}
+
 void ShareModel::slotAddShare(const SharePtr &share)
 {
     if (share.isNull()) {
@@ -515,6 +565,7 @@ void ShareModel::slotAddShare(const SharePtr &share)
         connect(linkShare.data(), &LinkShare::nameSet, this, [this, shareId]{ slotShareNameSet(shareId); });
         connect(linkShare.data(), &LinkShare::labelSet, this, [this, shareId]{ slotShareLabelSet(shareId); });
         connect(linkShare.data(), &LinkShare::expireDateSet, this, [this, shareId]{ slotShareExpireDateSet(shareId); });
+        connect(linkShare.data(), &LinkShare::hideDownloadSet, this, [this, shareId] { slotHideDownloadSet(shareId); });
     } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
         connect(userGroupShare.data(), &UserGroupShare::noteSet, this, [this, shareId]{ slotShareNoteSet(shareId); });
         connect(userGroupShare.data(), &UserGroupShare::expireDateSet, this, [this, shareId]{ slotShareExpireDateSet(shareId); });
@@ -582,7 +633,7 @@ QString ShareModel::displayStringForShare(const SharePtr &share) const
 {
     if (const auto linkShare = share.objectCast<LinkShare>()) {
 
-        const auto isSecureFileDropShare = _isSecureFileDropSupportedFolder && linkShare->getPermissions().testFlag(OCC::SharePermission::SharePermissionCreate);
+        const auto isSecureFileDropShare = isSecureFileDropSupportedFolder() && linkShare->getPermissions().testFlag(OCC::SharePermission::SharePermissionCreate);
 
         const auto displayString = isSecureFileDropShare ? tr("Secure file drop link") : tr("Share link");
 
@@ -708,7 +759,8 @@ void ShareModel::slotSharePermissionsSet(const QString &shareId)
 
     const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
     const auto shareModelIndex = index(sharePersistentModelIndex.row());
-    Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { EditingAllowedRole });
+    Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { EditingAllowedRole, CurrentPermissionModeRole });
+    setSharePermissionChangeInProgress(shareId, false);
 }
 
 void ShareModel::slotSharePasswordSet(const QString &shareId)
@@ -722,6 +774,18 @@ void ShareModel::slotSharePasswordSet(const QString &shareId)
     Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { PasswordProtectEnabledRole, PasswordRole });
 }
 
+void ShareModel::slotHideDownloadSet(const QString &shareId)
+{
+    if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+        return;
+    }
+
+    const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+    const auto shareModelIndex = index(sharePersistentModelIndex.row());
+    Q_EMIT dataChanged(shareModelIndex, shareModelIndex, {HideDownloadEnabledRole});
+    setHideDownloadEnabledChangeInProgress(shareId, false);
+}
+
 void ShareModel::slotShareNoteSet(const QString &shareId)
 {
     if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
@@ -768,37 +832,56 @@ void ShareModel::slotShareExpireDateSet(const QString &shareId)
 
 // ----------------------- Shares modification slots ----------------------- //
 
-void ShareModel::toggleShareAllowEditing(const SharePtr &share, const bool enable) const
+void ShareModel::toggleShareAllowEditing(const SharePtr &share, const bool enable)
 {
-    if (share.isNull()) {
+    if (share.isNull() || _sharePermissionsChangeInProgress) {
         return;
     }
 
     auto permissions = share->getPermissions();
     enable ? permissions |= SharePermissionUpdate : permissions &= ~SharePermissionUpdate;
 
+    setSharePermissionChangeInProgress(share->getId(), true);
     share->setPermissions(permissions);
 }
 
-void ShareModel::toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) const
+void ShareModel::toggleShareAllowEditingFromQml(const QVariant &share, const bool enable)
 {
     const auto ptr = share.value<SharePtr>();
     toggleShareAllowEditing(ptr, enable);
 }
 
-void ShareModel::toggleShareAllowResharing(const SharePtr &share, const bool enable) const
+void ShareModel::toggleShareAllowResharing(const SharePtr &share, const bool enable)
 {
-    if (share.isNull()) {
+    if (share.isNull() || _sharePermissionsChangeInProgress) {
         return;
     }
 
     auto permissions = share->getPermissions();
     enable ? permissions |= SharePermissionShare : permissions &= ~SharePermissionShare;
 
+    setSharePermissionChangeInProgress(share->getId(), true);
     share->setPermissions(permissions);
 }
 
-void ShareModel::toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) const
+void ShareModel::toggleHideDownloadFromQml(const QVariant &share, const bool enable)
+{
+    const auto sharePtr = share.value<SharePtr>();
+    if (sharePtr.isNull() || _hideDownloadEnabledChangeInProgress) {
+        return;
+    }
+
+    const auto linkShare = sharePtr.objectCast<LinkShare>();
+
+    if (linkShare.isNull()) {
+        return;
+    }
+
+    setHideDownloadEnabledChangeInProgress(linkShare->getId(), true);
+    linkShare->setHideDownload(enable);
+}
+
+void ShareModel::toggleShareAllowResharingFromQml(const QVariant &share, const bool enable)
 {
     const auto ptr = share.value<SharePtr>();
     toggleShareAllowResharing(ptr, enable);
@@ -867,6 +950,42 @@ void ShareModel::toggleShareNoteToRecipientFromQml(const QVariant &share, const
     toggleShareNoteToRecipient(ptr, enable);
 }
 
+void ShareModel::changePermissionModeFromQml(const QVariant &share, const SharePermissionsMode permissionMode)
+{
+    const auto sharePtr = share.value<SharePtr>();
+    if (sharePtr.isNull() || _sharePermissionsChangeInProgress) {
+        return;
+    }
+
+    const auto shareIndex = _shareIdIndexHash.value(sharePtr->getId());
+
+    if (!checkIndex(shareIndex, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid)) {
+        qCWarning(lcShareModel) << "Can't change permission mode for:" << sharePtr->getId() << ", invalid share index: " << shareIndex;
+        return;
+    }
+
+    const auto currentPermissionMode = shareIndex.data(ShareModel::CurrentPermissionModeRole).value<SharePermissionsMode>();
+
+    if (currentPermissionMode == permissionMode) {
+        return;
+    }
+
+    SharePermissions perm = SharePermissionRead;
+    switch (permissionMode) {
+    case SharePermissionsMode::ModeViewOnly:
+        break;
+    case SharePermissionsMode::ModeUploadAndEditing:
+        perm |= SharePermissionCreate | SharePermissionUpdate | SharePermissionDelete;
+        break;
+    case SharePermissionsMode::ModeFileDropOnly:
+        perm = SharePermissionCreate;
+        break;
+    }
+
+    setSharePermissionChangeInProgress(sharePtr->getId(), true);
+    sharePtr->setPermissions(perm);
+}
+
 void ShareModel::setLinkShareLabel(const QSharedPointer<LinkShare> &linkShare, const QString &label) const
 {
     if (linkShare.isNull()) {
@@ -944,7 +1063,7 @@ void ShareModel::setShareNoteFromQml(const QVariant &share, const QString &note)
 
 void ShareModel::createNewLinkShare() const
 {
-    if (_isEncryptedItem && !_isSecureFileDropSupportedFolder) {
+    if (isEncryptedItem() && !isSecureFileDropSupportedFolder()) {
         qCWarning(lcShareModel) << "Attempt to create a link share for non-root encrypted folder or a file.";
         return;
     }
@@ -952,7 +1071,7 @@ void ShareModel::createNewLinkShare() const
     if (_manager) {
         const auto askOptionalPassword = _accountState->account()->capabilities().sharePublicLinkAskOptionalPassword();
         const auto password = askOptionalPassword ? createRandomPassword() : QString();
-        if (_isSecureFileDropSupportedFolder) {
+        if (isSecureFileDropSupportedFolder()) {
             _manager->createSecureFileDropShare(_sharePath, {}, password);
             return;
         }
@@ -1089,6 +1208,17 @@ bool ShareModel::validCapabilities() const
             _accountState->account()->capabilities().isValid();
 }
 
+bool ShareModel::isSecureFileDropSupportedFolder() const
+{
+    return _sharedItemType == SharedItemType::SharedItemTypeEncryptedTopLevelFolder && _accountState->account()->secureFileDropSupported();
+}
+
+bool ShareModel::isEncryptedItem() const
+{
+    return _sharedItemType == SharedItemType::SharedItemTypeEncryptedFile || _sharedItemType == SharedItemType::SharedItemTypeEncryptedFolder
+        || _sharedItemType == SharedItemType::SharedItemTypeEncryptedTopLevelFolder;
+}
+
 bool ShareModel::sharingEnabled() const
 {
     return validCapabilities() &&

+ 36 - 7
src/gui/filedetails/sharemodel.h

@@ -57,7 +57,11 @@ public:
         PasswordRole,
         PasswordEnforcedRole,
         EditingAllowedRole,
-        IsSecureFileDropLinkRole,
+        CurrentPermissionModeRole,
+        SharedItemTypeRole,
+        IsSharePermissionsChangeInProgress,
+        HideDownloadEnabledRole,
+        IsHideDownloadEnabledChangeInProgress,
     };
     Q_ENUM(Roles)
 
@@ -79,6 +83,23 @@ public:
         ShareTypeSecureFileDropPlaceholderLink = Share::TypeSecureFileDropPlaceholderLink,
     };
     Q_ENUM(ShareType);
+    
+    enum class SharedItemType {
+        SharedItemTypeUndefined = -1,
+        SharedItemTypeFile,
+        SharedItemTypeFolder,
+        SharedItemTypeEncryptedFile,
+        SharedItemTypeEncryptedFolder,
+        SharedItemTypeEncryptedTopLevelFolder,
+    };
+    Q_ENUM(SharedItemType);
+    
+    enum class SharePermissionsMode {
+        ModeViewOnly,
+        ModeUploadAndEditing,
+        ModeFileDropOnly,
+    };
+    Q_ENUM(SharePermissionsMode);
 
     explicit ShareModel(QObject *parent = nullptr);
 
@@ -135,16 +156,18 @@ public slots:
     void deleteShare(const OCC::SharePtr &share) const;
     void deleteShareFromQml(const QVariant &share) const;
 
-    void toggleShareAllowEditing(const OCC::SharePtr &share, const bool enable) const;
-    void toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) const;
-    void toggleShareAllowResharing(const OCC::SharePtr &share, const bool enable) const;
-    void toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) const;
+    void toggleHideDownloadFromQml(const QVariant &share, const bool enable);
+    void toggleShareAllowEditing(const OCC::SharePtr &share, const bool enable);
+    void toggleShareAllowEditingFromQml(const QVariant &share, const bool enable);
+    void toggleShareAllowResharing(const OCC::SharePtr &share, const bool enable);
+    void toggleShareAllowResharingFromQml(const QVariant &share, const bool enable);
     void toggleSharePasswordProtect(const OCC::SharePtr &share, const bool enable);
     void toggleSharePasswordProtectFromQml(const QVariant &share, const bool enable);
     void toggleShareExpirationDate(const OCC::SharePtr &share, const bool enable) const;
     void toggleShareExpirationDateFromQml(const QVariant &share, const bool enable) const;
     void toggleShareNoteToRecipient(const OCC::SharePtr &share, const bool enable) const;
     void toggleShareNoteToRecipientFromQml(const QVariant &share, const bool enable) const;
+    void changePermissionModeFromQml(const QVariant &share, const SharePermissionsMode permissionMode);
 
     void setLinkShareLabel(const QSharedPointer<OCC::LinkShare> &linkShare, const QString &label) const;
     void setLinkShareLabelFromQml(const QVariant &linkShare, const QString &label) const;
@@ -164,6 +187,8 @@ private slots:
     void handleSecureFileDropLinkShare();
     void handleLinkShare();
     void setupInternalLinkShare();
+    void setSharePermissionChangeInProgress(const QString &shareId, const bool isInProgress);
+    void setHideDownloadEnabledChangeInProgress(const QString &shareId, const bool isInProgress);
 
     void slotPropfindReceived(const QVariantMap &result);
     void slotServerError(const int code, const QString &message);
@@ -176,6 +201,7 @@ private slots:
     void slotSharePermissionsSet(const QString &shareId);
     void slotSharePasswordSet(const QString &shareId);
     void slotShareNoteSet(const QString &shareId);
+    void slotHideDownloadSet(const QString &shareId);
     void slotShareNameSet(const QString &shareId);
     void slotShareLabelSet(const QString &shareId);
     void slotShareExpireDateSet(const QString &shareId);
@@ -187,9 +213,13 @@ private:
     [[nodiscard]] long long enforcedMaxExpireDateForShare(const SharePtr &share) const;
     [[nodiscard]] bool expireDateEnforcedForShare(const SharePtr &share) const;
     [[nodiscard]] bool validCapabilities() const;
+    [[nodiscard]] bool isSecureFileDropSupportedFolder() const;
+    [[nodiscard]] bool isEncryptedItem() const;
 
     bool _fetchOngoing = false;
     bool _hasInitialShareFetchCompleted = false;
+    bool _sharePermissionsChangeInProgress = false;
+    bool _hideDownloadEnabledChangeInProgress = false;
     SharePtr _placeholderLinkShare;
     SharePtr _internalLinkShare;
     SharePtr _secureFileDropPlaceholderLinkShare;
@@ -201,8 +231,7 @@ private:
     QString _sharePath;
     SharePermissions _maxSharingPermissions;
     QByteArray _numericFileId;
-    bool _isEncryptedItem = false;
-    bool _isSecureFileDropSupportedFolder = false;
+    SharedItemType _sharedItemType = SharedItemType::SharedItemTypeUndefined;
     SyncJournalFileLockInfo _filelockState;
     QString _privateLinkUrl;
 

+ 12 - 0
src/gui/ocssharejob.cpp

@@ -134,6 +134,18 @@ void OcsShareJob::setLabel(const QString &shareId, const QString &label)
     start();
 }
 
+void OcsShareJob::setHideDownload(const QString &shareId, const bool hideDownload)
+{
+    appendPath(shareId);
+    setVerb("PUT");
+
+    const auto value = QString::fromLatin1(hideDownload ? QByteArrayLiteral("true") : QByteArrayLiteral("false"));
+    addParam(QStringLiteral("hideDownload"), value);
+    _value = hideDownload;
+
+    start();
+}
+
 void OcsShareJob::createLinkShare(const QString &path,
     const QString &name,
     const QString &password)

+ 5 - 0
src/gui/ocssharejob.h

@@ -102,6 +102,11 @@ public:
      */
     void setLabel(const QString &shareId, const QString &label);
 
+    /**
+     * Set share hideDownload flag
+     */
+    void setHideDownload(const QString &shareId, const bool hideDownload);
+
     /**
      * Create a new link share
      *

+ 25 - 2
src/gui/sharemanager.cpp

@@ -203,7 +203,8 @@ LinkShare::LinkShare(AccountPtr account,
     const QUrl &url,
     const QDate &expireDate,
     const QString &note,
-    const QString &label)
+    const QString &label,
+    const bool hideDownload)
     : Share(account, id, uidowner, ownerDisplayName, path, Share::TypeLink, isPasswordSet, permissions)
     , _name(name)
     , _token(token)
@@ -211,6 +212,7 @@ LinkShare::LinkShare(AccountPtr account,
     , _expireDate(expireDate)
     , _url(url)
     , _label(label)
+    , _hideDownload(hideDownload)
 {
 }
 
@@ -239,6 +241,11 @@ QString LinkShare::getLabel() const
     return _label;
 }
 
+bool LinkShare::getHideDownload() const
+{
+    return _hideDownload;
+}
+
 void LinkShare::setName(const QString &name)
 {
     createShareJob(&LinkShare::slotNameSet)->setName(getId(), name);
@@ -270,6 +277,11 @@ void LinkShare::setLabel(const QString &label)
     createShareJob(&LinkShare::slotLabelSet)->setLabel(getId(), label);
 }
 
+void LinkShare::setHideDownload(const bool hideDownload)
+{
+    createShareJob(&LinkShare::slotHideDownloadSet)->setHideDownload(getId(), hideDownload);
+}
+
 template <typename LinkShareSlot>
 OcsShareJob *LinkShare::createShareJob(const LinkShareSlot slotFunction) {
     auto *job = new OcsShareJob(_account);
@@ -308,6 +320,16 @@ void LinkShare::slotLabelSet(const QJsonDocument &, const QVariant &label)
     }
 }
 
+void LinkShare::slotHideDownloadSet(const QJsonDocument &jsonDoc, const QVariant &hideDownload)
+{
+    Q_UNUSED(jsonDoc);
+    if (!hideDownload.isValid()) {
+        return;
+    }
+    _hideDownload = hideDownload.toBool();
+    emit hideDownloadSet();
+}
+
 UserGroupShare::UserGroupShare(AccountPtr account,
     const QString &id,
     const QString &owner,
@@ -583,7 +605,8 @@ QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QJsonObject &data)
         url,
         expireDate,
         note,
-        data.value("label").toString()));
+        data.value("label").toString(),
+        data.value("hide_download").toInt() == 1));
 }
 
 SharePtr ShareManager::parseShare(const QJsonObject &data) const

+ 17 - 1
src/gui/sharemanager.h

@@ -132,6 +132,7 @@ signals:
     void shareDeleted();
     void serverError(int code, const QString &message);
     void passwordSet();
+    void hideDownloadSet();
     void passwordSetError(int statusCode, const QString &message);    
 
 public slots:
@@ -197,6 +198,7 @@ class LinkShare : public Share
     Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameSet)
     Q_PROPERTY(QString note READ getNote WRITE setNote NOTIFY noteSet)
     Q_PROPERTY(QString label READ getLabel WRITE setLabel NOTIFY labelSet)
+    Q_PROPERTY(bool hideDownload READ getHideDownload WRITE setHideDownload NOTIFY hideDownloadSet)
     Q_PROPERTY(QDate expireDate READ getExpireDate WRITE setExpireDate NOTIFY expireDateSet)
     Q_PROPERTY(QString token READ getToken CONSTANT)
 
@@ -213,7 +215,8 @@ public:
         const QUrl &url,
         const QDate &expireDate,
         const QString &note,
-        const QString &label);
+        const QString &label,
+        const bool hideDownload);
 
     /*
      * Get the share link
@@ -250,6 +253,11 @@ public:
      */
     [[nodiscard]] QString getLabel() const;
 
+    /*
+     * Returns if the link share's hideDownload is true or false
+     */
+    [[nodiscard]] bool getHideDownload() const;
+
     /*
      * Returns the token of the link share.
      */
@@ -291,18 +299,25 @@ public slots:
      * Set the label of the share link.
      */
     void setLabel(const QString &label);
+
+    /*
+     * Set the hideDownload flag of the share link.
+     */
+    void setHideDownload(const bool hideDownload);
     
 signals:
     void expireDateSet();
     void noteSet();
     void nameSet();
     void labelSet();
+    void hideDownloadSet();
 
 private slots:
     void slotNoteSet(const QJsonDocument &, const QVariant &value);
     void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
     void slotNameSet(const QJsonDocument &, const QVariant &value);
     void slotLabelSet(const QJsonDocument &, const QVariant &value);
+    void slotHideDownloadSet(const QJsonDocument &jsonDoc, const QVariant &hideDownload);
 
 private:
     QString _name;
@@ -311,6 +326,7 @@ private:
     QDate _expireDate;
     QUrl _url;
     QString _label;
+    bool _hideDownload = false;
 };
 
 class UserGroupShare : public Share

+ 5 - 0
theme/Style/Style.qml

@@ -122,6 +122,11 @@ QtObject {
     readonly property int unifiedSearchResultSectionItemVerticalPadding: 8
     readonly property int unifiedSearchResultNothingFoundHorizontalMargin: 10
 
+    readonly property int radioButtonCustomMarginLeftInner: 4
+    readonly property int radioButtonCustomMarginLeftOuter: 5
+    readonly property int radioButtonCustomRadius: 9
+    readonly property int radioButtonIndicatorSize: 16
+
     readonly property var fontMetrics: FontMetrics {}
 
     readonly property int activityContentSpace: 4