Add a file details window/page, QMLify file sharing
authorClaudio Cambra <claudio.cambra@nextcloud.com>
Mon, 25 Jul 2022 16:57:18 +0000 (18:57 +0200)
committerClaudio Cambra <claudio.cambra@nextcloud.com>
Mon, 31 Oct 2022 17:06:03 +0000 (18:06 +0100)
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
52 files changed:
resources.qrc
src/gui/CMakeLists.txt
src/gui/application.cpp
src/gui/fileactivitylistmodel.cpp
src/gui/fileactivitylistmodel.h
src/gui/filedetails/FileActivityView.qml [new file with mode: 0644]
src/gui/filedetails/FileDetailsPage.qml [new file with mode: 0644]
src/gui/filedetails/FileDetailsWindow.qml [new file with mode: 0644]
src/gui/filedetails/NCInputTextEdit.qml [new file with mode: 0644]
src/gui/filedetails/NCInputTextField.qml [new file with mode: 0644]
src/gui/filedetails/NCTabButton.qml [new file with mode: 0644]
src/gui/filedetails/ShareDelegate.qml [new file with mode: 0644]
src/gui/filedetails/ShareView.qml [new file with mode: 0644]
src/gui/filedetails/ShareeDelegate.qml [new file with mode: 0644]
src/gui/filedetails/ShareeSearchField.qml [new file with mode: 0644]
src/gui/filedetails/filedetails.cpp [new file with mode: 0644]
src/gui/filedetails/filedetails.h [new file with mode: 0644]
src/gui/filedetails/shareemodel.cpp [new file with mode: 0644]
src/gui/filedetails/shareemodel.h [new file with mode: 0644]
src/gui/filedetails/sharemodel.cpp [new file with mode: 0644]
src/gui/filedetails/sharemodel.h [new file with mode: 0644]
src/gui/filedetails/sortedsharemodel.cpp [new file with mode: 0644]
src/gui/filedetails/sortedsharemodel.h [new file with mode: 0644]
src/gui/folderman.cpp
src/gui/owncloudgui.cpp
src/gui/owncloudgui.h
src/gui/sharedialog.cpp [deleted file]
src/gui/sharedialog.h [deleted file]
src/gui/sharedialog.ui [deleted file]
src/gui/sharee.cpp
src/gui/sharee.h
src/gui/sharelinkwidget.cpp [deleted file]
src/gui/sharelinkwidget.h [deleted file]
src/gui/sharelinkwidget.ui [deleted file]
src/gui/sharemanager.cpp
src/gui/sharemanager.h
src/gui/shareusergroupwidget.cpp [deleted file]
src/gui/shareusergroupwidget.h [deleted file]
src/gui/shareusergroupwidget.ui [deleted file]
src/gui/socketapi/socketapi.cpp
src/gui/socketapi/socketapi.h
src/gui/systray.cpp
src/gui/systray.h
src/gui/tray/ActivityItem.qml
src/gui/tray/ActivityItemContent.qml
src/gui/tray/ActivityList.qml
src/gui/tray/FileActivityDialog.qml [deleted file]
src/gui/tray/Window.qml
src/gui/tray/activitylistmodel.cpp
src/gui/tray/activitylistmodel.h
src/gui/tray/asyncimageresponse.cpp
src/gui/tray/asyncimageresponse.h

index e49b6ec482102652ca9bdc8bfaed4026763874ab..3132330e26cc77375ddf7b6002d6df97ba70114d 100644 (file)
@@ -7,17 +7,24 @@
         <file>src/gui/PredefinedStatusButton.qml</file>
         <file>src/gui/BasicComboBox.qml</file>
         <file>src/gui/ErrorBox.qml</file>
+        <file>src/gui/filedetails/FileActivityView.qml</file>
+        <file>src/gui/filedetails/FileDetailsPage.qml</file>
+        <file>src/gui/filedetails/FileDetailsWindow.qml</file>
+        <file>src/gui/filedetails/NCInputTextEdit.qml</file>
+        <file>src/gui/filedetails/NCInputTextField.qml</file>
+        <file>src/gui/filedetails/NCTabButton.qml</file>
+        <file>src/gui/filedetails/ShareeDelegate.qml</file>
+        <file>src/gui/filedetails/ShareDelegate.qml</file>
+        <file>src/gui/filedetails/ShareeSearchField.qml</file>
+        <file>src/gui/filedetails/ShareView.qml</file>
         <file>src/gui/tray/Window.qml</file>
         <file>src/gui/tray/UserLine.qml</file>
         <file>src/gui/tray/HeaderButton.qml</file>
         <file>src/gui/tray/SyncStatus.qml</file>
-        <file>theme/Style/Style.qml</file>
-        <file>theme/Style/qmldir</file>
         <file>src/gui/tray/ActivityActionButton.qml</file>
         <file>src/gui/tray/ActivityItem.qml</file>
         <file>src/gui/tray/AutoSizingMenu.qml</file>
         <file>src/gui/tray/ActivityList.qml</file>
-        <file>src/gui/tray/FileActivityDialog.qml</file>
         <file>src/gui/tray/UnifiedSearchInputContainer.qml</file>
         <file>src/gui/tray/UnifiedSearchResultFetchMoreTrigger.qml</file>
         <file>src/gui/tray/UnifiedSearchResultItem.qml</file>
@@ -39,5 +46,7 @@
         <file>src/gui/tray/EditFileLocallyLoadingDialog.qml</file>
         <file>src/gui/tray/NCBusyIndicator.qml</file>
         <file>src/gui/tray/NCToolTip.qml</file>
+        <file>theme/Style/Style.qml</file>
+        <file>theme/Style/qmldir</file>
     </qresource>
 </RCC>
index 3a845531e5db05d5781b3fcb8c87da94d91366c5..77d875b854854a7bdd7c14cdb0877a1f50e06a68 100644 (file)
@@ -39,9 +39,6 @@ set(client_UI_SRCS
     ignorelisttablewidget.ui
     networksettings.ui
     settingsdialog.ui
-    sharedialog.ui
-    sharelinkwidget.ui
-    shareusergroupwidget.ui
     shareuserline.ui
     sslerrordialog.ui
     addcertificatedialog.ui
@@ -139,14 +136,8 @@ set(client_SRCS
     selectivesyncdialog.cpp
     settingsdialog.h
     settingsdialog.cpp
-    sharedialog.h
-    sharedialog.cpp
-    sharelinkwidget.h
-    sharelinkwidget.cpp
     sharemanager.h
     sharemanager.cpp
-    shareusergroupwidget.h
-    shareusergroupwidget.cpp
     profilepagewidget.h
     profilepagewidget.cpp
     sharee.h
@@ -193,6 +184,14 @@ set(client_SRCS
     emojimodel.cpp
     fileactivitylistmodel.h
     fileactivitylistmodel.cpp
+    filedetails/filedetails.h
+    filedetails/filedetails.cpp
+    filedetails/sharemodel.h
+    filedetails/sharemodel.cpp
+    filedetails/shareemodel.h
+    filedetails/shareemodel.cpp
+    filedetails/sortedsharemodel.h
+    filedetails/sortedsharemodel.cpp
     tray/svgimageprovider.h
     tray/svgimageprovider.cpp
     tray/syncstatussummary.h
index fafc33a24da59e6bf0e4ccd78964637eae01cc13..9dd436dc6ee09c2ecdb8e04df67864307f52a6d8 100644 (file)
@@ -32,7 +32,6 @@
 #include "sslerrordialog.h"
 #include "theme.h"
 #include "clientproxy.h"
-#include "sharedialog.h"
 #include "accountmanager.h"
 #include "creds/abstractcredentials.h"
 #include "pushnotifications.h"
@@ -379,7 +378,7 @@ Application::Application(int &argc, char **argv)
         _gui.data(), &ownCloudGui::slotShowShareDialog);
 
     connect(FolderMan::instance()->socketApi(), &SocketApi::fileActivityCommandReceived,
-        Systray::instance(), &Systray::showFileActivityDialog);
+        _gui.data(), &ownCloudGui::slotShowFileActivityDialog);
 
     // startup procedure.
     connect(&_checkConnectionTimer, &QTimer::timeout, this, &Application::slotCheckConnection);
index 5fadec9816ad9055177fdb4f7600a732485ee66c..2dde4924f46c77f20712b61cc625e6d5a4c69e8a 100644 (file)
@@ -24,23 +24,46 @@ FileActivityListModel::FileActivityListModel(QObject *parent)
     : ActivityListModel(nullptr, parent)
 {
     setDisplayActions(false);
+    connect(this, &FileActivityListModel::accountStateChanged, this, &FileActivityListModel::load);
 }
 
-void FileActivityListModel::load(AccountState *accountState, const int objectId)
+QString FileActivityListModel::localPath() const
 {
-    Q_ASSERT(accountState);
-    if (!accountState || currentlyFetching()) {
+    return _localPath;
+}
+
+void FileActivityListModel::setLocalPath(const QString &localPath)
+{
+    _localPath = localPath;
+    Q_EMIT localPathChanged();
+
+    load();
+}
+
+void FileActivityListModel::load()
+{
+    if (!accountState() || _localPath.isEmpty() || currentlyFetching()) {
+        return;
+    }
+
+    const auto folder = FolderMan::instance()->folderForPath(_localPath);
+
+    if (!folder) {
+        qCWarning(lcFileActivityListModel) << "Invalid folder for localPath:" << _localPath << "will not load activity list model.";
         return;
     }
-    setAccountState(accountState);
 
-    _objectId = objectId;
+    const auto folderRelativePath = _localPath.mid(folder->cleanPath().length() + 1);
+    SyncJournalFileRecord record;
+    folder->journalDb()->getFileRecord(folderRelativePath, &record);
+
+    _objectId = record.numericFileId().toInt();
     slotRefreshActivity();
 }
 
 void FileActivityListModel::startFetchJob()
 {
-    if (!accountState()->isConnected()) {
+    if (!accountState()->isConnected() || _objectId == -1) {
         return;
     }
     setAndRefreshCurrentlyFetching(true);
index 18d8d9830edb212b387a69f1eda9aed24b98f251..4134dc0c746c243d0a503388ee1536f4ac0c634d 100644 (file)
@@ -22,17 +22,25 @@ namespace OCC {
 class FileActivityListModel : public ActivityListModel
 {
     Q_OBJECT
+    Q_PROPERTY(QString localPath READ localPath WRITE setLocalPath NOTIFY localPathChanged)
 
 public:
     explicit FileActivityListModel(QObject *parent = nullptr);
 
+    QString localPath() const;
+
+signals:
+    void localPathChanged();
+
 public slots:
-    void load(AccountState *accountState, const int objectId);
+    void setLocalPath(const QString &localPath);
+    void load();
 
 protected slots:
     void startFetchJob() override;
 
 private:
-    int _objectId;
+    int _objectId = -1;
+    QString _localPath;
 };
 }
diff --git a/src/gui/filedetails/FileActivityView.qml b/src/gui/filedetails/FileActivityView.qml
new file mode 100644 (file)
index 0000000..47fbe35
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+import "../tray"
+
+Item {
+    id: root
+
+    property string localPath: ""
+    property var accountState: ({})
+    property int horizontalPadding: 0
+    property int iconSize: 32
+    property alias model: activityModel
+
+    FileActivityListModel {
+        id: activityModel
+        localPath: root.localPath
+        accountState: root.accountState
+    }
+
+    ActivityList {
+        anchors.fill: parent
+        anchors.leftMargin: root.horizontalPadding
+        anchors.rightMargin: root.horizontalPadding
+
+        iconSize: root.iconSize
+        isFileActivityList: true
+        model: root.model
+    }
+}
diff --git a/src/gui/filedetails/FileDetailsPage.qml b/src/gui/filedetails/FileDetailsPage.qml
new file mode 100644 (file)
index 0000000..3e31f10
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+Page {
+    id: root
+
+    property var accountState: ({})
+    property string localPath: ({})
+
+    // We want the SwipeView to "spill" over the edges of the window to really
+    // make it look nice. If we apply page-wide padding, however, the swipe
+    // contents only go as far as the page contents, clipped by the padding.
+    // This property reflects the padding we intend to display, but not the real
+    // padding, which we have to apply selectively to achieve our desired effect.
+    property int intendedPadding: Style.standardSpacing * 2
+    property int iconSize: 32
+
+    property FileDetails fileDetails: FileDetails {
+        id: fileDetails
+        localPath: root.localPath
+    }
+
+    Connections {
+        target: Systray
+        function onShowFileDetailsPage(fileLocalPath, page) {
+            if(fileLocalPath === root.localPath) {
+                switch(page) {
+                case Systray.FileDetailsPage.Activity:
+                    swipeView.currentIndex = fileActivityView.swipeIndex;
+                    break;
+                case Systray.FileDetailsPage.Sharing:
+                    swipeView.currentIndex = shareView.swipeIndex;
+                    break;
+                }
+            }
+        }
+    }
+
+    topPadding: intendedPadding
+    bottomPadding: intendedPadding
+
+    background: Rectangle {
+        color: Style.backgroundColor
+    }
+
+    header: ColumnLayout {
+        spacing: root.intendedPadding
+
+        GridLayout {
+            id: headerGridLayout
+
+            readonly property bool showFileLockedString: root.fileDetails.lockExpireString !== ""
+
+            Layout.fillWidth: parent
+            Layout.topMargin: root.topPadding
+
+            columns: 2
+            rows: showFileLockedString ? 3 : 2
+
+            rowSpacing: Style.standardSpacing / 2
+            columnSpacing: Style.standardSpacing
+
+            Image {
+                id: fileIcon
+
+                Layout.rowSpan: headerGridLayout.rows
+                Layout.preferredWidth: Style.trayListItemIconSize
+                Layout.leftMargin: root.intendedPadding
+                Layout.fillHeight: true
+
+                verticalAlignment: Image.AlignVCenter
+                horizontalAlignment: Image.AlignHCenter
+                source: root.fileDetails.iconUrl
+                sourceSize.width: Style.trayListItemIconSize
+                sourceSize.height: Style.trayListItemIconSize
+                fillMode: Image.PreserveAspectFit
+            }
+
+            Label {
+                id: fileNameLabel
+
+                Layout.fillWidth: true
+                Layout.rightMargin: root.intendedPadding
+
+                text: root.fileDetails.name
+                color: Style.ncTextColor
+                font.bold: true
+                wrapMode: Text.Wrap
+            }
+
+            Label {
+                id: fileDetailsLabel
+
+                Layout.fillWidth: true
+                Layout.rightMargin: root.intendedPadding
+
+                text: `${root.fileDetails.sizeString} · ${root.fileDetails.lastChangedString}`
+                color: Style.ncSecondaryTextColor
+                wrapMode: Text.Wrap
+            }
+
+            Label {
+                id: fileLockedLabel
+
+                Layout.fillWidth: true
+                Layout.rightMargin: root.intendedPadding
+
+                text: root.fileDetails.lockExpireString
+                color: Style.ncSecondaryTextColor
+                wrapMode: Text.Wrap
+                visible: headerGridLayout.showFileLockedString
+            }
+        }
+
+        TabBar {
+            id: viewBar
+
+            Layout.leftMargin: root.intendedPadding
+            Layout.rightMargin: root.intendedPadding
+
+            padding: 0
+            background: Rectangle {
+                color: Style.backgroundColor
+            }
+
+            NCTabButton {
+                svgCustomColorSource: "image://svgimage-custom-color/activity.svg"
+                text: qsTr("Activity")
+                checked: swipeView.currentIndex === fileActivityView.swipeIndex
+                onClicked: swipeView.currentIndex = fileActivityView.swipeIndex
+            }
+
+            NCTabButton {
+                svgCustomColorSource: "image://svgimage-custom-color/share.svg"
+                text: qsTr("Sharing")
+                checked: swipeView.currentIndex === shareView.swipeIndex
+                onClicked: swipeView.currentIndex = shareView.swipeIndex
+            }
+        }
+    }
+
+    SwipeView {
+        id: swipeView
+
+        anchors.fill: parent
+        clip: true
+
+        FileActivityView {
+            id: fileActivityView
+
+            property int swipeIndex: SwipeView.index
+
+            accountState: root.accountState
+            localPath: root.localPath
+            horizontalPadding: root.intendedPadding
+            iconSize: root.iconSize
+        }
+
+        ShareView {
+            id: shareView
+
+            property int swipeIndex: SwipeView.index
+
+            accountState: root.accountState
+            localPath: root.localPath
+            fileDetails: root.fileDetails
+            horizontalPadding: root.intendedPadding
+            iconSize: root.iconSize
+        }
+    }
+}
diff --git a/src/gui/filedetails/FileDetailsWindow.qml b/src/gui/filedetails/FileDetailsWindow.qml
new file mode 100644 (file)
index 0000000..a63607a
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.Window 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+ApplicationWindow {
+    id: root
+
+    property var accountState
+    property string localPath: ""
+
+    width: 400
+    height: 500
+
+    title: qsTr("File details of %1 · %2").arg(fileDetailsPage.fileDetails.name).arg(Systray.windowTitle)
+
+    FileDetailsPage {
+        id: fileDetailsPage
+        anchors.fill: parent
+        accountState: root.accountState
+        localPath: root.localPath
+    }
+}
diff --git a/src/gui/filedetails/NCInputTextEdit.qml b/src/gui/filedetails/NCInputTextEdit.qml
new file mode 100644 (file)
index 0000000..85cd399
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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 QtQuick.Layouts 1.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+TextEdit {
+    id: root
+
+    property color accentColor: Style.ncBlue
+    property color secondaryColor: Style.menuBorder
+    property alias submitButton: submitButton
+
+    clip: true
+    color: Style.ncTextColor
+    textMargin: Style.smallSpacing
+    wrapMode: TextEdit.Wrap
+    selectByMouse: true
+    height: Math.max(Style.talkReplyTextFieldPreferredHeight, contentHeight)
+
+    Rectangle {
+        id: textFieldBorder
+        anchors.fill: parent
+        radius: Style.slightlyRoundedButtonRadius
+        border.width: Style.normalBorderWidth
+        border.color: root.activeFocus ? root.accentColor : root.secondaryColor
+        color: Style.backgroundColor
+        z: -1
+    }
+
+    Button {
+        id: submitButton
+
+        anchors.bottom: root.bottom
+        anchors.right: root.right
+        anchors.margins: 1
+
+        width: height
+        height: parent.height
+
+        background: Rectangle {
+            radius: width / 2
+            color: textFieldBorder.color
+        }
+
+        flat: true
+        icon.source: "image://svgimage-custom-color/confirm.svg" + "/" + root.secondaryColor
+        icon.color: hovered && enabled ? UserModel.currentUser.accentColor : root.secondaryColor
+
+        enabled: root.text !== ""
+
+        onClicked: root.editingFinished()
+    }
+}
+
diff --git a/src/gui/filedetails/NCInputTextField.qml b/src/gui/filedetails/NCInputTextField.qml
new file mode 100644 (file)
index 0000000..36dd42e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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 QtQuick.Layouts 1.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+TextField {
+    id: root
+
+    property color accentColor: Style.ncBlue
+    property color secondaryColor: Style.menuBorder
+    property alias submitButton: submitButton
+
+    implicitHeight: Style.talkReplyTextFieldPreferredHeight
+    color: Style.ncTextColor
+    placeholderTextColor: secondaryColor
+
+    rightPadding: submitButton.width
+
+    selectByMouse: true
+
+    background: Rectangle {
+        id: textFieldBorder
+        radius: Style.slightlyRoundedButtonRadius
+        border.width: Style.normalBorderWidth
+        border.color: root.activeFocus ? root.accentColor : root.secondaryColor
+        color: Style.backgroundColor
+    }
+
+    Button {
+        id: submitButton
+
+        anchors.top: root.top
+        anchors.right: root.right
+        anchors.margins: 1
+
+        width: height
+        height: parent.height
+
+        background: null
+        flat: true
+        icon.source: "image://svgimage-custom-color/confirm.svg" + "/" + root.secondaryColor
+        icon.color: hovered && enabled ? UserModel.currentUser.accentColor : root.secondaryColor
+
+        enabled: root.text !== ""
+
+        onClicked: root.accepted()
+    }
+}
+
diff --git a/src/gui/filedetails/NCTabButton.qml b/src/gui/filedetails/NCTabButton.qml
new file mode 100644 (file)
index 0000000..6a5a282
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.Window 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+TabButton {
+    id: tabButton
+
+    property string svgCustomColorSource: ""
+
+    padding: 0
+    background: Rectangle {
+        color: tabButton.pressed ? Style.lightHover : Style.backgroundColor
+    }
+
+    contentItem: ColumnLayout {
+        id: tabButtonLayout
+
+        property var elementColors: tabButton.checked ? Style.ncTextColor : Style.ncSecondaryTextColor
+
+        // We'd like to just set the height of the Image, but this causes crashing.
+        // So we use a wrapping Item and use anchors to adjust the size.
+        Item {
+            id: iconItem
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            height: 20
+
+            Image {
+                id: iconItemImage
+                anchors.fill: parent
+                anchors.margins: tabButton.checked ? 0 : 2
+                horizontalAlignment: Image.AlignHCenter
+                verticalAlignment: Image.AlignVCenter
+                fillMode: Image.PreserveAspectFit
+                source: tabButton.svgCustomColorSource + "/" + tabButtonLayout.elementColors
+                sourceSize.width: 32
+                sourceSize.height: 32
+            }
+        }
+
+        Label {
+            id: tabButtonLabel
+            Layout.fillWidth: true
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            color: tabButtonLayout.elementColors
+            text: tabButton.text
+            font.bold: tabButton.checked
+        }
+
+        Rectangle {
+            FontMetrics {
+                id: fontMetrics
+                font.family: tabButtonLabel.font.family
+                font.pixelSize: tabButtonLabel.font.pixelSize
+                font.bold: true
+            }
+
+            property int textWidth: fontMetrics.boundingRect(tabButtonLabel.text).width
+
+            implicitWidth: textWidth + Style.standardSpacing * 2
+            implicitHeight: 2
+            color: tabButton.checked ? Style.ncBlue : "transparent"
+        }
+    }
+}
diff --git a/src/gui/filedetails/ShareDelegate.qml b/src/gui/filedetails/ShareDelegate.qml
new file mode 100644 (file)
index 0000000..56cbe9e
--- /dev/null
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.Window 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+import QtGraphicalEffects 1.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+import "../tray"
+
+GridLayout {
+    id: root
+
+    signal deleteShare
+    signal createNewLinkShare
+
+    signal toggleAllowEditing(bool enable)
+    signal toggleAllowResharing(bool enable)
+    signal togglePasswordProtect(bool enable)
+    signal toggleExpirationDate(bool enable)
+    signal toggleNoteToRecipient(bool enable)
+
+    signal setLinkShareLabel(string label)
+    signal setExpireDate(var milliseconds) // Since QML ints are only 32 bits, use a variant
+    signal setPassword(string password)
+    signal setNote(string note)
+
+    anchors.left: parent.left
+    anchors.right: parent.right
+
+    columns: 3
+    rows: linkDetailLabel.visible ? 1 : 2
+
+    columnSpacing: Style.standardSpacing / 2
+    rowSpacing: Style.standardSpacing / 2
+
+    property int iconSize: 32
+
+    property var share: model.share ?? ({})
+
+    property string iconUrl: model.iconUrl ?? ""
+    property string avatarUrl: model.avatarUrl ?? ""
+    property string text: model.display ?? ""
+    property string detailText: model.detailText ?? ""
+    property string link: model.link ?? ""
+    property string note: model.note ?? ""
+    property string password: model.password ?? ""
+    property string passwordPlaceholder: "●●●●●●●●●●"
+
+    property var expireDate: model.expireDate // Don't use int as we are limited
+    property var maximumExpireDate: model.enforcedMaximumExpireDate
+
+    property string linkShareLabel: model.linkShareLabel ?? ""
+
+    property bool editingAllowed: model.editingAllowed
+    property bool noteEnabled: model.noteEnabled
+    property bool expireDateEnabled: model.expireDateEnabled
+    property bool expireDateEnforced: model.expireDateEnforced
+    property bool passwordProtectEnabled: model.passwordProtectEnabled
+    property bool passwordEnforced: model.passwordEnforced
+
+    property bool isLinkShare: model.shareType === ShareModel.ShareTypeLink
+    property bool isPlaceholderLinkShare: model.shareType === ShareModel.ShareTypePlaceholderLink
+
+    property bool canCreateLinkShares: true
+
+    property bool waitingForEditingAllowedChange: false
+    property bool waitingForNoteEnabledChange: false
+    property bool waitingForExpireDateEnabledChange: false
+    property bool waitingForPasswordProtectEnabledChange: false
+    property bool waitingForExpireDateChange: false
+    property bool waitingForLinkShareLabelChange: false
+    property bool waitingForPasswordChange: false
+    property bool waitingForNoteChange: false
+
+    function resetNoteField() {
+        noteTextEdit.text = note;
+        waitingForNoteChange = false;
+    }
+
+    function resetLinkShareLabelField() {
+        linkShareLabelTextField.text = linkShareLabel;
+        waitingForLinkShareLabelChange = false;
+    }
+
+    function resetPasswordField() {
+        passwordTextField.text = password !== "" ? password : passwordPlaceholder;
+        waitingForPasswordChange = false;
+    }
+
+    function resetExpireDateField() {
+        // Expire date changing is handled by the expireDateSpinBox
+        waitingForExpireDateChange = false;
+    }
+
+    function resetEditingAllowedField() {
+        editingAllowedMenuItem.checked = editingAllowed;
+        waitingForEditingAllowedChange = false;
+    }
+
+    function resetNoteEnabledField() {
+        noteEnabledMenuItem.checked = noteEnabled;
+        waitingForNoteEnabledChange = false;
+    }
+
+    function resetExpireDateEnabledField() {
+        expireDateEnabledMenuItem.checked = expireDateEnabled;
+        waitingForExpireDateEnabledChange = false;
+    }
+
+    function resetPasswordProtectEnabledField() {
+        passwordProtectEnabledMenuItem.checked = passwordProtectEnabled;
+        waitingForPasswordProtectEnabledChange = false;
+    }
+
+    function resetMenu() {
+        moreMenu.close();
+
+        resetNoteField();
+        resetPasswordField();
+        resetLinkShareLabelField();
+        resetExpireDateField();
+
+        resetEditingAllowedField();
+        resetNoteEnabledField();
+        resetExpireDateEnabledField();
+        resetPasswordProtectEnabledField();
+    }
+
+    // Renaming a link share can lead to the model being reshuffled.
+    // This can cause a situation where this delegate is assigned to
+    // a new row and it doesn't have its properties signalled as
+    // changed by the model, leading to bugs. We therefore reset all
+    // the fields here when we detect the share has been changed
+    onShareChanged: resetMenu()
+
+    // Reset value after property binding broken by user interaction
+    onNoteChanged: resetNoteField()
+    onPasswordChanged: resetPasswordField()
+    onLinkShareLabelChanged: resetLinkShareLabelField()
+    onExpireDateChanged: resetExpireDateField()
+
+    onEditingAllowedChanged: resetEditingAllowedField()
+    onNoteEnabledChanged: resetNoteEnabledField()
+    onExpireDateEnabledChanged: resetExpireDateEnabledField()
+    onPasswordProtectEnabledChanged: resetPasswordProtectEnabledField()
+
+    Item {
+        id: imageItem
+
+        property bool isAvatar: root.avatarUrl !== ""
+
+        Layout.row: 0
+        Layout.column: 0
+        Layout.rowSpan: root.rows
+        Layout.preferredWidth: root.iconSize
+        Layout.preferredHeight: root.iconSize
+
+        Rectangle {
+            id: backgroundOrMask
+            anchors.fill: parent
+            radius: width / 2
+            color: Style.ncBlue
+            visible: !imageItem.isAvatar
+        }
+
+        Image {
+            id: shareIconOrThumbnail
+
+            anchors.centerIn: parent
+
+            verticalAlignment: Image.AlignVCenter
+            horizontalAlignment: Image.AlignHCenter
+            fillMode: Image.PreserveAspectFit
+
+            source: imageItem.isAvatar ? root.avatarUrl : root.iconUrl + "/white"
+            sourceSize.width: imageItem.isAvatar ? root.iconSize : root.iconSize / 2
+            sourceSize.height: imageItem.isAvatar ? root.iconSize : root.iconSize / 2
+
+            visible: !imageItem.isAvatar
+        }
+
+        OpacityMask {
+            anchors.fill: parent
+            source: shareIconOrThumbnail
+            maskSource: backgroundOrMask
+            visible: imageItem.isAvatar
+        }
+    }
+
+    Label {
+        id: shareTypeLabel
+
+        Layout.fillWidth: true
+        Layout.alignment: linkDetailLabel.visible ? Qt.AlignBottom : Qt.AlignVCenter
+        Layout.row: 0
+        Layout.column: 1
+        Layout.rowSpan: root.rows
+
+        text: root.text
+        color: Style.ncTextColor
+        elide: Text.ElideRight
+    }
+
+    Label {
+        id: linkDetailLabel
+
+        Layout.fillWidth: true
+        Layout.alignment: Qt.AlignTop
+        Layout.row: 1
+        Layout.column: 1
+
+        text: root.detailText
+        color: Style.ncSecondaryTextColor
+        elide: Text.ElideRight
+        visible: text !== ""
+    }
+
+    RowLayout {
+        Layout.row: 0
+        Layout.column: 2
+        Layout.rowSpan: root.rows
+        Layout.fillHeight: true
+        spacing: 0
+
+        Button {
+            id: createLinkButton
+
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredWidth: icon.width + (Style.standardSpacing * 2)
+
+            flat: true
+            display: AbstractButton.IconOnly
+            icon.color: Style.ncTextColor
+            icon.source: "qrc:///client/theme/add.svg"
+            icon.width: 16
+            icon.height: 16
+
+            visible: root.isPlaceholderLinkShare && root.canCreateLinkShares
+            enabled: visible
+
+            onClicked: root.createNewLinkShare()
+        }
+
+        Button {
+            id: copyLinkButton
+
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredWidth: icon.width + (Style.standardSpacing * 2)
+
+            flat: true
+            display: AbstractButton.IconOnly
+            icon.color: Style.ncTextColor
+            icon.source: "qrc:///client/theme/copy.svg"
+            icon.width: 16
+            icon.height: 16
+
+            visible: root.isLinkShare
+            enabled: visible
+
+            onClicked: {
+                clipboardHelper.text = root.link;
+                clipboardHelper.selectAll();
+                clipboardHelper.copy();
+                clipboardHelper.clear();
+            }
+
+            TextEdit { id: clipboardHelper; visible: false}
+        }
+
+        Button {
+            id: moreButton
+
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredWidth: icon.width + (Style.standardSpacing * 2)
+
+            flat: true
+            display: AbstractButton.IconOnly
+            icon.color: Style.ncTextColor
+            icon.source: "qrc:///client/theme/more.svg"
+            icon.width: 16
+            icon.height: 16
+
+            visible: !root.isPlaceholderLinkShare
+            enabled: visible
+
+            onClicked: moreMenu.popup()
+
+            Menu {
+                id: moreMenu
+
+                property int rowIconWidth: 16
+                property int indicatorItemWidth: 20
+                property int indicatorSpacing: Style.standardSpacing
+                property int itemPadding: Style.smallSpacing
+
+                padding: Style.smallSpacing
+                // TODO: Rather than setting all these palette colours manually,
+                // create a custom style and do it for all components globally
+                palette {
+                    text: Style.ncTextColor
+                    windowText: Style.ncTextColor
+                    buttonText: Style.ncTextColor
+                    light: Style.lightHover
+                    midlight: Style.lightHover
+                    mid: Style.ncSecondaryTextColor
+                    dark: Style.menuBorder
+                    button: Style.menuBorder
+                    window: Style.backgroundColor
+                    base: Style.backgroundColor
+                }
+
+                RowLayout {
+                    anchors.left: parent.left
+                    anchors.leftMargin: moreMenu.itemPadding
+                    anchors.right: parent.right
+                    anchors.rightMargin: moreMenu.itemPadding
+                    height: visible ? implicitHeight : 0
+                    spacing: moreMenu.indicatorSpacing
+
+                    visible: root.isLinkShare
+
+                    Image {
+                        Layout.preferredWidth: moreMenu.indicatorItemWidth
+                        Layout.fillHeight: true
+
+                        verticalAlignment: Image.AlignVCenter
+                        horizontalAlignment: Image.AlignHCenter
+                        fillMode: Image.Pad
+
+                        source: "image://svgimage-custom-color/edit.svg/" + Style.menuBorder
+                        sourceSize.width: moreMenu.rowIconWidth
+                        sourceSize.height: moreMenu.rowIconWidth
+                    }
+
+                    NCInputTextField {
+                        id: linkShareLabelTextField
+
+                        Layout.fillWidth: true
+                        height: visible ? implicitHeight : 0
+
+                        text: root.linkShareLabel
+                        placeholderText: qsTr("Share label")
+
+                        enabled: root.isLinkShare &&
+                                 !root.waitingForLinkShareLabelChange
+
+                        onAccepted: if(text !== root.linkShareLabel) {
+                            root.setLinkShareLabel(text);
+                            root.waitingForLinkShareLabelChange = true;
+                        }
+
+                        NCBusyIndicator {
+                            anchors.fill: parent
+                            visible: root.waitingForLinkShareLabelChange
+                            running: visible
+                            z: 1
+                        }
+                    }
+                }
+
+                // On these checkables, the clicked() signal is called after
+                // the check state changes.
+                CheckBox {
+                    id: editingAllowedMenuItem
+
+                    spacing: moreMenu.indicatorSpacing
+                    padding: moreMenu.itemPadding
+                    indicator.width: moreMenu.indicatorItemWidth
+                    indicator.height: moreMenu.indicatorItemWidth
+
+                    checkable: true
+                    checked: root.editingAllowed
+                    text: qsTr("Allow editing")
+                    enabled: !root.waitingForEditingAllowedChange
+
+                    onClicked: {
+                        root.toggleAllowEditing(checked);
+                        root.waitingForEditingAllowedChange = true;
+                    }
+
+                    NCBusyIndicator {
+                        anchors.fill: parent
+                        visible: root.waitingForEditingAllowedChange
+                        running: visible
+                        z: 1
+                    }
+                }
+
+                CheckBox {
+                    id: passwordProtectEnabledMenuItem
+
+                    spacing: moreMenu.indicatorSpacing
+                    padding: moreMenu.itemPadding
+                    indicator.width: moreMenu.indicatorItemWidth
+                    indicator.height: moreMenu.indicatorItemWidth
+
+                    checkable: true
+                    checked: root.passwordProtectEnabled
+                    text: qsTr("Password protect")
+                    enabled: !root.waitingForPasswordProtectEnabledChange && !root.passwordEnforced
+
+                    onClicked: {
+                        root.togglePasswordProtect(checked);
+                        root.waitingForPasswordProtectEnabledChange = true;
+                    }
+
+                    NCBusyIndicator {
+                        anchors.fill: parent
+                        visible: root.waitingForPasswordProtectEnabledChange
+                        running: visible
+                        z: 1
+                    }
+                }
+
+                RowLayout {
+                    anchors.left: parent.left
+                    anchors.leftMargin: moreMenu.itemPadding
+                    anchors.right: parent.right
+                    anchors.rightMargin: moreMenu.itemPadding
+                    height: visible ? implicitHeight : 0
+                    spacing: moreMenu.indicatorSpacing
+
+                    visible: root.passwordProtectEnabled
+
+                    Image {
+                        Layout.preferredWidth: moreMenu.indicatorItemWidth
+                        Layout.fillHeight: true
+
+                        verticalAlignment: Image.AlignVCenter
+                        horizontalAlignment: Image.AlignHCenter
+                        fillMode: Image.Pad
+
+                        source: "image://svgimage-custom-color/lock-https.svg/" + Style.menuBorder
+                        sourceSize.width: moreMenu.rowIconWidth
+                        sourceSize.height: moreMenu.rowIconWidth
+                    }
+
+                    NCInputTextField {
+                        id: passwordTextField
+
+                        Layout.fillWidth: true
+                        height: visible ? implicitHeight : 0
+
+                        text: root.password !== "" ? root.password : root.passwordPlaceholder
+                        enabled: root.passwordProtectEnabled &&
+                                 !root.waitingForPasswordChange &&
+                                 !root.waitingForPasswordProtectEnabledChange
+
+                        onAccepted: if(text !== root.password && text !== root.passwordPlaceholder) {
+                            root.setPassword(text);
+                            root.waitingForPasswordChange = true;
+                        }
+
+                        NCBusyIndicator {
+                            anchors.fill: parent
+                            visible: root.waitingForPasswordChange ||
+                                     root.waitingForPasswordProtectEnabledChange
+                            running: visible
+                            z: 1
+                        }
+                    }
+                }
+
+                CheckBox {
+                    id: expireDateEnabledMenuItem
+
+                    spacing: moreMenu.indicatorSpacing
+                    padding: moreMenu.itemPadding
+                    indicator.width: moreMenu.indicatorItemWidth
+                    indicator.height: moreMenu.indicatorItemWidth
+
+                    checkable: true
+                    checked: root.expireDateEnabled
+                    text: qsTr("Set expiration date")
+                    enabled: !root.waitingForExpireDateEnabledChange && !root.expireDateEnforced
+
+                    onClicked: {
+                        root.toggleExpirationDate(checked);
+                        root.waitingForExpireDateEnabledChange = true;
+                    }
+
+                    NCBusyIndicator {
+                        anchors.fill: parent
+                        visible: root.waitingForExpireDateEnabledChange
+                        running: visible
+                        z: 1
+                    }
+                }
+
+                RowLayout {
+                    anchors.left: parent.left
+                    anchors.leftMargin: moreMenu.itemPadding
+                    anchors.right: parent.right
+                    anchors.rightMargin: moreMenu.itemPadding
+                    height: visible ? implicitHeight : 0
+                    spacing: moreMenu.indicatorSpacing
+
+                    visible: root.expireDateEnabled
+
+                    Image {
+                        Layout.preferredWidth: moreMenu.indicatorItemWidth
+                        Layout.fillHeight: true
+
+                        verticalAlignment: Image.AlignVCenter
+                        horizontalAlignment: Image.AlignHCenter
+                        fillMode: Image.Pad
+
+                        source: "image://svgimage-custom-color/calendar.svg/" + Style.menuBorder
+                        sourceSize.width: moreMenu.rowIconWidth
+                        sourceSize.height: moreMenu.rowIconWidth
+                    }
+
+                    // QML dates are essentially JavaScript dates, which makes them very finicky and unreliable.
+                    // Instead, we exclusively deal with msecs from epoch time to make things less painful when editing.
+                    // We only use the QML Date when showing the nice string to the user.
+                    SpinBox {
+                        id: expireDateSpinBox
+
+                        // Work arounds the limitations of QML's 32 bit integer when handling msecs from epoch
+                        // Instead, we handle everything as days since epoch
+                        readonly property int dayInMSecs: 24 * 60 * 60 * 1000
+                        readonly property int expireDateReduced: Math.floor(root.expireDate / dayInMSecs)
+                        // Reset the model data after binding broken on user interact
+                        onExpireDateReducedChanged: value = expireDateReduced
+
+                        // We can't use JS's convenient Infinity or Number.MAX_VALUE as
+                        // JS Number type is 64 bits, whereas QML's int type is only 32 bits
+                        readonly property IntValidator intValidator: IntValidator {}
+                        readonly property int maximumExpireDateReduced: root.expireDateEnforced ?
+                                                                            Math.floor(root.maximumExpireDate / dayInMSecs) :
+                                                                            intValidator.top
+                        readonly property int minimumExpireDateReduced: {
+                            const currentDate = new Date();
+                            const minDateUTC = new Date(Date.UTC(currentDate.getFullYear(),
+                                                                 currentDate.getMonth(),
+                                                                 currentDate.getDate() + 1));
+                            return Math.floor(minDateUTC / dayInMSecs) // Start of day at 00:00:0000 UTC
+                        }
+
+                        // Taken from Kalendar 22.08
+                        // https://invent.kde.org/pim/kalendar/-/blob/release/22.08/src/contents/ui/KalendarUtils/dateutils.js
+                        function parseDateString(dateString) {
+                            function defaultParse() {
+                                return Date.fromLocaleDateString(Qt.locale(), dateString, Locale.NarrowFormat);
+                            }
+
+                            const dateStringDelimiterMatches = dateString.match(/\D/);
+                            if(dateStringDelimiterMatches.length === 0) {
+                                // Let the date method figure out this weirdness
+                                return defaultParse();
+                            }
+
+                            const dateStringDelimiter = dateStringDelimiterMatches[0];
+
+                            const localisedDateFormatSplit = Qt.locale().dateFormat(Locale.NarrowFormat).split(dateStringDelimiter);
+                            const localisedDateDayPosition = localisedDateFormatSplit.findIndex((x) => /d/gi.test(x));
+                            const localisedDateMonthPosition = localisedDateFormatSplit.findIndex((x) => /m/gi.test(x));
+                            const localisedDateYearPosition = localisedDateFormatSplit.findIndex((x) => /y/gi.test(x));
+
+                            let splitDateString = dateString.split(dateStringDelimiter);
+                            let userProvidedYear = splitDateString[localisedDateYearPosition]
+
+                            const dateNow = new Date();
+                            const stringifiedCurrentYear = dateNow.getFullYear().toString();
+
+                            // If we have any input weirdness, or if we have a fully-written year
+                            // (e.g. 2022 instead of 22) then use default parse
+                            if(splitDateString.length === 0 ||
+                                    splitDateString.length > 3 ||
+                                    userProvidedYear.length >= stringifiedCurrentYear.length) {
+                                return defaultParse();
+                            }
+
+                            let fullyWrittenYear = userProvidedYear.split("");
+                            const digitsToAdd = stringifiedCurrentYear.length - fullyWrittenYear.length;
+                            for(let i = 0; i < digitsToAdd; i++) {
+                                fullyWrittenYear.splice(i, 0, stringifiedCurrentYear[i])
+                            }
+                            fullyWrittenYear = fullyWrittenYear.join("");
+
+                            const fixedYearNum = Number(fullyWrittenYear);
+                            const monthIndexNum = Number(splitDateString[localisedDateMonthPosition]) - 1;
+                            const dayNum = Number(splitDateString[localisedDateDayPosition]);
+
+                            // Modification: return date in UTC
+                            return new Date(Date.UTC(fixedYearNum, monthIndexNum, dayNum));
+                        }
+
+                        Layout.fillWidth: true
+                        height: visible ? implicitHeight : 0
+
+                        // We want all the internal benefits of the spinbox but don't actually want the
+                        // buttons, so set an empty item as a dummy
+                        up.indicator: Item {}
+                        down.indicator: Item {}
+
+                        background: Rectangle {
+                            radius: Style.slightlyRoundedButtonRadius
+                            border.width: Style.normalBorderWidth
+                            border.color: expireDateSpinBox.activeFocus ? Style.ncBlue : Style.menuBorder
+                            color: Style.backgroundColor
+                        }
+
+                        value: expireDateReduced
+                        from: minimumExpireDateReduced
+                        to: maximumExpireDateReduced
+
+                        textFromValue: (value, locale) => {
+                            const dateFromValue = new Date(value * dayInMSecs);
+                            return dateFromValue.toLocaleDateString(Qt.locale(), Locale.NarrowFormat);
+                        }
+                        valueFromText: (text, locale) => {
+                            const dateFromText = parseDateString(text);
+                            return Math.floor(dateFromText.getTime() / dayInMSecs);
+                        }
+
+                        editable: true
+                        inputMethodHints: Qt.ImhDate | Qt.ImhFormattedNumbersOnly
+
+                        enabled: root.expireDateEnabled &&
+                                 !root.waitingForExpireDateChange &&
+                                 !root.waitingForExpireDateEnabledChange
+
+                        onValueModified: {
+                            root.setExpireDate(value * dayInMSecs);
+                            root.waitingForExpireDateChange = true;
+                        }
+
+                        NCBusyIndicator {
+                            anchors.fill: parent
+                            visible: root.waitingForExpireDateEnabledChange ||
+                                     root.waitingForExpireDateChange
+                            running: visible
+                            z: 1
+                        }
+                    }
+                }
+
+                CheckBox {
+                    id: noteEnabledMenuItem
+
+                    spacing: moreMenu.indicatorSpacing
+                    padding: moreMenu.itemPadding
+                    indicator.width: moreMenu.indicatorItemWidth
+                    indicator.height: moreMenu.indicatorItemWidth
+
+                    checkable: true
+                    checked: root.noteEnabled
+                    text: qsTr("Note to recipient")
+                    enabled: !root.waitingForNoteEnabledChange
+
+                    onClicked: {
+                        root.toggleNoteToRecipient(checked);
+                        root.waitingForNoteEnabledChange = true;
+                    }
+
+                    NCBusyIndicator {
+                        anchors.fill: parent
+                        visible: root.waitingForNoteEnabledChange
+                        running: visible
+                        z: 1
+                    }
+                }
+
+                RowLayout {
+                    anchors.left: parent.left
+                    anchors.leftMargin: moreMenu.itemPadding
+                    anchors.right: parent.right
+                    anchors.rightMargin: moreMenu.itemPadding
+                    height: visible ? implicitHeight : 0
+                    spacing: moreMenu.indicatorSpacing
+
+                    visible: root.noteEnabled
+
+                    Image {
+                        Layout.preferredWidth: moreMenu.indicatorItemWidth
+                        Layout.fillHeight: true
+
+                        verticalAlignment: Image.AlignVCenter
+                        horizontalAlignment: Image.AlignHCenter
+                        fillMode: Image.Pad
+
+                        source: "image://svgimage-custom-color/edit.svg/" + Style.menuBorder
+                        sourceSize.width: moreMenu.rowIconWidth
+                        sourceSize.height: moreMenu.rowIconWidth
+                    }
+
+                    NCInputTextEdit {
+                        id: noteTextEdit
+
+                        Layout.fillWidth: true
+                        height: visible ? Math.max(Style.talkReplyTextFieldPreferredHeight, contentHeight) : 0
+                        submitButton.height: Math.min(Style.talkReplyTextFieldPreferredHeight, height - 2)
+
+                        text: root.note
+                        enabled: root.noteEnabled &&
+                                 !root.waitingForNoteChange &&
+                                 !root.waitingForNoteEnabledChange
+
+                        onEditingFinished: if(text !== root.note) {
+                            root.setNote(text);
+                            root.waitingForNoteChange = true;
+                        }
+
+                        NCBusyIndicator {
+                            anchors.fill: parent
+                            visible: root.waitingForNoteChange ||
+                                     root.waitingForNoteEnabledChange
+                            running: visible
+                            z: 1
+                        }
+                    }
+                }
+
+                MenuItem {
+                    spacing: moreMenu.indicatorSpacing
+                    padding: moreMenu.itemPadding
+
+                    icon.width: moreMenu.indicatorItemWidth
+                    icon.height: moreMenu.indicatorItemWidth
+                    icon.color: Style.ncTextColor
+                    icon.source: "qrc:///client/theme/close.svg"
+                    text: qsTr("Unshare")
+
+                    onTriggered: root.deleteShare()
+                }
+
+                MenuItem {
+                    height: visible ? implicitHeight : 0
+                    spacing: moreMenu.indicatorSpacing
+                    padding: moreMenu.itemPadding
+
+                    icon.width: moreMenu.indicatorItemWidth
+                    icon.height: moreMenu.indicatorItemWidth
+                    icon.color: Style.ncTextColor
+                    icon.source: "qrc:///client/theme/add.svg"
+                    text: qsTr("Add another link")
+
+                    visible: root.isLinkShare && root.canCreateLinkShares
+                    enabled: visible
+
+                    onTriggered: root.createNewLinkShare()
+                }
+            }
+        }
+    }
+}
diff --git a/src/gui/filedetails/ShareView.qml b/src/gui/filedetails/ShareView.qml
new file mode 100644 (file)
index 0000000..eb8da6f
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.Window 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+import "../tray"
+import "../"
+
+ColumnLayout {
+    id: root
+
+    property string localPath: ""
+    property var accountState: ({})
+    property FileDetails fileDetails: FileDetails {}
+    property int horizontalPadding: 0
+    property int iconSize: 32
+
+    readonly property bool sharingPossible: shareModel && shareModel.canShare && shareModel.sharingEnabled
+    readonly property bool userGroupSharingPossible: sharingPossible && shareModel.userGroupSharingEnabled
+    readonly property bool publicLinkSharingPossible: sharingPossible && shareModel.publicLinkSharesEnabled
+
+    readonly property bool loading: sharingPossible && (!shareModel ||
+                                                        shareModel.fetchOngoing ||
+                                                        !shareModel.hasInitialShareFetchCompleted ||
+                                                        waitingForSharesToChange)
+    property bool waitingForSharesToChange: true // Gets changed to false when listview count changes
+    property bool stopWaitingForSharesToChangeOnPasswordError: false
+
+    readonly property ShareModel shareModel: ShareModel {
+        accountState: root.accountState
+        localPath: root.localPath
+
+        onSharesChanged: root.waitingForSharesToChange = false
+
+        onServerError: {
+            if(errorBox.text === "") {
+                errorBox.text = message;
+            } else {
+                errorBox.text += "\n\n" + message
+            }
+
+            errorBox.visible = true;
+        }
+
+        onPasswordSetError: if(root.stopWaitingForSharesToChangeOnPasswordError) {
+            root.waitingForSharesToChange = false;
+            root.stopWaitingForSharesToChangeOnPasswordError = false;
+        }
+
+        onRequestPasswordForLinkShare: shareRequiresPasswordDialog.open()
+        onRequestPasswordForEmailSharee: {
+            shareRequiresPasswordDialog.sharee = sharee;
+            shareRequiresPasswordDialog.open();
+        }
+    }
+
+    Dialog {
+        id: shareRequiresPasswordDialog
+
+        property var sharee
+
+        function discardDialog() {
+            sharee = undefined;
+            root.waitingForSharesToChange = false;
+            close();
+        }
+
+        anchors.centerIn: parent
+        width: parent.width * 0.8
+
+        title: qsTr("Password required for new share")
+        standardButtons: Dialog.Ok | Dialog.Cancel
+        modal: true
+        closePolicy: Popup.NoAutoClose
+
+        // TODO: Rather than setting all these palette colours manually,
+        // create a custom style and do it for all components globally
+        palette {
+            text: Style.ncTextColor
+            windowText: Style.ncTextColor
+            buttonText: Style.ncTextColor
+            light: Style.lightHover
+            midlight: Style.lightHover
+            mid: Style.ncSecondaryTextColor
+            dark: Style.menuBorder
+            button: Style.menuBorder
+            window: Style.backgroundColor
+            base: Style.backgroundColor
+        }
+
+        visible: false
+
+        onAccepted: {
+            if(sharee) {
+                root.shareModel.createNewUserGroupShareWithPasswordFromQml(sharee, dialogPasswordField.text);
+                sharee = undefined;
+            } else {
+                root.shareModel.createNewLinkShareWithPassword(dialogPasswordField.text);
+            }
+
+            root.stopWaitingForSharesToChangeOnPasswordError = true;
+            dialogPasswordField.text = "";
+        }
+        onDiscarded: discardDialog()
+        onRejected: discardDialog()
+
+        NCInputTextField {
+            id: dialogPasswordField
+
+            anchors.left: parent.left
+            anchors.right: parent.right
+
+            placeholderText: qsTr("Share password")
+            onAccepted: shareRequiresPasswordDialog.accept()
+        }
+    }
+
+    ErrorBox {
+        id: errorBox
+
+        Layout.fillWidth: true
+        Layout.leftMargin: root.horizontalPadding
+        Layout.rightMargin: root.horizontalPadding
+
+        showCloseButton: true
+        visible: false
+
+        onCloseButtonClicked: {
+            text = "";
+            visible = false;
+        }
+    }
+
+    ShareeSearchField {
+        Layout.fillWidth: true
+        Layout.leftMargin: root.horizontalPadding
+        Layout.rightMargin: root.horizontalPadding
+
+        visible: root.userGroupSharingPossible
+        enabled: visible && !root.loading
+
+        accountState: root.accountState
+        shareItemIsFolder: root.fileDetails && root.fileDetails.isFolder
+
+        onShareeSelected: {
+            root.waitingForSharesToChange = true;
+            root.shareModel.createNewUserGroupShareFromQml(sharee)
+        }
+    }
+
+    Loader {
+        id: sharesViewLoader
+
+        Layout.fillWidth: true
+        Layout.fillHeight: true
+        Layout.leftMargin: root.horizontalPadding
+        Layout.rightMargin: root.horizontalPadding
+
+        active: root.sharingPossible
+
+        sourceComponent: ScrollView {
+            id: scrollView
+            anchors.fill: parent
+
+            contentWidth: availableWidth
+            clip: true
+            enabled: root.sharingPossible
+
+            ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+
+            ListView {
+                id: shareLinksListView
+
+                enabled: !root.loading
+                model: SortedShareModel {
+                    shareModel: root.shareModel
+                }
+
+                delegate: ShareDelegate {
+                    id: shareDelegate
+
+                    Connections {
+                        target: root.shareModel
+                        // Though we try to handle this internally by listening to onPasswordChanged,
+                        // with passwords we will get the same value from the model data when a
+                        // password set has failed, meaning we won't be able to easily tell when we
+                        // have had a response from the server in QML. So we listen to this signal
+                        // directly from the model and do the reset of the password field manually.
+                        function onPasswordSetError(shareId) {
+                            if(shareId !== model.shareId) {
+                                return;
+                            }
+                            shareDelegate.resetPasswordField();
+                        }
+
+                        function onServerError() {
+                            if(shareId !== model.shareId) {
+                                return;
+                            }
+
+                            shareDelegate.resetMenu();
+                        }
+                    }
+
+                    iconSize: root.iconSize
+                    canCreateLinkShares: root.publicLinkSharingPossible
+
+                    onCreateNewLinkShare: {
+                        root.waitingForSharesToChange = true;
+                        shareModel.createNewLinkShare();
+                    }
+                    onDeleteShare: {
+                        root.waitingForSharesToChange = true;
+                        shareModel.deleteShareFromQml(model.share);
+                    }
+
+                    onToggleAllowEditing: shareModel.toggleShareAllowEditingFromQml(model.share, enable)
+                    onToggleAllowResharing: shareModel.toggleShareAllowResharingFromQml(model.share, enable)
+                    onTogglePasswordProtect: shareModel.toggleSharePasswordProtectFromQml(model.share, enable)
+                    onToggleExpirationDate: shareModel.toggleShareExpirationDateFromQml(model.share, enable)
+                    onToggleNoteToRecipient: shareModel.toggleShareNoteToRecipientFromQml(model.share, enable)
+
+                    onSetLinkShareLabel: shareModel.setLinkShareLabelFromQml(model.share, label)
+                    onSetExpireDate: shareModel.setShareExpireDateFromQml(model.share, milliseconds)
+                    onSetPassword: shareModel.setSharePasswordFromQml(model.share, password)
+                    onSetNote: shareModel.setShareNoteFromQml(model.share, note)
+                }
+
+                Loader {
+                    id: sharesFetchingLoader
+                    anchors.fill: parent
+                    active: root.loading
+                    z: Infinity
+
+                    sourceComponent: Rectangle {
+                        color: Style.backgroundColor
+                        opacity: 0.5
+
+                        NCBusyIndicator {
+                            anchors.centerIn: parent
+                            color: Style.ncSecondaryTextColor
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    Loader {
+        id: sharingNotPossibleView
+
+        Layout.fillWidth: true
+        Layout.fillHeight: true
+        Layout.leftMargin: root.horizontalPadding
+        Layout.rightMargin: root.horizontalPadding
+
+        active: !root.sharingPossible
+
+        sourceComponent: Column {
+            anchors.left: parent.left
+            anchors.right: parent.right
+            anchors.verticalCenter: parent.verticalCenter
+
+            Label {
+                id: sharingDisabledLabel
+                width: parent.width
+                text: qsTr("Sharing is disabled")
+                color: Style.ncSecondaryTextColor
+                wrapMode: Text.Wrap
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+            }
+            Label {
+                width: parent.width
+                text: qsTr("This item cannot be shared.")
+                color: Style.ncSecondaryTextColor
+                wrapMode: Text.Wrap
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                visible: !root.shareModel.canShare
+            }
+            Label {
+                width: parent.width
+                text: qsTr("Sharing is disabled.")
+                color: Style.ncSecondaryTextColor
+                wrapMode: Text.Wrap
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                visible: !root.shareModel.sharingEnabled
+            }
+        }
+    }
+}
diff --git a/src/gui/filedetails/ShareeDelegate.qml b/src/gui/filedetails/ShareeDelegate.qml
new file mode 100644 (file)
index 0000000..a9128cb
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.Window 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+ItemDelegate {
+    id: root
+
+    text: model.display
+}
diff --git a/src/gui/filedetails/ShareeSearchField.qml b/src/gui/filedetails/ShareeSearchField.qml
new file mode 100644 (file)
index 0000000..eedf20d
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.Window 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+import "../tray"
+
+TextField {
+    id: root
+
+    signal shareeSelected(var sharee)
+
+    property var accountState: ({})
+    property bool shareItemIsFolder: false
+    property ShareeModel shareeModel: ShareeModel {
+        accountState: root.accountState
+        shareItemIsFolder: root.shareItemIsFolder
+        searchString: root.text
+    }
+
+    readonly property int horizontalPaddingOffset: Style.trayHorizontalMargin
+    readonly property color placeholderColor: Style.menuBorder
+    readonly property double iconsScaleFactor: 0.6
+
+    function triggerSuggestionsVisibility() {
+        shareeListView.count > 0 && text !== "" ? suggestionsPopup.open() : suggestionsPopup.close();
+    }
+
+    placeholderText: qsTr("Search for users or groups…")
+    placeholderTextColor: placeholderColor
+    color: Style.ncTextColor
+    enabled: !shareeModel.fetchOngoing
+
+    onActiveFocusChanged: triggerSuggestionsVisibility()
+    onTextChanged: triggerSuggestionsVisibility()
+    Keys.onPressed: {
+        if(suggestionsPopup.visible) {
+            switch(event.key) {
+            case Qt.Key_Escape:
+                suggestionsPopup.close();
+                shareeListView.currentIndex = -1;
+                event.accepted = true;
+                break;
+
+            case Qt.Key_Up:
+                shareeListView.decrementCurrentIndex();
+                event.accepted = true;
+                break;
+
+            case Qt.Key_Down:
+                shareeListView.incrementCurrentIndex();
+                event.accepted = true;
+                break;
+
+            case Qt.Key_Enter:
+            case Qt.Key_Return:
+                if(shareeListView.currentIndex > -1) {
+                    shareeListView.itemAtIndex(shareeListView.currentIndex).selectSharee();
+                    event.accepted = true;
+                    break;
+                }
+            }
+        } else {
+            switch(event.key) {
+            case Qt.Key_Down:
+                triggerSuggestionsVisibility();
+                event.accepted = true;
+                break;
+            }
+        }
+    }
+
+    leftPadding: searchIcon.width + searchIcon.anchors.leftMargin + horizontalPaddingOffset
+    rightPadding: clearTextButton.width + clearTextButton.anchors.rightMargin + horizontalPaddingOffset
+
+    background: Rectangle {
+        radius: 5
+        border.color: parent.activeFocus ? UserModel.currentUser.accentColor : Style.menuBorder
+        border.width: 1
+        color: Style.backgroundColor
+    }
+
+    Image {
+        id: searchIcon
+        anchors {
+            top: parent.top
+            left: parent.left
+            bottom: parent.bottom
+            margins: 4
+        }
+
+        width: height
+
+        smooth: true
+        antialiasing: true
+        mipmap: true
+        fillMode: Image.PreserveAspectFit
+        horizontalAlignment: Image.AlignLeft
+
+        source: "image://svgimage-custom-color/search.svg" + "/" + root.placeholderColor
+        sourceSize: Qt.size(parent.height * root.iconsScaleFactor, parent.height * root.iconsScaleFactor)
+
+        visible: !root.shareeModel.fetchOngoing
+    }
+
+    NCBusyIndicator {
+        id: busyIndicator
+
+        anchors {
+            top: parent.top
+            left: parent.left
+            bottom: parent.bottom
+        }
+
+        width: height
+        color: root.placeholderColor
+        visible: root.shareeModel.fetchOngoing
+        running: visible
+    }
+
+    Image {
+        id: clearTextButton
+
+        anchors {
+            top: parent.top
+            right: parent.right
+            bottom: parent.bottom
+            margins: 4
+        }
+
+        width: height
+
+        smooth: true
+        antialiasing: true
+        mipmap: true
+        fillMode: Image.PreserveAspectFit
+
+        source: "image://svgimage-custom-color/clear.svg" + "/" + root.placeholderColor
+        sourceSize: Qt.size(parent.height * root.iconsScaleFactor, parent.height * root.iconsScaleFactor)
+
+        visible: root.text
+
+        MouseArea {
+            id: clearTextButtonMouseArea
+            anchors.fill: parent
+            onClicked: root.clear()
+        }
+    }
+
+    Popup {
+        id: suggestionsPopup
+
+        width: root.width
+        height: 100
+        y: root.height
+
+        // TODO: Rather than setting all these palette colours manually,
+        // create a custom style and do it for all components globally
+        palette {
+            text: Style.ncTextColor
+            windowText: Style.ncTextColor
+            buttonText: Style.ncTextColor
+            light: Style.lightHover
+            midlight: Style.lightHover
+            mid: Style.ncSecondaryTextColor
+            dark: Style.menuBorder
+            button: Style.menuBorder
+            window: Style.backgroundColor
+            base: Style.backgroundColor
+        }
+
+        contentItem: ScrollView {
+            id: suggestionsScrollView
+
+            clip: true
+            ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+
+            ListView {
+                id: shareeListView
+
+                spacing: 0
+                currentIndex: -1
+                interactive: true
+
+                highlight: Rectangle {
+                    width: shareeListView.currentItem.width
+                    height: shareeListView.currentItem.height
+                    color: Style.lightHover
+                }
+                highlightFollowsCurrentItem: true
+                highlightMoveDuration: 0
+                highlightResizeDuration: 0
+                highlightRangeMode: ListView.ApplyRange
+                preferredHighlightBegin: 0
+                preferredHighlightEnd: suggestionsScrollView.height
+
+                onCountChanged: root.triggerSuggestionsVisibility()
+
+                model: root.shareeModel
+                delegate: ShareeDelegate {
+                    anchors.left: parent.left
+                    anchors.right: parent.right
+
+                    function selectSharee() {
+                        root.shareeSelected(model.sharee);
+                        suggestionsPopup.close();
+
+                        root.clear();
+                    }
+
+                    onHoveredChanged: if (hovered) {
+                        // When we set the currentIndex the list view will scroll...
+                        // unless we tamper with the preferred highlight points to stop this.
+                        const savedPreferredHighlightBegin = shareeListView.preferredHighlightBegin;
+                        const savedPreferredHighlightEnd = shareeListView.preferredHighlightEnd;
+                        // Set overkill values to make sure no scroll happens when we hover with mouse
+                        shareeListView.preferredHighlightBegin = -suggestionsScrollView.height;
+                        shareeListView.preferredHighlightEnd = suggestionsScrollView.height * 2;
+
+                        shareeListView.currentIndex = index
+
+                        // Reset original values so keyboard navigation makes list view scroll
+                        shareeListView.preferredHighlightBegin = savedPreferredHighlightBegin;
+                        shareeListView.preferredHighlightEnd = savedPreferredHighlightEnd;
+                    }
+                    onClicked: selectSharee()
+                }
+            }
+        }
+    }
+}
diff --git a/src/gui/filedetails/filedetails.cpp b/src/gui/filedetails/filedetails.cpp
new file mode 100644 (file)
index 0000000..b7fa88a
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.
+ */
+
+#include <QDateTime>
+
+#include "filedetails.h"
+#include "folderman.h"
+
+namespace OCC {
+
+FileDetails::FileDetails(QObject *parent)
+    : QObject(parent)
+{
+    _filelockStateUpdateTimer.setInterval(6000);
+    _filelockStateUpdateTimer.setSingleShot(false);
+    connect(&_filelockStateUpdateTimer, &QTimer::timeout, this, &FileDetails::updateLockExpireString);
+}
+
+void FileDetails::refreshFileDetails()
+{
+    _fileInfo.refresh();
+    Q_EMIT fileChanged();
+}
+
+QString FileDetails::localPath() const
+{
+    return _localPath;
+}
+
+void FileDetails::setLocalPath(const QString &localPath)
+{
+    if(localPath.isEmpty()) {
+        return;
+    }
+
+    if(!_localPath.isEmpty()) {
+        _fileWatcher.removePath(_localPath);
+    }
+
+    if(_fileInfo.exists()) {
+        disconnect(&_fileWatcher, &QFileSystemWatcher::fileChanged, this, &FileDetails::refreshFileDetails);
+    }
+
+    _localPath = localPath;
+    _fileInfo = QFileInfo(localPath);
+
+    _fileWatcher.addPath(localPath);
+    connect(&_fileWatcher, &QFileSystemWatcher::fileChanged, this, &FileDetails::refreshFileDetails);
+
+    const auto folder = FolderMan::instance()->folderForPath(_localPath);
+    const auto file = _localPath.mid(folder->cleanPath().length() + 1);
+
+    folder->journalDb()->getFileRecord(file, &_fileRecord);
+
+    _filelockState = _fileRecord._lockstate;
+    updateLockExpireString();
+
+    Q_EMIT fileChanged();
+}
+
+QString FileDetails::name() const
+{
+    return _fileInfo.fileName();
+}
+
+QString FileDetails::sizeString() const
+{
+    return _locale.formattedDataSize(_fileInfo.size());
+}
+
+QString FileDetails::lastChangedString() const
+{
+    static constexpr auto secsInMinute = 60;
+    static constexpr auto secsInHour = secsInMinute * 60;
+    static constexpr auto secsInDay = secsInHour * 24;
+    static constexpr auto secsInMonth = secsInDay * 30;
+    static constexpr auto secsInYear = secsInMonth * 12;
+
+    const auto elapsedSecs = _fileInfo.lastModified().secsTo(QDateTime::currentDateTime());
+
+    if(elapsedSecs < 60) {
+        const auto elapsedSecsAsInt = static_cast<int>(elapsedSecs);
+        return tr("%1 second(s) ago", "seconds elapsed since file last modified", elapsedSecsAsInt).arg(elapsedSecsAsInt);
+    } else if (elapsedSecs < secsInHour) {
+        const auto elapsedMinutes = static_cast<int>(elapsedSecs / secsInMinute);
+        return tr("%1 minute(s) ago", "minutes elapsed since file last modified", elapsedMinutes).arg(elapsedMinutes);
+    } else if (elapsedSecs < secsInDay) {
+        const auto elapsedHours = static_cast<int>(elapsedSecs / secsInHour);
+        return tr("%1 hour(s) ago", "hours elapsed since file last modified", elapsedHours).arg(elapsedHours);
+    } else if (elapsedSecs < secsInMonth) {
+        const auto elapsedDays = static_cast<int>(elapsedSecs / secsInDay);
+        return tr("%1 day(s) ago", "days elapsed since file last modified", elapsedDays).arg(elapsedDays);
+    } else if (elapsedSecs < secsInYear) {
+        const auto elapsedMonths = static_cast<int>(elapsedSecs / secsInMonth);
+        return tr("%1 month(s) ago", "months elapsed since file last modified", elapsedMonths).arg(elapsedMonths);
+    } else {
+        const auto elapsedYears = static_cast<int>(elapsedSecs / secsInYear);
+        return tr("%1 year(s) ago", "years elapsed since file last modified", elapsedYears).arg(elapsedYears);
+    }
+}
+
+QString FileDetails::iconUrl() const
+{
+    return QStringLiteral("image://tray-image-provider/:/fileicon") + _localPath;
+}
+
+QString FileDetails::lockExpireString() const
+{
+    return _lockExpireString;
+}
+
+void FileDetails::updateLockExpireString()
+{
+    if(!_filelockState._locked) {
+        _filelockStateUpdateTimer.stop();
+        _lockExpireString = QString();
+        Q_EMIT lockExpireStringChanged();
+        return;
+    }
+
+    if(!_filelockStateUpdateTimer.isActive()) {
+        _filelockStateUpdateTimer.start();
+    }
+
+    static constexpr auto SECONDS_PER_MINUTE = 60;
+    const auto lockExpirationTime = _filelockState._lockTime + _filelockState._lockTimeout;
+    const auto remainingTime = QDateTime::currentDateTime().secsTo(QDateTime::fromSecsSinceEpoch(lockExpirationTime));
+    const auto remainingTimeInMinutes = static_cast<int>(remainingTime > 0 ? remainingTime / SECONDS_PER_MINUTE : 0);
+
+    _lockExpireString = tr("Locked by %1 - Expires in %2 minute(s)", "remaining time before lock expires", remainingTimeInMinutes).arg(_filelockState._lockOwnerDisplayName).arg(remainingTimeInMinutes);
+    Q_EMIT lockExpireStringChanged();
+}
+
+bool FileDetails::isFolder() const
+{
+    return _fileInfo.isDir();
+}
+
+} // namespace OCC
diff --git a/src/gui/filedetails/filedetails.h b/src/gui/filedetails/filedetails.h
new file mode 100644 (file)
index 0000000..7b33847
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.
+ */
+
+#pragma once
+
+#include <QFileInfo>
+#include <QFileSystemWatcher>
+#include <QLocale>
+#include <QTimer>
+
+#include "common/syncjournalfilerecord.h"
+
+namespace OCC {
+
+class FileDetails : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QString localPath READ localPath WRITE setLocalPath NOTIFY localPathChanged)
+    Q_PROPERTY(QString name READ name NOTIFY fileChanged)
+    Q_PROPERTY(QString sizeString READ sizeString NOTIFY fileChanged)
+    Q_PROPERTY(QString lastChangedString READ lastChangedString NOTIFY fileChanged)
+    Q_PROPERTY(QString iconUrl READ iconUrl NOTIFY fileChanged)
+    Q_PROPERTY(QString lockExpireString READ lockExpireString NOTIFY lockExpireStringChanged)
+    Q_PROPERTY(bool isFolder READ isFolder NOTIFY isFolderChanged)
+
+public:
+    explicit FileDetails(QObject *parent = nullptr);
+
+    QString localPath() const;
+    QString name() const;
+    QString sizeString() const;
+    QString lastChangedString() const;
+    QString iconUrl() const;
+    QString lockExpireString() const;
+    bool isFolder() const;
+
+public slots:
+    void setLocalPath(const QString &localPath);
+
+signals:
+    void localPathChanged();
+    void fileChanged();
+    void lockExpireStringChanged();
+    void isFolderChanged();
+
+private slots:
+    void refreshFileDetails();
+    void updateLockExpireString();
+
+private:
+    QString _localPath;
+
+    QFileInfo _fileInfo;
+    QFileSystemWatcher _fileWatcher;
+    SyncJournalFileRecord _fileRecord;
+    SyncJournalFileLockInfo _filelockState;
+    QByteArray _numericFileId;
+    QString _lockExpireString;
+    QTimer _filelockStateUpdateTimer;
+
+    QLocale _locale;
+};
+
+} // namespace OCC
diff --git a/src/gui/filedetails/shareemodel.cpp b/src/gui/filedetails/shareemodel.cpp
new file mode 100644 (file)
index 0000000..ddc9d7a
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.
+ */
+
+#include "shareemodel.h"
+
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QJsonArray>
+
+#include "ocsshareejob.h"
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcShareeModel, "com.nextcloud.shareemodel")
+
+ShareeModel::ShareeModel(QObject *parent)
+    : QAbstractListModel(parent)
+{
+    _userStoppedTypingTimer.setSingleShot(true);
+    _userStoppedTypingTimer.setInterval(500);
+    connect(&_userStoppedTypingTimer, &QTimer::timeout, this, &ShareeModel::fetch);
+}
+
+// ---------------------- QAbstractListModel methods ---------------------- //
+
+int ShareeModel::rowCount(const QModelIndex &parent) const
+{
+    if(parent.isValid() || !_accountState) {
+        return 0;
+    }
+
+    return _sharees.count();
+}
+
+QHash<int, QByteArray> ShareeModel::roleNames() const
+{
+    auto roles = QAbstractListModel::roleNames();
+    roles[ShareeRole] = "sharee";
+    roles[AutoCompleterStringMatchRole] = "autoCompleterStringMatch";
+
+    return roles;
+}
+
+QVariant ShareeModel::data(const QModelIndex &index, const int role) const
+{
+    if (index.row() < 0 || index.row() > _sharees.size()) {
+        return {};
+    }
+
+    const auto sharee = _sharees.at(index.row());
+
+    if(sharee.isNull()) {
+        return {};
+    }
+
+    switch(role) {
+    case Qt::DisplayRole:
+        return sharee->format();
+    case AutoCompleterStringMatchRole:
+        // Don't show this to the user
+        return QString(sharee->displayName() + " (" + sharee->shareWith() + ")");
+    case ShareeRole:
+        return QVariant::fromValue(sharee);
+    default:
+        qCWarning(lcShareeModel) << "Got unknown role -- returning null value.";
+        return {};
+    }
+}
+
+// --------------------------- QPROPERTY methods --------------------------- //
+
+AccountState *ShareeModel::accountState() const
+{
+    return _accountState.data();
+}
+
+void ShareeModel::setAccountState(AccountState *accountState)
+{
+    _accountState = accountState;
+    Q_EMIT accountStateChanged();
+}
+
+bool ShareeModel::shareItemIsFolder() const
+{
+    return _shareItemIsFolder;
+}
+
+void ShareeModel::setShareItemIsFolder(const bool shareItemIsFolder)
+{
+    _shareItemIsFolder = shareItemIsFolder;
+    Q_EMIT shareItemIsFolderChanged();
+}
+
+QString ShareeModel::searchString() const
+{
+    return _searchString;
+}
+
+void ShareeModel::setSearchString(const QString &searchString)
+{
+    _searchString = searchString;
+    Q_EMIT searchStringChanged();
+
+    _userStoppedTypingTimer.start();
+}
+
+bool ShareeModel::fetchOngoing() const
+{
+    return _fetchOngoing;
+}
+
+ShareeModel::LookupMode ShareeModel::lookupMode() const
+{
+    return _lookupMode;
+}
+
+void ShareeModel::setLookupMode(const ShareeModel::LookupMode lookupMode)
+{
+    _lookupMode = lookupMode;
+    Q_EMIT lookupModeChanged();
+}
+
+// ------------------------- Internal data methods ------------------------- //
+
+void ShareeModel::fetch()
+{
+    if(!_accountState || !_accountState->account() || _searchString.isEmpty()) {
+        qCInfo(lcShareeModel) << "Not fetching sharees for searchString: " << _searchString;
+        return;
+    }
+
+    _fetchOngoing = true;
+    Q_EMIT fetchOngoingChanged();
+
+    const auto shareItemTypeString = _shareItemIsFolder ? QStringLiteral("folder") : QStringLiteral("file");
+
+    auto *job = new OcsShareeJob(_accountState->account());
+
+    connect(job, &OcsShareeJob::shareeJobFinished, this, &ShareeModel::shareesFetched);
+    connect(job, &OcsJob::ocsError, this, [&](const int statusCode, const QString &message) {
+        _fetchOngoing = false;
+        Q_EMIT fetchOngoingChanged();
+        Q_EMIT ShareeModel::displayErrorMessage(statusCode, message);
+    });
+
+    job->getSharees(_searchString, shareItemTypeString, 1, 50, _lookupMode == LookupMode::GlobalSearch ? true : false);
+}
+
+void ShareeModel::shareesFetched(const QJsonDocument &reply)
+{
+    _fetchOngoing = false;
+    Q_EMIT fetchOngoingChanged();
+
+    qCInfo(lcShareeModel) << "SearchString: " << _searchString << "resulted in reply: " << reply;
+
+    QVector<ShareePtr> newSharees;
+
+    const QStringList shareeTypes {"users", "groups", "emails", "remotes", "circles", "rooms"};
+
+    const auto appendSharees = [this, &shareeTypes, &newSharees](const QJsonObject &data) {
+        for (const auto &shareeType : shareeTypes) {
+            const auto category = data.value(shareeType).toArray();
+
+            for (const auto &sharee : category) {
+                const auto shareeJsonObject = sharee.toObject();
+                const auto parsedSharee = parseSharee(shareeJsonObject);
+
+                // Filter sharees that we have already shared with
+                const auto shareeInBlacklistIt = std::find_if(_shareeBlacklist.cbegin(),
+                                                              _shareeBlacklist.cend(),
+                                                              [&parsedSharee](const ShareePtr &blacklistSharee) {
+                    return parsedSharee->type() == blacklistSharee->type() &&
+                           parsedSharee->shareWith() == blacklistSharee->shareWith();
+                });
+
+                if (shareeInBlacklistIt != _shareeBlacklist.cend()) {
+                    continue;
+                }
+
+                newSharees.append(parsedSharee);
+            }
+        }
+    };
+    const auto replyDataObject = reply.object().value("ocs").toObject().value("data").toObject();
+    const auto replyDataExactMatchObject = replyDataObject.value("exact").toObject();
+
+    appendSharees(replyDataObject);
+    appendSharees(replyDataExactMatchObject);
+
+    Q_EMIT beginResetModel();
+    _sharees = newSharees;
+    Q_EMIT endResetModel();
+
+    Q_EMIT shareesReady();
+}
+
+ShareePtr ShareeModel::parseSharee(const QJsonObject &data) const
+{
+    auto displayName = data.value("label").toString();
+    const auto shareWith = data.value("value").toObject().value("shareWith").toString();
+    const auto type = (Sharee::Type)data.value("value").toObject().value("shareType").toInt();
+    const auto additionalInfo = data.value("value").toObject().value("shareWithAdditionalInfo").toString();
+    if (!additionalInfo.isEmpty()) {
+        displayName = tr("%1 (%2)", "sharee (shareWithAdditionalInfo)").arg(displayName, additionalInfo);
+    }
+
+    return ShareePtr(new Sharee(shareWith, displayName, type));
+}
+
+}
diff --git a/src/gui/filedetails/shareemodel.h b/src/gui/filedetails/shareemodel.h
new file mode 100644 (file)
index 0000000..954f10a
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.
+ */
+
+#pragma once
+
+#include <QAbstractListModel>
+#include <QTimer>
+
+#include "accountstate.h"
+#include "sharee.h"
+
+class QJsonDocument;
+class QJsonObject;
+
+namespace OCC {
+
+class ShareeModel : public QAbstractListModel
+{
+    Q_OBJECT
+    Q_PROPERTY(AccountState* accountState READ accountState WRITE setAccountState NOTIFY accountStateChanged)
+    Q_PROPERTY(bool shareItemIsFolder READ shareItemIsFolder WRITE setShareItemIsFolder NOTIFY shareItemIsFolderChanged)
+    Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY searchStringChanged)
+    Q_PROPERTY(bool fetchOngoing READ fetchOngoing NOTIFY fetchOngoingChanged)
+    Q_PROPERTY(LookupMode lookupMode READ lookupMode WRITE setLookupMode NOTIFY lookupModeChanged)
+
+public:
+    enum class LookupMode {
+        LocalSearch = 0,
+        GlobalSearch = 1
+    };
+    Q_ENUM(LookupMode);
+
+    enum Roles {
+        ShareeRole = Qt::UserRole + 1,
+        AutoCompleterStringMatchRole,
+    };
+    Q_ENUM(Roles);
+
+    explicit ShareeModel(QObject *parent = nullptr);
+
+    using ShareeSet = QVector<ShareePtr>; // FIXME: make it a QSet<Sharee> when Sharee can be compared
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    QHash<int, QByteArray> roleNames() const override;
+    QVariant data(const QModelIndex &index, const int role) const override;
+
+    AccountState *accountState() const;
+    bool shareItemIsFolder() const;
+    QString searchString() const;
+    bool fetchOngoing() const;
+    LookupMode lookupMode() const;
+
+signals:
+    void accountStateChanged();
+    void shareItemIsFolderChanged();
+    void searchStringChanged();
+    void fetchOngoingChanged();
+    void lookupModeChanged();
+
+    void shareesReady();
+    void displayErrorMessage(int code, const QString &);
+
+public slots:
+    void setAccountState(AccountState *accountState);
+    void setShareItemIsFolder(const bool shareItemIsFolder);
+    void setSearchString(const QString &searchString);
+    void setLookupMode(const LookupMode lookupMode);
+
+    void fetch();
+
+private slots:
+     void shareesFetched(const QJsonDocument &reply);
+
+private:
+    ShareePtr parseSharee(const QJsonObject &data) const;
+
+    QTimer _userStoppedTypingTimer;
+
+    AccountStatePtr _accountState;
+    QString _searchString;
+    bool _shareItemIsFolder = false;
+    bool _fetchOngoing = false;
+    LookupMode _lookupMode = LookupMode::LocalSearch;
+
+    QVector<ShareePtr> _sharees;
+    QVector<ShareePtr> _shareeBlacklist;
+};
+
+}
diff --git a/src/gui/filedetails/sharemodel.cpp b/src/gui/filedetails/sharemodel.cpp
new file mode 100644 (file)
index 0000000..fba85c7
--- /dev/null
@@ -0,0 +1,973 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.
+ */
+
+#include "sharemodel.h"
+
+#include <QFileInfo>
+#include <QTimeZone>
+
+#include "account.h"
+#include "folderman.h"
+#include "theme.h"
+#include "wordlist.h"
+
+namespace {
+
+static const QString placeholderLinkShareId = QStringLiteral("__placeholderLinkShareId__");
+
+QString createRandomPassword()
+{
+    const auto words = OCC::WordList::getRandomWords(10);
+
+    const auto addFirstLetter = [](const QString &current, const QString &next) -> QString {
+        return current + next.at(0);
+    };
+
+    return std::accumulate(std::cbegin(words), std::cend(words), QString(), addFirstLetter);
+}
+}
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcShareModel, "com.nextcloud.sharemodel")
+
+ShareModel::ShareModel(QObject *parent)
+    : QAbstractListModel(parent)
+{
+}
+
+// ---------------------- QAbstractListModel methods ---------------------- //
+
+int ShareModel::rowCount(const QModelIndex &parent) const
+{
+    if(parent.isValid() || !_accountState || _localPath.isEmpty()) {
+        return 0;
+    }
+
+    return _shares.count();
+}
+
+QHash<int, QByteArray> ShareModel::roleNames() const
+{
+    auto roles = QAbstractListModel::roleNames();
+    roles[ShareRole] = "share";
+    roles[ShareTypeRole] = "shareType";
+    roles[ShareIdRole] = "shareId";
+    roles[IconUrlRole] = "iconUrl";
+    roles[AvatarUrlRole] = "avatarUrl";
+    roles[LinkRole] = "link";
+    roles[LinkShareNameRole] = "linkShareName";
+    roles[LinkShareLabelRole] = "linkShareLabel";
+    roles[NoteEnabledRole] = "noteEnabled";
+    roles[NoteRole] = "note";
+    roles[ExpireDateEnabledRole] = "expireDateEnabled";
+    roles[ExpireDateEnforcedRole] = "expireDateEnforced";
+    roles[ExpireDateRole] = "expireDate";
+    roles[EnforcedMaximumExpireDateRole] = "enforcedMaximumExpireDate";
+    roles[PasswordProtectEnabledRole] = "passwordProtectEnabled";
+    roles[PasswordRole] = "password";
+    roles[PasswordEnforcedRole] = "passwordEnforced";
+    roles[EditingAllowedRole] = "editingAllowed";
+
+    return roles;
+}
+
+QVariant ShareModel::data(const QModelIndex &index, const int role) const
+{
+    if (!index.isValid()) {
+        return {};
+    }
+
+    const auto share = _shares.at(index.row());
+
+    if (!share) {
+        return {};
+    }
+
+    // Some roles only provide values for the link and user/group share types
+    if(const auto linkShare = share.objectCast<LinkShare>()) {
+        switch(role) {
+        case LinkRole:
+            return linkShare->getLink();
+        case LinkShareNameRole:
+            return linkShare->getName();
+        case LinkShareLabelRole:
+            return linkShare->getLabel();
+        case NoteEnabledRole:
+            return !linkShare->getNote().isEmpty();
+        case NoteRole:
+            return linkShare->getNote();
+        case ExpireDateEnabledRole:
+            return linkShare->getExpireDate().isValid();
+        case ExpireDateRole:
+        {
+            const auto startOfExpireDayUTC = linkShare->getExpireDate().startOfDay(QTimeZone::utc());
+            return startOfExpireDayUTC.toMSecsSinceEpoch();
+        }
+        default:
+            break;
+        }
+
+    } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+        switch(role) {
+        case NoteEnabledRole:
+            return !userGroupShare->getNote().isEmpty();
+        case NoteRole:
+            return userGroupShare->getNote();
+        case ExpireDateEnabledRole:
+            return userGroupShare->getExpireDate().isValid();
+        case ExpireDateRole:
+        {
+            const auto startOfExpireDayUTC = userGroupShare->getExpireDate().startOfDay(QTimeZone::utc());
+            return startOfExpireDayUTC.toMSecsSinceEpoch();
+        }
+        default:
+            break;
+        }
+    }
+
+    switch(role) {
+    case Qt::DisplayRole:
+        return displayStringForShare(share);
+    case ShareRole:
+        return QVariant::fromValue(share);
+    case ShareTypeRole:
+        return share->getShareType();
+    case ShareIdRole:
+        return share->getId();
+    case IconUrlRole:
+        return iconUrlForShare(share);
+    case AvatarUrlRole:
+        return avatarUrlForShare(share);
+    case ExpireDateEnforcedRole:
+        return expireDateEnforcedForShare(share);
+    case EnforcedMaximumExpireDateRole:
+        return enforcedMaxExpireDateForShare(share);
+    case PasswordProtectEnabledRole:
+        return share->isPasswordSet();
+    case PasswordRole:
+        if (!share->isPasswordSet() || !_shareIdRecentlySetPasswords.contains(share->getId())) {
+            return {};
+        }
+        return _shareIdRecentlySetPasswords.value(share->getId());
+    case PasswordEnforcedRole:
+        return _accountState && _accountState->account() && _accountState->account()->capabilities().isValid() &&
+               ((share->getShareType() == Share::TypeEmail && _accountState->account()->capabilities().shareEmailPasswordEnforced()) ||
+               (share->getShareType() == Share::TypeLink && _accountState->account()->capabilities().sharePublicLinkEnforcePassword()));
+    case EditingAllowedRole:
+        return share->getPermissions().testFlag(SharePermissionUpdate);
+
+    // Deal with roles that only return certain values for link or user/group share types
+    case NoteEnabledRole:
+    case ExpireDateEnabledRole:
+        return false;
+    case LinkRole:
+    case LinkShareNameRole:
+    case LinkShareLabelRole:
+    case NoteRole:
+    case ExpireDateRole:
+        return {};
+    default:
+        qCWarning(lcShareModel) << "Got unknown role" << role
+                                << "for share of type" << share->getShareType()
+                                << "so returning null value.";
+        return {};
+    }
+}
+
+// ---------------------- Internal model data methods ---------------------- //
+
+void ShareModel::resetData()
+{
+    beginResetModel();
+
+    _folder = nullptr;
+    _sharePath.clear();
+    _maxSharingPermissions = {};
+    _numericFileId.clear();
+    _manager.clear();
+    _shares.clear();
+    _fetchOngoing = false;
+    _hasInitialShareFetchCompleted = false;
+
+    Q_EMIT fetchOngoingChanged();
+    Q_EMIT hasInitialShareFetchCompletedChanged();
+
+    endResetModel();
+}
+
+void ShareModel::updateData()
+{
+    resetData();
+
+    if (_localPath.isEmpty() || !_accountState || _accountState->account().isNull()) {
+        qCWarning(lcShareModel) << "Not updating share model data. Local path is:"  << _localPath
+                                << "Is account state null:" << !_accountState;
+        return;
+    }
+
+    if (!sharingEnabled()) {
+        qCWarning(lcShareModel) << "Server does not support sharing";
+        return;
+    }
+
+    _folder = FolderMan::instance()->folderForPath(_localPath);
+
+    if (!_folder) {
+        qCWarning(lcShareModel) << "Could not update share model data for" << _localPath << "no responsible folder found";
+        resetData();
+        return;
+    }
+
+    qCDebug(lcShareModel) << "Updating share model data now.";
+
+    const auto relPath = _localPath.mid(_folder->cleanPath().length() + 1);
+    _sharePath = _folder->remotePathTrailingSlash() + relPath;
+
+    SyncJournalFileRecord fileRecord;
+    bool resharingAllowed = true; // lets assume the good
+
+    if(_folder->journalDb()->getFileRecord(relPath, &fileRecord) && fileRecord.isValid()) {
+        if (!fileRecord._remotePerm.isNull() &&
+            !fileRecord._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
+
+            resharingAllowed = false;
+        }
+    }
+
+    _maxSharingPermissions = resharingAllowed ? SharePermissions(_accountState->account()->capabilities().shareDefaultPermissions()) : SharePermissions({});
+    Q_EMIT sharePermissionsChanged();
+
+    _numericFileId = fileRecord.numericFileId();
+
+    _placeholderLinkShare.reset(new Share(_accountState->account(),
+                                          placeholderLinkShareId,
+                                          _accountState->account()->id(),
+                                          _accountState->account()->davDisplayName(),
+                                          _sharePath,
+                                          Share::TypePlaceholderLink));
+    slotAddShare(_placeholderLinkShare);
+
+    auto job = new PropfindJob(_accountState->account(), _sharePath);
+    job->setProperties(
+        QList<QByteArray>()
+        << "https://open-collaboration-services.org/ns:share-permissions"
+        << "https://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
+        << "https://owncloud.org/ns:privatelink");
+    job->setTimeout(10 * 1000);
+    connect(job, &PropfindJob::result, this, &ShareModel::slotPropfindReceived);
+    connect(job, &PropfindJob::finishedWithError, this, [&]{
+        qCWarning(lcShareModel) << "Propfind for" << _sharePath << "failed";
+        _fetchOngoing = false;
+        Q_EMIT fetchOngoingChanged();
+    });
+
+    _fetchOngoing = true;
+    Q_EMIT fetchOngoingChanged();
+    job->start();
+
+    initShareManager();
+}
+
+void ShareModel::initShareManager()
+{
+    if (!_accountState || _accountState->account().isNull()) {
+        return;
+    }
+
+    bool sharingPossible = true;
+    if (!publicLinkSharesEnabled()) {
+        qCWarning(lcSharing) << "Link shares have been disabled";
+        sharingPossible = false;
+    } else if (!canShare()) {
+        qCWarning(lcSharing) << "The file cannot be shared because it does not have sharing permission.";
+        sharingPossible = false;
+    }
+
+    if (_manager.isNull() && sharingPossible) {
+        _manager.reset(new ShareManager(_accountState->account(), this));
+        connect(_manager.data(), &ShareManager::sharesFetched, this, &ShareModel::slotSharesFetched);
+        connect(_manager.data(), &ShareManager::shareCreated, this, [&]{ _manager->fetchShares(_sharePath); });
+        connect(_manager.data(), &ShareManager::linkShareCreated, this, &ShareModel::slotAddShare);
+        connect(_manager.data(), &ShareManager::linkShareRequiresPassword, this, &ShareModel::requestPasswordForLinkShare);
+
+        _manager->fetchShares(_sharePath);
+    }
+}
+
+void ShareModel::slotPropfindReceived(const QVariantMap &result)
+{
+    _fetchOngoing = false;
+    Q_EMIT fetchOngoingChanged();
+
+    const QVariant receivedPermissions = result["share-permissions"];
+    if (!receivedPermissions.toString().isEmpty()) {
+        _maxSharingPermissions = static_cast<SharePermissions>(receivedPermissions.toInt());
+        Q_EMIT sharePermissionsChanged();
+        qCInfo(lcShareModel) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions;
+    }
+
+    const auto privateLinkUrl = result["privatelink"].toString();
+    const auto numericFileId = result["fileid"].toByteArray();
+
+    if (!privateLinkUrl.isEmpty()) {
+        qCInfo(lcShareModel) << "Received private link url for" << _sharePath << privateLinkUrl;
+        _privateLinkUrl = privateLinkUrl;
+    } else if (!numericFileId.isEmpty()) {
+        qCInfo(lcShareModel) << "Received numeric file id for" << _sharePath << numericFileId;
+        _privateLinkUrl = _accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded);
+    }
+}
+
+void ShareModel::slotSharesFetched(const QList<SharePtr> &shares)
+{
+    _hasInitialShareFetchCompleted = true;
+    Q_EMIT hasInitialShareFetchCompletedChanged();
+
+    qCInfo(lcSharing) << "Fetched" << shares.count() << "shares";
+
+    for (const auto &share : shares) {
+        if (share.isNull() ||
+            share->account().isNull() ||
+            share->getUidOwner() != share->account()->davUser()) {
+
+            continue;
+        }
+
+        slotAddShare(share);
+    }
+}
+
+void ShareModel::slotAddShare(const SharePtr &share)
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    const auto shareId = share->getId();
+
+    // Remove placeholder link share if this is a link share
+    if(share->getShareType() == Share::TypeLink) {
+        slotRemoveShareWithId(placeholderLinkShareId);
+    }
+
+    QModelIndex shareModelIndex;
+
+    if (_shareIdIndexHash.contains(shareId)) {
+        const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+        const auto shareIndex = sharePersistentModelIndex.row();
+
+        _shares.replace(shareIndex, share);
+
+        shareModelIndex = index(sharePersistentModelIndex.row());
+        Q_EMIT dataChanged(shareModelIndex, shareModelIndex);
+    } else {
+        const auto shareIndex = _shares.count();
+
+        beginInsertRows({}, _shares.count(), _shares.count());
+        _shares.append(share);
+        endInsertRows();
+
+        shareModelIndex = index(shareIndex);
+    }
+
+    const QPersistentModelIndex sharePersistentIndex(shareModelIndex);
+    _shareIdIndexHash.insert(shareId, sharePersistentIndex);
+
+    connect(share.data(), &Share::serverError, this, &ShareModel::slotServerError);
+    connect(share.data(), &Share::passwordSetError, this, [this, shareId](const int code, const QString &message) {
+        _shareIdRecentlySetPasswords.remove(shareId);
+        slotServerError(code, message);
+        slotSharePasswordSet(shareId);
+        Q_EMIT passwordSetError(shareId);
+    });
+
+    // Passing shareId by reference here will cause crashing, so we pass by value
+    connect(share.data(), &Share::shareDeleted, this, [this, shareId]{ slotRemoveShareWithId(shareId); });
+    connect(share.data(), &Share::permissionsSet, this, [this, shareId]{ slotSharePermissionsSet(shareId); });
+    connect(share.data(), &Share::passwordSet, this, [this, shareId]{ slotSharePasswordSet(shareId); });
+
+    if (const auto linkShare = share.objectCast<LinkShare>()) {
+        connect(linkShare.data(), &LinkShare::noteSet, this, [this, shareId]{ slotShareNoteSet(shareId); });
+        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); });
+    } 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); });
+    }
+
+    if (_manager) {
+        connect(_manager.data(), &ShareManager::serverError, this, &ShareModel::slotServerError);
+    }
+
+    Q_EMIT sharesChanged();
+}
+
+void ShareModel::slotRemoveShareWithId(const QString &shareId)
+{
+    if (_shares.empty() || shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+        return;
+    }
+
+    _shareIdRecentlySetPasswords.remove(shareId);
+    const auto shareIndex = _shareIdIndexHash.take(shareId);
+
+    if (!shareIndex.isValid()) {
+        qCWarning(lcShareModel) << "Won't remove share with id:" << shareId
+                                << ", invalid share index: " << shareIndex;
+        return;
+    }
+
+    beginRemoveRows({}, shareIndex.row(), shareIndex.row());
+    _shares.removeAt(shareIndex.row());
+    endRemoveRows();
+
+    // If no link shares then re-add placeholder link share
+    if (shareIndex.data(ShareModel::ShareTypeRole).toInt() == Share::TypeLink) {
+
+        // Early return if we find another link share
+        for(const auto &share : _shares) {
+            if(share->getShareType() == Share::TypeLink) {
+                return;
+            }
+        }
+
+        slotAddShare(_placeholderLinkShare);
+    }
+
+    Q_EMIT sharesChanged();
+}
+
+void ShareModel::slotServerError(const int code, const QString &message)
+{
+    qCWarning(lcShareModel) << "Error from server" << code << message;
+    Q_EMIT serverError(code, message);
+}
+
+QString ShareModel::displayStringForShare(const SharePtr &share) const
+{
+    if (const auto linkShare = share.objectCast<LinkShare>()) {
+        const auto displayString = tr("Link share");
+
+        if (!linkShare->getLabel().isEmpty()) {
+            return QStringLiteral("%1 (%2)").arg(displayString, linkShare->getLabel());
+        }
+
+        return displayString;
+    } else if (share->getShareType() == Share::TypePlaceholderLink) {
+        return tr("Link share");
+    } else if (share->getShareWith()) {
+        return share->getShareWith()->format();
+    }
+
+    qCWarning(lcShareModel) << "Unable to provide good display string for share";
+    return QStringLiteral("Share");
+}
+
+QString ShareModel::iconUrlForShare(const SharePtr &share) const
+{
+    const auto iconsPath = QStringLiteral("image://svgimage-custom-color/");
+
+    switch(share->getShareType()) {
+    case Share::TypePlaceholderLink:
+    case Share::TypeLink:
+        return QString(iconsPath + QStringLiteral("public.svg"));
+    case Share::TypeEmail:
+        return QString(iconsPath + QStringLiteral("email.svg"));
+    case Share::TypeRoom:
+        return QString(iconsPath + QStringLiteral("wizard-talk.svg"));
+    case Share::TypeUser:
+        return QString(iconsPath + QStringLiteral("user.svg"));
+    case Share::TypeGroup:
+        return QString(iconsPath + QStringLiteral("wizard-groupware.svg"));
+    default:
+        return {};
+    }
+}
+
+QString ShareModel::avatarUrlForShare(const SharePtr &share) const
+{
+    if (share->getShareWith() && share->getShareWith()->type() == Sharee::User && _accountState && _accountState->account()) {
+        const QString provider = QStringLiteral("image://tray-image-provider/");
+        const QString userId = share->getShareWith()->shareWith();
+        const QString avatarUrl = Utility::concatUrlPath(_accountState->account()->url(),
+                                                         QString("remote.php/dav/avatars/%1/%2.png").arg(userId, QString::number(64))).toString();
+        return QString(provider + avatarUrl);
+    }
+
+    return {};
+}
+
+long long ShareModel::enforcedMaxExpireDateForShare(const SharePtr &share) const
+{
+    if (!_accountState || !_accountState->account() || !_accountState->account()->capabilities().isValid()) {
+        return {};
+    }
+
+    auto expireDays = -1;
+
+    // Both public links and emails count as "public" shares
+    if ((share->getShareType() == Share::TypeLink || share->getShareType() == Share::TypeEmail)
+        && _accountState->account()->capabilities().sharePublicLinkEnforceExpireDate()) {
+        expireDays = _accountState->account()->capabilities().sharePublicLinkExpireDateDays();
+
+    } else if (share->getShareType() == Share::TypeRemote && _accountState->account()->capabilities().shareRemoteEnforceExpireDate()) {
+        expireDays = _accountState->account()->capabilities().shareRemoteExpireDateDays();
+
+    } else if ((share->getShareType() == Share::TypeUser ||
+                share->getShareType() == Share::TypeGroup ||
+                share->getShareType() == Share::TypeCircle ||
+                share->getShareType() == Share::TypeRoom) &&
+               _accountState->account()->capabilities().shareInternalEnforceExpireDate()) {
+        expireDays = _accountState->account()->capabilities().shareInternalExpireDateDays();
+
+    } else {
+        return {};
+    }
+
+    const auto expireDateTime = QDate::currentDate().addDays(expireDays).startOfDay(QTimeZone::utc());
+    return expireDateTime.toMSecsSinceEpoch();
+}
+
+bool ShareModel::expireDateEnforcedForShare(const SharePtr &share) const
+{
+    if(!_accountState || !_accountState->account() || !_accountState->account()->capabilities().isValid()) {
+        return false;
+    }
+
+    // Both public links and emails count as "public" shares
+    if (share->getShareType() == Share::TypeLink ||
+        share->getShareType() == Share::TypeEmail) {
+        return _accountState->account()->capabilities().sharePublicLinkEnforceExpireDate();
+
+    } else if (share->getShareType() == Share::TypeRemote) {
+        return _accountState->account()->capabilities().shareRemoteEnforceExpireDate();
+
+    } else if (share->getShareType() == Share::TypeUser ||
+               share->getShareType() == Share::TypeGroup ||
+               share->getShareType() == Share::TypeCircle ||
+               share->getShareType() == Share::TypeRoom) {
+        return _accountState->account()->capabilities().shareInternalEnforceExpireDate();
+
+    }
+
+    return false;
+}
+
+// ----------------- Shares modified signal handling slots ----------------- //
+
+void ShareModel::slotSharePermissionsSet(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, { EditingAllowedRole });
+}
+
+void ShareModel::slotSharePasswordSet(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, { PasswordProtectEnabledRole, PasswordRole });
+}
+
+void ShareModel::slotShareNoteSet(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, { NoteEnabledRole, NoteRole });
+}
+
+void ShareModel::slotShareNameSet(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, { LinkShareNameRole });
+}
+
+void ShareModel::slotShareLabelSet(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, { Qt::DisplayRole, LinkShareLabelRole });
+}
+
+void ShareModel::slotShareExpireDateSet(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, { ExpireDateEnabledRole, ExpireDateRole });
+}
+
+// ----------------------- Shares modification slots ----------------------- //
+
+void ShareModel::toggleShareAllowEditing(const SharePtr &share, const bool enable) const
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    auto permissions = share->getPermissions();
+    enable ? permissions |= SharePermissionUpdate : permissions &= ~SharePermissionUpdate;
+
+    share->setPermissions(permissions);
+}
+
+void ShareModel::toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) const
+{
+    const auto ptr = share.value<SharePtr>();
+    toggleShareAllowEditing(ptr, enable);
+}
+
+void ShareModel::toggleShareAllowResharing(const SharePtr &share, const bool enable) const
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    auto permissions = share->getPermissions();
+    enable ? permissions |= SharePermissionShare : permissions &= ~SharePermissionShare;
+
+    share->setPermissions(permissions);
+}
+
+void ShareModel::toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) const
+{
+    const auto ptr = share.value<SharePtr>();
+    toggleShareAllowResharing(ptr, enable);
+}
+
+void ShareModel::toggleSharePasswordProtect(const SharePtr &share, const bool enable)
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    if(!enable) {
+        share->setPassword({});
+        return;
+    }
+
+    const auto randomPassword = createRandomPassword();
+    _shareIdRecentlySetPasswords.insert(share->getId(), randomPassword);
+    share->setPassword(randomPassword);
+}
+
+void ShareModel::toggleSharePasswordProtectFromQml(const QVariant &share, const bool enable)
+{
+    const auto ptr = share.value<SharePtr>();
+    toggleSharePasswordProtect(ptr, enable);
+}
+
+void ShareModel::toggleShareExpirationDate(const SharePtr &share, const bool enable) const
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    const auto expireDate = enable ? QDate::currentDate().addDays(1) : QDate();
+
+    if (const auto linkShare = share.objectCast<LinkShare>()) {
+        linkShare->setExpireDate(expireDate);
+    } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+        userGroupShare->setExpireDate(expireDate);
+    }
+}
+
+void ShareModel::toggleShareExpirationDateFromQml(const QVariant &share, const bool enable) const
+{
+    const auto ptr = share.value<SharePtr>();
+    toggleShareExpirationDate(ptr, enable);
+}
+
+void ShareModel::toggleShareNoteToRecipient(const SharePtr &share, const bool enable) const
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    const QString note = enable ? tr("Enter a note for the recipient") : QString();
+    if (const auto linkShare = share.objectCast<LinkShare>()) {
+        linkShare->setNote(note);
+    } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+        userGroupShare->setNote(note);
+    }
+}
+
+void ShareModel::toggleShareNoteToRecipientFromQml(const QVariant &share, const bool enable) const
+{
+    const auto ptr = share.value<SharePtr>();
+    toggleShareNoteToRecipient(ptr, enable);
+}
+
+void ShareModel::setLinkShareLabel(const QSharedPointer<LinkShare> &linkShare, const QString &label) const
+{
+    if (linkShare.isNull()) {
+        return;
+    }
+
+    linkShare->setLabel(label);
+}
+
+void ShareModel::setLinkShareLabelFromQml(const QVariant &linkShare, const QString &label) const
+{
+    // All of our internal share pointers are SharePtr, so cast to LinkShare for this method
+    const auto ptr = linkShare.value<SharePtr>().objectCast<LinkShare>();
+    setLinkShareLabel(ptr, label);
+}
+
+void ShareModel::setShareExpireDate(const SharePtr &share, const qint64 milliseconds) const
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    const auto date = QDateTime::fromMSecsSinceEpoch(milliseconds, QTimeZone::utc()).date();
+
+    if (const auto linkShare = share.objectCast<LinkShare>()) {
+        linkShare->setExpireDate(date);
+    } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+        userGroupShare->setExpireDate(date);
+    }
+}
+
+void ShareModel::setShareExpireDateFromQml(const QVariant &share, const QVariant milliseconds) const
+{
+    const auto ptr = share.value<SharePtr>();
+    const auto millisecondsLL = milliseconds.toLongLong();
+    setShareExpireDate(ptr, millisecondsLL);
+}
+
+void ShareModel::setSharePassword(const SharePtr &share, const QString &password)
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    _shareIdRecentlySetPasswords.insert(share->getId(), password);
+    share->setPassword(password);
+}
+
+void ShareModel::setSharePasswordFromQml(const QVariant &share, const QString &password)
+{
+    const auto ptr = share.value<SharePtr>();
+    setSharePassword(ptr, password);
+}
+
+void ShareModel::setShareNote(const SharePtr &share, const QString &note) const
+{
+    if (share.isNull()) {
+        return;
+    }
+
+    if (const auto linkShare = share.objectCast<LinkShare>()) {
+        linkShare->setNote(note);
+    } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+        userGroupShare->setNote(note);
+    }
+}
+
+void ShareModel::setShareNoteFromQml(const QVariant &share, const QString &note) const
+{
+    const auto ptr = share.value<SharePtr>();
+    setShareNote(ptr, note);
+}
+
+// ------------------- Share creation and deletion slots ------------------- //
+
+void ShareModel::createNewLinkShare() const
+{
+    if (_manager) {
+        const auto askOptionalPassword = _accountState->account()->capabilities().sharePublicLinkAskOptionalPassword();
+        const auto password = askOptionalPassword ? createRandomPassword() : QString();
+        _manager->createLinkShare(_sharePath, QString(), password);
+    }
+}
+
+void ShareModel::createNewLinkShareWithPassword(const QString &password) const
+{
+    if (_manager) {
+        _manager->createLinkShare(_sharePath, QString(), password);
+    }
+}
+
+void ShareModel::createNewUserGroupShare(const ShareePtr &sharee)
+{
+    if (sharee.isNull()) {
+        return;
+    }
+
+    qCInfo(lcShareModel) << "Creating new user/group share for sharee: " << sharee->format();
+
+    if (sharee->type() == Sharee::Email &&
+        _accountState &&
+        !_accountState->account().isNull() &&
+        _accountState->account()->capabilities().isValid() &&
+        _accountState->account()->capabilities().shareEmailPasswordEnforced()) {
+
+        Q_EMIT requestPasswordForEmailSharee(sharee);
+        return;
+    }
+
+    _manager->createShare(_sharePath,
+                          Share::ShareType(sharee->type()),
+                          sharee->shareWith(),
+                          _maxSharingPermissions,
+                          {});
+}
+
+void ShareModel::createNewUserGroupShareWithPassword(const ShareePtr &sharee, const QString &password) const
+{
+    if (sharee.isNull()) {
+        return;
+    }
+
+    _manager->createShare(_sharePath,
+                          Share::ShareType(sharee->type()),
+                          sharee->shareWith(),
+                          _maxSharingPermissions,
+                          password);
+}
+
+void ShareModel::createNewUserGroupShareFromQml(const QVariant &sharee)
+{
+    const auto ptr = sharee.value<ShareePtr>();
+    createNewUserGroupShare(ptr);
+}
+
+void ShareModel::createNewUserGroupShareWithPasswordFromQml(const QVariant &sharee, const QString &password) const
+{
+    const auto ptr = sharee.value<ShareePtr>();
+    createNewUserGroupShareWithPassword(ptr, password);
+}
+
+void ShareModel::deleteShare(const SharePtr &share) const
+{
+    if(share.isNull()) {
+        return;
+    }
+
+    share->deleteShare();
+}
+
+void ShareModel::deleteShareFromQml(const QVariant &share) const
+{
+    const auto ptr = share.value<SharePtr>();
+    deleteShare(ptr);
+}
+
+// --------------------------- QPROPERTY methods --------------------------- //
+
+QString ShareModel::localPath() const
+{
+    return _localPath;
+}
+
+void ShareModel::setLocalPath(const QString &localPath)
+{
+    _localPath = localPath;
+    Q_EMIT localPathChanged();
+    updateData();
+}
+
+AccountState *ShareModel::accountState() const
+{
+    return _accountState;
+}
+
+void ShareModel::setAccountState(AccountState *accountState)
+{
+    _accountState = accountState;
+
+    // Change the server and account-related properties
+    connect(_accountState, &AccountState::stateChanged, this, &ShareModel::accountConnectedChanged);
+    connect(_accountState, &AccountState::stateChanged, this, &ShareModel::sharingEnabledChanged);
+    connect(_accountState, &AccountState::stateChanged, this, &ShareModel::publicLinkSharesEnabledChanged);
+    connect(_accountState, &AccountState::stateChanged, this, &ShareModel::userGroupSharingEnabledChanged);
+
+    Q_EMIT accountStateChanged();
+    Q_EMIT accountConnectedChanged();
+    Q_EMIT sharingEnabledChanged();
+    Q_EMIT publicLinkSharesEnabledChanged();
+    Q_EMIT userGroupSharingEnabledChanged();
+    updateData();
+}
+
+bool ShareModel::accountConnected() const
+{
+    return _accountState && _accountState->isConnected();
+}
+
+bool ShareModel::sharingEnabled() const
+{
+    return _accountState &&
+            _accountState->account() &&
+            _accountState->account()->capabilities().isValid() &&
+            _accountState->account()->capabilities().shareAPI();
+}
+
+bool ShareModel::publicLinkSharesEnabled() const
+{
+    return Theme::instance()->linkSharing() &&
+            _accountState &&
+            _accountState->account() &&
+            _accountState->account()->capabilities().isValid() &&
+            _accountState->account()->capabilities().sharePublicLink();
+}
+
+bool ShareModel::userGroupSharingEnabled() const
+{
+    return Theme::instance()->userGroupSharing();
+}
+
+bool ShareModel::fetchOngoing() const
+{
+    return _fetchOngoing;
+}
+
+bool ShareModel::hasInitialShareFetchCompleted() const
+{
+    return _hasInitialShareFetchCompleted;
+}
+
+bool ShareModel::canShare() const
+{
+    return _maxSharingPermissions & SharePermissionShare;
+}
+
+} // namespace OCC
diff --git a/src/gui/filedetails/sharemodel.h b/src/gui/filedetails/sharemodel.h
new file mode 100644 (file)
index 0000000..aa49897
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.
+ */
+
+#pragma once
+
+#include <QAbstractListModel>
+
+#include "accountstate.h"
+#include "folder.h"
+#include "sharemanager.h"
+#include "sharepermissions.h"
+
+namespace OCC {
+
+class ShareModel : public QAbstractListModel
+{
+    Q_OBJECT
+    Q_PROPERTY(AccountState* accountState READ accountState WRITE setAccountState NOTIFY accountStateChanged)
+    Q_PROPERTY(QString localPath READ localPath WRITE setLocalPath NOTIFY localPathChanged)
+    Q_PROPERTY(bool accountConnected READ accountConnected NOTIFY accountConnectedChanged)
+    Q_PROPERTY(bool sharingEnabled READ sharingEnabled NOTIFY sharingEnabledChanged)
+    Q_PROPERTY(bool publicLinkSharesEnabled READ publicLinkSharesEnabled NOTIFY publicLinkSharesEnabledChanged)
+    Q_PROPERTY(bool userGroupSharingEnabled READ userGroupSharingEnabled NOTIFY userGroupSharingEnabledChanged)
+    Q_PROPERTY(bool canShare READ canShare NOTIFY sharePermissionsChanged)
+    Q_PROPERTY(bool fetchOngoing READ fetchOngoing NOTIFY fetchOngoingChanged)
+    Q_PROPERTY(bool hasInitialShareFetchCompleted READ hasInitialShareFetchCompleted NOTIFY hasInitialShareFetchCompletedChanged)
+
+public:
+    enum Roles {
+        ShareRole = Qt::UserRole + 1,
+        ShareTypeRole,
+        ShareIdRole,
+        IconUrlRole,
+        AvatarUrlRole,
+        LinkRole,
+        LinkShareNameRole,
+        LinkShareLabelRole,
+        NoteEnabledRole,
+        NoteRole,
+        ExpireDateEnabledRole,
+        ExpireDateEnforcedRole,
+        ExpireDateRole,
+        EnforcedMaximumExpireDateRole,
+        PasswordProtectEnabledRole,
+        PasswordRole,
+        PasswordEnforcedRole,
+        EditingAllowedRole,
+    };
+    Q_ENUM(Roles)
+
+    /**
+     * Possible share types
+     * Need to be in sync with Share::ShareType.
+     * We use this in QML.
+     */
+    enum ShareType {
+        ShareTypeUser = Share::TypeUser,
+        ShareTypeGroup = Share::TypeGroup,
+        ShareTypeLink = Share::TypeLink,
+        ShareTypeEmail = Share::TypeEmail,
+        ShareTypeRemote = Share::TypeRemote,
+        ShareTypeCircle = Share::TypeCircle,
+        ShareTypeRoom = Share::TypeRoom,
+        ShareTypePlaceholderLink = Share::TypePlaceholderLink,
+    };
+    Q_ENUM(ShareType);
+
+    explicit ShareModel(QObject *parent = nullptr);
+
+    QVariant data(const QModelIndex &index, const int role) const override;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    QHash<int, QByteArray> roleNames() const override;
+
+    AccountState *accountState() const;
+    QString localPath() const;
+
+    bool accountConnected() const;
+    bool sharingEnabled() const;
+    bool publicLinkSharesEnabled() const;
+    bool userGroupSharingEnabled() const;
+    bool canShare() const;
+
+    bool fetchOngoing() const;
+    bool hasInitialShareFetchCompleted() const;
+
+signals:
+    void localPathChanged();
+    void accountStateChanged();
+    void accountConnectedChanged();
+    void sharingEnabledChanged();
+    void publicLinkSharesEnabledChanged();
+    void userGroupSharingEnabledChanged();
+    void sharePermissionsChanged();
+    void lockExpireStringChanged();
+    void fetchOngoingChanged();
+    void hasInitialShareFetchCompletedChanged();
+
+    void serverError(const int code, const QString &message);
+    void passwordSetError(const QString &shareId);
+    void requestPasswordForLinkShare();
+    void requestPasswordForEmailSharee(const ShareePtr &sharee);
+
+    void sharesChanged();
+
+public slots:
+    void setAccountState(AccountState *accountState);
+    void setLocalPath(const QString &localPath);
+
+    void createNewLinkShare() const;
+    void createNewLinkShareWithPassword(const QString &password) const;
+    void createNewUserGroupShare(const ShareePtr &sharee);
+    void createNewUserGroupShareFromQml(const QVariant &sharee);
+    void createNewUserGroupShareWithPassword(const ShareePtr &sharee, const QString &password) const;
+    void createNewUserGroupShareWithPasswordFromQml(const QVariant &sharee, const QString &password) const;
+
+    void deleteShare(const SharePtr &share) const;
+    void deleteShareFromQml(const QVariant &share) const;
+
+    void toggleShareAllowEditing(const SharePtr &share, const bool enable) const;
+    void toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) const;
+    void toggleShareAllowResharing(const SharePtr &share, const bool enable) const;
+    void toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) const;
+    void toggleSharePasswordProtect(const SharePtr &share, const bool enable);
+    void toggleSharePasswordProtectFromQml(const QVariant &share, const bool enable);
+    void toggleShareExpirationDate(const SharePtr &share, const bool enable) const;
+    void toggleShareExpirationDateFromQml(const QVariant &share, const bool enable) const;
+    void toggleShareNoteToRecipient(const SharePtr &share, const bool enable) const;
+    void toggleShareNoteToRecipientFromQml(const QVariant &share, const bool enable) const;
+
+    void setLinkShareLabel(const QSharedPointer<LinkShare> &linkShare, const QString &label) const;
+    void setLinkShareLabelFromQml(const QVariant &linkShare, const QString &label) const;
+    void setShareExpireDate(const SharePtr &share, const qint64 milliseconds) const;
+    // Needed as ints in QML are 32 bits so we need to use a QVariant
+    void setShareExpireDateFromQml(const QVariant &share, const QVariant milliseconds) const;
+    void setSharePassword(const SharePtr &share, const QString &password);
+    void setSharePasswordFromQml(const QVariant &share, const QString &password);
+    void setShareNote(const SharePtr &share, const QString &note) const;
+    void setShareNoteFromQml(const QVariant &share, const QString &note) const;
+
+private slots:
+    void resetData();
+    void updateData();
+    void initShareManager();
+
+    void slotPropfindReceived(const QVariantMap &result);
+    void slotServerError(const int code, const QString &message);
+    void slotAddShare(const SharePtr &share);
+    void slotRemoveShareWithId(const QString &shareId);
+    void slotSharesFetched(const QList<SharePtr> &shares);
+
+    void slotSharePermissionsSet(const QString &shareId);
+    void slotSharePasswordSet(const QString &shareId);
+    void slotShareNoteSet(const QString &shareId);
+    void slotShareNameSet(const QString &shareId);
+    void slotShareLabelSet(const QString &shareId);
+    void slotShareExpireDateSet(const QString &shareId);
+
+private:
+    QString displayStringForShare(const SharePtr &share) const;
+    QString iconUrlForShare(const SharePtr &share) const;
+    QString avatarUrlForShare(const SharePtr &share) const;
+    long long enforcedMaxExpireDateForShare(const SharePtr &share) const;
+    bool expireDateEnforcedForShare(const SharePtr &share) const;
+
+    bool _fetchOngoing = false;
+    bool _hasInitialShareFetchCompleted = false;
+    SharePtr _placeholderLinkShare;
+
+    // DO NOT USE QSHAREDPOINTERS HERE.
+    // QSharedPointers MUST NOT be used with pointers already assigned to other shared pointers.
+    // This is because they do not share reference counters, and as such are not aware of another
+    // smart pointer's use of the same object.
+    //
+    // We cannot pass objects instantiated in QML using smart pointers through the property interface
+    // so we have to pass the pointer here. If we kill the dialog using a smart pointer then
+    // these objects will be deallocated for the entire application. We do not want that!!
+    AccountState *_accountState;
+    Folder *_folder;
+
+    QString _localPath;
+    QString _sharePath;
+    SharePermissions _maxSharingPermissions;
+    QByteArray _numericFileId;
+    SyncJournalFileLockInfo _filelockState;
+    QString _privateLinkUrl;
+
+    QSharedPointer<ShareManager> _manager;
+
+    QVector<SharePtr> _shares;
+    QHash<QString, QPersistentModelIndex> _shareIdIndexHash;
+    QHash<QString, QString> _shareIdRecentlySetPasswords;
+};
+
+} // namespace OCC
diff --git a/src/gui/filedetails/sortedsharemodel.cpp b/src/gui/filedetails/sortedsharemodel.cpp
new file mode 100644 (file)
index 0000000..9906cfc
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.
+ */
+
+#include "sortedsharemodel.h"
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcSortedShareModel, "com.nextcloud.sortedsharemodel")
+
+SortedShareModel::SortedShareModel(QObject *parent)
+    : QSortFilterProxyModel(parent)
+{
+}
+
+void SortedShareModel::sortModel()
+{
+    sort(0);
+}
+
+ShareModel *SortedShareModel::shareModel() const
+{
+    return qobject_cast<ShareModel*>(sourceModel());
+}
+
+void SortedShareModel::setShareModel(ShareModel *shareModel)
+{
+    const auto currentSetModel = sourceModel();
+
+    if(currentSetModel) {
+        disconnect(currentSetModel, &ShareModel::rowsInserted, this, &SortedShareModel::sortModel);
+        disconnect(currentSetModel, &ShareModel::rowsMoved, this, &SortedShareModel::sortModel);
+        disconnect(currentSetModel, &ShareModel::rowsRemoved, this, &SortedShareModel::sortModel);
+        disconnect(currentSetModel, &ShareModel::dataChanged, this, &SortedShareModel::sortModel);
+        disconnect(currentSetModel, &ShareModel::modelReset, this, &SortedShareModel::sortModel);
+    }
+
+    // Re-sort model when any changes take place
+    connect(shareModel, &ShareModel::rowsInserted, this, &SortedShareModel::sortModel);
+    connect(shareModel, &ShareModel::rowsMoved, this, &SortedShareModel::sortModel);
+    connect(shareModel, &ShareModel::rowsRemoved, this, &SortedShareModel::sortModel);
+    connect(shareModel, &ShareModel::dataChanged, this, &SortedShareModel::sortModel);
+    connect(shareModel, &ShareModel::modelReset, this, &SortedShareModel::sortModel);
+
+    setSourceModel(shareModel);
+    sortModel();
+    Q_EMIT shareModelChanged();
+}
+
+bool SortedShareModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const
+{
+    if (!sourceLeft.isValid() || !sourceRight.isValid()) {
+        return false;
+    }
+
+    const auto leftShare = sourceLeft.data(ShareModel::ShareRole).value<SharePtr>();
+    const auto rightShare = sourceRight.data(ShareModel::ShareRole).value<SharePtr>();
+
+    if (leftShare.isNull() || rightShare.isNull()) {
+        return false;
+    }
+
+    const auto leftShareType = leftShare->getShareType();
+
+    // Placeholder link shares always go at top
+    if(leftShareType == Share::TypePlaceholderLink) {
+        return true;
+    }
+
+    const auto rightShareType = rightShare->getShareType();
+
+    // We want to place link shares at the top
+    if (leftShareType == Share::TypeLink && rightShareType != Share::TypeLink) {
+        return true;
+    } else if (rightShareType == Share::TypeLink && leftShareType != Share::TypeLink) {
+        return false;
+    } else if (leftShareType != rightShareType) {
+        return leftShareType < rightShareType;
+    }
+
+    if (leftShareType == Share::TypeLink) {
+        const auto leftLinkShare = leftShare.objectCast<LinkShare>();
+        const auto rightLinkShare = rightShare.objectCast<LinkShare>();
+
+        if(leftLinkShare.isNull() || rightLinkShare.isNull()) {
+            qCWarning(lcSortedShareModel) << "One of compared shares is a null pointer after conversion despite having same share type. Left link share is null:" << leftLinkShare.isNull()
+                                          << "Right link share is null: " << rightLinkShare.isNull();
+            return false;
+        }
+
+        return leftLinkShare->getLabel() < rightLinkShare->getLabel();
+
+    } else if (leftShare->getShareWith()) {
+        if(rightShare->getShareWith().isNull()) {
+            return true;
+        }
+
+        return leftShare->getShareWith()->format() < rightShare->getShareWith()->format();
+    }
+
+    return false;
+}
+
+} // namespace OCC
diff --git a/src/gui/filedetails/sortedsharemodel.h b/src/gui/filedetails/sortedsharemodel.h
new file mode 100644 (file)
index 0000000..c5cdbb9
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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.
+ */
+
+#pragma once
+
+#include <QSortFilterProxyModel>
+#include "sharemodel.h"
+
+namespace OCC {
+
+class SortedShareModel : public QSortFilterProxyModel
+{
+    Q_OBJECT
+    Q_PROPERTY(ShareModel* shareModel READ shareModel WRITE setShareModel NOTIFY shareModelChanged)
+
+public:
+    explicit SortedShareModel(QObject *parent = nullptr);
+
+    ShareModel *shareModel() const;
+
+signals:
+    void shareModelChanged();
+
+public slots:
+    void setShareModel(ShareModel *shareModel);
+
+protected:
+    bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
+
+private slots:
+    void sortModel();
+};
+
+} // namespace OCC
index 8d57a72b844f3f65d505524e2dba659ff2ef8ca7..b71a9593da261890004cee74b2dc516145de0218 100644 (file)
@@ -24,6 +24,7 @@
 #include "filesystem.h"
 #include "lockwatcher.h"
 #include "common/asserts.h"
+#include "gui/systray.h"
 #include <pushnotifications.h>
 #include <syncengine.h>
 
index ab96fe8315e08cc84f775daeed2c969da8bab306..5ef4b7e4c1ea25e0fd0de4822967222c7b8d6e3e 100644 (file)
 #include "owncloudsetupwizard.h"
 #include "progressdispatcher.h"
 #include "settingsdialog.h"
-#include "sharedialog.h"
 #include "theme.h"
 #include "wheelhandler.h"
-#include "common/syncjournalfilerecord.h"
-#include "creds/abstractcredentials.h"
+#include "filedetails/filedetails.h"
+#include "filedetails/shareemodel.h"
+#include "filedetails/sharemodel.h"
+#include "filedetails/sortedsharemodel.h"
 #include "tray/sortedactivitylistmodel.h"
 #include "tray/syncstatussummary.h"
 #include "tray/unifiedsearchresultslistmodel.h"
@@ -97,11 +98,6 @@ ownCloudGui::ownCloudGui(Application *parent)
     connect(_tray.data(), &Systray::shutdown,
         this, &ownCloudGui::slotShutdown);
 
-    connect(_tray.data(), &Systray::openShareDialog,
-        this, [=](const QString &sharePath, const QString &localPath) {
-                slotShowShareDialog(sharePath, localPath, ShareDialogStartPage::UsersAndGroups);
-            });
-
     ProgressDispatcher *pd = ProgressDispatcher::instance();
     connect(pd, &ProgressDispatcher::progressInfo, this,
         &ownCloudGui::slotUpdateProgress);
@@ -125,6 +121,10 @@ ownCloudGui::ownCloudGui(Application *parent)
     qmlRegisterType<SortedActivityListModel>("com.nextcloud.desktopclient", 1, 0, "SortedActivityListModel");
     qmlRegisterType<WheelHandler>("com.nextcloud.desktopclient", 1, 0, "WheelHandler");
     qmlRegisterType<CallStateChecker>("com.nextcloud.desktopclient", 1, 0, "CallStateChecker");
+    qmlRegisterType<FileDetails>("com.nextcloud.desktopclient", 1, 0, "FileDetails");
+    qmlRegisterType<ShareModel>("com.nextcloud.desktopclient", 1, 0, "ShareModel");
+    qmlRegisterType<ShareeModel>("com.nextcloud.desktopclient", 1, 0, "ShareeModel");
+    qmlRegisterType<SortedShareModel>("com.nextcloud.desktopclient", 1, 0, "SortedShareModel");
 
     qmlRegisterUncreatableType<UnifiedSearchResultsListModel>("com.nextcloud.desktopclient", 1, 0, "UnifiedSearchResultsListModel", "UnifiedSearchResultsListModel");
     qmlRegisterUncreatableType<UserStatus>("com.nextcloud.desktopclient", 1, 0, "UserStatus", "Access to Status enum");
@@ -134,6 +134,8 @@ ownCloudGui::ownCloudGui(Application *parent)
     qRegisterMetaType<ActivityListModel *>("ActivityListModel*");
     qRegisterMetaType<UnifiedSearchResultsListModel *>("UnifiedSearchResultsListModel*");
     qRegisterMetaType<UserStatus>("UserStatus");
+    qRegisterMetaType<SharePtr>("SharePtr");
+    qRegisterMetaType<ShareePtr>("ShareePtr");
 
     qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "UserModel", UserModel::instance());
     qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "UserAppsModel", UserAppsModel::instance());
@@ -196,12 +198,8 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
     } else if (reason == QSystemTrayIcon::Trigger) {
         if (OwncloudSetupWizard::bringWizardToFrontIfVisible()) {
             // brought wizard to front
-        } else if (_shareDialogs.size() > 0) {
-            // Share dialog(s) be hidden by other apps, bring them back
-            Q_FOREACH (const QPointer<ShareDialog> &shareDialog, _shareDialogs) {
-                Q_ASSERT(shareDialog.data());
-                raiseDialog(shareDialog);
-            }
+        } else if (_tray->raiseDialogs()) {
+            // Brings dialogs hidden by other apps to front, returns true if any raised
         } else if (_tray->isOpen()) {
             _tray->hideWindow();
         } else {
@@ -652,54 +650,14 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget)
 }
 
 
-void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage)
+void ownCloudGui::slotShowShareDialog(const QString &localPath) const
 {
-    const auto folder = FolderMan::instance()->folderForPath(localPath);
-    if (!folder) {
-        qCWarning(lcApplication) << "Could not open share dialog for" << localPath << "no responsible folder found";
-        return;
-    }
-
-    const auto accountState = folder->accountState();
-
-    const QString file = localPath.mid(folder->cleanPath().length() + 1);
-    SyncJournalFileRecord fileRecord;
-
-    bool resharingAllowed = true; // lets assume the good
-    if (folder->journalDb()->getFileRecord(file, &fileRecord) && fileRecord.isValid()) {
-        // check the permission: Is resharing allowed?
-        if (!fileRecord._remotePerm.isNull() && !fileRecord._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
-            resharingAllowed = false;
-        }
-    }
-
-    auto maxSharingPermissions = resharingAllowed? SharePermissions(accountState->account()->capabilities().shareDefaultPermissions()) : SharePermissions({});
-
-    ShareDialog *w = nullptr;
-    if (_shareDialogs.contains(localPath) && _shareDialogs[localPath]) {
-        qCInfo(lcApplication) << "Raising share dialog" << sharePath << localPath;
-        w = _shareDialogs[localPath];
-    } else {
-        qCInfo(lcApplication) << "Opening share dialog" << sharePath << localPath << maxSharingPermissions;
-        w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId(), fileRecord._lockstate, startPage);
-        w->setAttribute(Qt::WA_DeleteOnClose, true);
-
-        _shareDialogs[localPath] = w;
-        connect(w, &QObject::destroyed, this, &ownCloudGui::slotRemoveDestroyedShareDialogs);
-    }
-    raiseDialog(w);
+    _tray->createShareDialog(localPath);
 }
 
-void ownCloudGui::slotRemoveDestroyedShareDialogs()
+void ownCloudGui::slotShowFileActivityDialog(const QString &localPath) const
 {
-    QMutableMapIterator<QString, QPointer<ShareDialog>> it(_shareDialogs);
-    while (it.hasNext()) {
-        it.next();
-        if (!it.value() || it.value() == sender()) {
-            it.remove();
-        }
-    }
+    _tray->createFileActivityDialog(localPath);
 }
 
-
 } // end namespace
index 3ffa57cd1320f3fe35948d4e55826ada789700c0..7b40d520ddc1dc6f24d413ecaf0c7cac620308e0 100644 (file)
@@ -100,14 +100,11 @@ public slots:
     /**
      * Open a share dialog for a file or folder.
      *
-     * sharePath is the full remote path to the item,
      * localPath is the absolute local path to it (so not relative
      * to the folder).
      */
-    void slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage);
-
-    void slotRemoveDestroyedShareDialogs();
-
+    void slotShowShareDialog(const QString &localPath) const;
+    void slotShowFileActivityDialog(const QString &localPath) const;
     void slotNewAccountWizard();
 
 private slots:
@@ -123,8 +120,6 @@ private:
     QDBusConnection _bus;
 #endif
 
-    QMap<QString, QPointer<ShareDialog>> _shareDialogs;
-
     QAction *_actionNewAccountWizard;
     QAction *_actionSettings;
     QAction *_actionEstimate;
diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp
deleted file mode 100644 (file)
index 99a0b83..0000000
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
- *
- * 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.
- */
-
-#include "ui_sharedialog.h"
-#include "sharedialog.h"
-#include "sharee.h"
-#include "sharelinkwidget.h"
-#include "internallinkwidget.h"
-#include "shareusergroupwidget.h"
-#include "passwordinputdialog.h"
-
-#include "sharemanager.h"
-
-#include "account.h"
-#include "accountstate.h"
-#include "configfile.h"
-#include "theme.h"
-#include "thumbnailjob.h"
-#include "wordlist.h"
-
-#include <QFileInfo>
-#include <QFileIconProvider>
-#include <QInputDialog>
-#include <QPointer>
-#include <QPushButton>
-#include <QFrame>
-#include <QScrollBar>
-
-namespace {
-QString createRandomPassword()
-{
-    const auto words = OCC::WordList::getRandomWords(10);
-
-    const auto addFirstLetter = [](const QString &current, const QString &next) -> QString {
-        return current + next.at(0);
-    };
-
-    return std::accumulate(std::cbegin(words), std::cend(words), QString(), addFirstLetter);
-}
-}
-
-
-namespace OCC {
-
-static const int thumbnailSize = 40;
-
-ShareDialog::ShareDialog(QPointer<AccountState> accountState,
-    const QString &sharePath,
-    const QString &localPath,
-    SharePermissions maxSharingPermissions,
-    const QByteArray &numericFileId,
-    SyncJournalFileLockInfo filelockState,
-    ShareDialogStartPage startPage,
-    QWidget *parent)
-    : QDialog(parent)
-    , _ui(new Ui::ShareDialog)
-    , _accountState(accountState)
-    , _sharePath(sharePath)
-    , _localPath(localPath)
-    , _maxSharingPermissions(maxSharingPermissions)
-    , _filelockState(std::move(filelockState))
-    , _privateLinkUrl(accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded))
-    , _startPage(startPage)
-{
-    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
-    setAttribute(Qt::WA_DeleteOnClose);
-    setObjectName("SharingDialog"); // required as group for saveGeometry call
-
-    _ui->setupUi(this);
-
-    // We want to act on account state changes
-    connect(_accountState.data(), &AccountState::stateChanged, this, &ShareDialog::slotAccountStateChanged);
-
-    // Set icon
-    QFileInfo f_info(_localPath);
-    QFileIconProvider icon_provider;
-    QIcon icon = icon_provider.icon(f_info);
-    auto pixmap = icon.pixmap(thumbnailSize, thumbnailSize);
-    if (pixmap.width() > 0) {
-        _ui->label_icon->setPixmap(pixmap);
-    }
-
-    // Set filename
-    QString fileName = QFileInfo(_sharePath).fileName();
-    _ui->label_name->setText(tr("%1").arg(fileName));
-    QFont f(_ui->label_name->font());
-    f.setPointSize(qRound(f.pointSize() * 1.4));
-    _ui->label_name->setFont(f);
-
-    if (_filelockState._locked) {
-        static constexpr auto SECONDS_PER_MINUTE = 60;
-        const auto lockExpirationTime = _filelockState._lockTime + _filelockState._lockTimeout;
-        const auto remainingTime = QDateTime::currentDateTime().secsTo(QDateTime::fromSecsSinceEpoch(lockExpirationTime));
-        const auto remainingTimeInMinute = static_cast<int>(remainingTime > 0 ? remainingTime / SECONDS_PER_MINUTE : 0);
-        _ui->label_lockinfo->setText(tr("Locked by %1 - Expires in %2 minutes", "remaining time before lock expires", remainingTimeInMinute).arg(_filelockState._lockOwnerDisplayName).arg(remainingTimeInMinute));
-    } else {
-        _ui->label_lockinfo->setVisible(false);
-    }
-
-    QString ocDir(_sharePath);
-    ocDir.truncate(ocDir.length() - fileName.length());
-
-    ocDir.replace(QRegularExpression("^/*"), "");
-    ocDir.replace(QRegularExpression("/*$"), "");
-
-    // Laying this out is complex because sharePath
-    // may be in use or not.
-    _ui->gridLayout->removeWidget(_ui->label_sharePath);
-    _ui->gridLayout->removeWidget(_ui->label_name);
-    if (ocDir.isEmpty()) {
-        _ui->gridLayout->addWidget(_ui->label_name, 0, 1, 2, 1);
-        _ui->label_sharePath->setText(QString());
-    } else {
-        _ui->gridLayout->addWidget(_ui->label_name, 0, 1, 1, 1);
-        _ui->gridLayout->addWidget(_ui->label_sharePath, 1, 1, 1, 1);
-        _ui->label_sharePath->setText(tr("Folder: %2").arg(ocDir));
-    }
-
-    this->setWindowTitle(tr("%1 Sharing").arg(Theme::instance()->appNameGUI()));
-
-    if (!accountState->account()->capabilities().shareAPI()) {
-        return;
-    }
-
-    if (QFileInfo(_localPath).isFile()) {
-        auto *job = new ThumbnailJob(_sharePath, _accountState->account(), this);
-        connect(job, &ThumbnailJob::jobFinished, this, &ShareDialog::slotThumbnailFetched);
-        job->start();
-    }
-
-    auto job = new PropfindJob(accountState->account(), _sharePath);
-    job->setProperties(
-        QList<QByteArray>()
-        << "http://open-collaboration-services.org/ns:share-permissions"
-        << "http://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
-        << "http://owncloud.org/ns:privatelink");
-    job->setTimeout(10 * 1000);
-    connect(job, &PropfindJob::result, this, &ShareDialog::slotPropfindReceived);
-    connect(job, &PropfindJob::finishedWithError, this, &ShareDialog::slotPropfindError);
-    job->start();
-
-    initShareManager();
-    
-    _scrollAreaViewPort = new QWidget(_ui->scrollArea);
-    _scrollAreaLayout = new QVBoxLayout(_scrollAreaViewPort);
-    _scrollAreaLayout->setContentsMargins(0, 0, 0, 0);
-    _ui->scrollArea->setWidget(_scrollAreaViewPort);
-
-    _internalLinkWidget = new InternalLinkWidget(localPath, this);
-    _ui->verticalLayout->addWidget(_internalLinkWidget);
-    _internalLinkWidget->setupUiOptions();
-    connect(this, &ShareDialog::styleChanged, _internalLinkWidget, &InternalLinkWidget::slotStyleChanged);
-
-    adjustScrollWidget();
-}
-
-ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare)
-{
-    const auto linkShareWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _ui->scrollArea);
-    _linkWidgetList.append(linkShareWidget);
-
-    linkShareWidget->setLinkShare(linkShare);
-
-    connect(linkShare.data(), &Share::serverError, linkShareWidget, &ShareLinkWidget::slotServerError);
-    connect(linkShare.data(), &Share::shareDeleted, linkShareWidget, &ShareLinkWidget::slotDeleteShareFetched);
-
-    if(_manager) {
-        connect(_manager, &ShareManager::serverError, linkShareWidget, &ShareLinkWidget::slotServerError);
-    }
-
-    // Connect all shares signals to gui slots
-    connect(this, &ShareDialog::toggleShareLinkAnimation, linkShareWidget, &ShareLinkWidget::slotToggleShareLinkAnimation);
-    connect(linkShareWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
-    connect(linkShareWidget, &ShareLinkWidget::deleteLinkShare, this, &ShareDialog::slotDeleteShare);
-    connect(linkShareWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare);
-    
-    // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
-    connect(this, &ShareDialog::styleChanged, linkShareWidget, &ShareLinkWidget::slotStyleChanged);
-
-    _ui->verticalLayout->insertWidget(_linkWidgetList.size() + 1, linkShareWidget);
-    _scrollAreaLayout->addWidget(linkShareWidget);
-    
-    linkShareWidget->setupUiOptions();
-    adjustScrollWidget();
-
-    return linkShareWidget;
-}
-
-void ShareDialog::initLinkShareWidget()
-{
-    if(_linkWidgetList.size() == 0) {
-        _emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _ui->scrollArea);
-        _linkWidgetList.append(_emptyShareLinkWidget);
-
-        _emptyShareLinkWidget->slotStyleChanged(); // Get the initial customizeStyle() to happen
-
-        connect(this, &ShareDialog::toggleShareLinkAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleShareLinkAnimation);
-        connect(this, &ShareDialog::styleChanged, _emptyShareLinkWidget, &ShareLinkWidget::slotStyleChanged);
-
-        connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
-        connect(_emptyShareLinkWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare);
-
-        _ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _emptyShareLinkWidget);
-        _scrollAreaLayout->addWidget(_emptyShareLinkWidget);
-        _emptyShareLinkWidget->show();
-    } else if (_emptyShareLinkWidget) {
-        _emptyShareLinkWidget->hide();
-        _ui->verticalLayout->removeWidget(_emptyShareLinkWidget);
-        _linkWidgetList.removeAll(_emptyShareLinkWidget);
-        _emptyShareLinkWidget = nullptr;
-    }
-
-    adjustScrollWidget();
-}
-
-void ShareDialog::slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare)
-{
-    emit toggleShareLinkAnimation(true);
-    const auto addedLinkShareWidget = addLinkShareWidget(linkShare);
-    initLinkShareWidget();
-    if (linkShare->isPasswordSet()) {
-        addedLinkShareWidget->focusPasswordLineEdit();
-    }
-    emit toggleShareLinkAnimation(false);
-}
-
-void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
-{
-    emit toggleShareLinkAnimation(true);
-
-    const QString versionString = _accountState->account()->serverVersion();
-    qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares";
-    
-    foreach (auto share, shares) {
-        if (share->getShareType() != Share::TypeLink || share->getUidOwner() != share->account()->davUser()) {
-            continue;
-        }
-
-        QSharedPointer<LinkShare> linkShare = qSharedPointerDynamicCast<LinkShare>(share);
-        addLinkShareWidget(linkShare);
-    }
-
-    initLinkShareWidget();
-    emit toggleShareLinkAnimation(false);
-}
-
-void ShareDialog::adjustScrollWidget()
-{
-    _ui->scrollArea->setVisible(_scrollAreaLayout->count() > 0);
-
-    // Sometimes the contentRect returns a height of 0, so we need a backup plan
-    const auto scrollAreaContentHeight = _scrollAreaLayout->contentsRect().height();
-
-    auto linkWidgetHeights = 0;
-
-    if(scrollAreaContentHeight == 0 && !_linkWidgetList.empty()) {
-        for (const auto linkWidget : _linkWidgetList) {
-            linkWidgetHeights += linkWidget->height() - 10;
-        }
-    }
-
-    const auto overAvailableHeight = scrollAreaContentHeight > _ui->scrollArea->height() ||
-            linkWidgetHeights > _ui->scrollArea->height();
-
-    _ui->scrollArea->setFrameShape(overAvailableHeight ? QFrame::StyledPanel : QFrame::NoFrame);
-    _ui->verticalLayout->setSpacing(overAvailableHeight ? 10 : 0);
-}
-
-ShareDialog::~ShareDialog()
-{
-    _linkWidgetList.clear();
-    delete _ui;
-}
-
-void ShareDialog::done(int r)
-{
-    ConfigFile cfg;
-    cfg.saveGeometry(this);
-    QDialog::done(r);
-}
-
-void ShareDialog::slotPropfindReceived(const QVariantMap &result)
-{
-    const QVariant receivedPermissions = result["share-permissions"];
-    if (!receivedPermissions.toString().isEmpty()) {
-        _maxSharingPermissions = static_cast<SharePermissions>(receivedPermissions.toInt());
-        qCInfo(lcSharing) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions;
-    }
-    auto privateLinkUrl = result["privatelink"].toString();
-    auto numericFileId = result["fileid"].toByteArray();
-    if (!privateLinkUrl.isEmpty()) {
-        qCInfo(lcSharing) << "Received private link url for" << _sharePath << privateLinkUrl;
-        _privateLinkUrl = privateLinkUrl;
-    } else if (!numericFileId.isEmpty()) {
-        qCInfo(lcSharing) << "Received numeric file id for" << _sharePath << numericFileId;
-        _privateLinkUrl = _accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded);
-    }
-
-    showSharingUi();
-}
-
-void ShareDialog::slotPropfindError()
-{
-    // On error show the share ui anyway. The user can still see shares,
-    // delete them and so on, even though adding new shares or granting
-    // some of the permissions might fail.
-
-    showSharingUi();
-}
-
-void ShareDialog::showSharingUi()
-{
-    auto theme = Theme::instance();
-
-    // There's no difference between being unable to reshare and
-    // being unable to reshare with reshare permission.
-    bool canReshare = _maxSharingPermissions & SharePermissionShare;
-
-    if (!canReshare) {
-        auto label = new QLabel(this);
-        label->setText(tr("The file cannot be shared because it does not have sharing permission."));
-        label->setWordWrap(true);
-        _ui->verticalLayout->insertWidget(1, label);
-        return;
-    }
-
-    if (theme->userGroupSharing()) {
-        _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, _ui->scrollArea);
-        _userGroupWidget->getShares();
-        
-        // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
-        connect(this, &ShareDialog::styleChanged, _userGroupWidget, &ShareUserGroupWidget::slotStyleChanged);
-
-        _userGroupWidget->slotStyleChanged();
-
-        _ui->verticalLayout->insertWidget(1, _userGroupWidget);
-        _scrollAreaLayout->addLayout(_userGroupWidget->shareUserGroupLayout());
-    }
-
-    initShareManager();
-
-    if (theme->linkSharing()) {
-        if(_manager) {
-            _manager->fetchShares(_sharePath);
-        }
-    }
-
-    adjustScrollWidget();
-}
-
-void ShareDialog::initShareManager()
-{
-    bool sharingPossible = true;
-    if (!_accountState->account()->capabilities().sharePublicLink()) {
-        qCWarning(lcSharing) << "Link shares have been disabled";
-        sharingPossible = false;
-    } else if (!(_maxSharingPermissions & SharePermissionShare)) {
-        qCWarning(lcSharing) << "The file cannot be shared because it does not have sharing permission.";
-        sharingPossible = false;
-    }
-
-    if (!_manager && sharingPossible) {
-        _manager = new ShareManager(_accountState->account(), this);
-        connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
-        connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
-        connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareDialog::slotLinkShareRequiresPassword);
-    }
-}
-
-void ShareDialog::slotCreateLinkShare()
-{
-    if(_manager) {
-        const auto askOptionalPassword = _accountState->account()->capabilities().sharePublicLinkAskOptionalPassword();
-        const auto password = askOptionalPassword ? createRandomPassword() : QString();
-        _manager->createLinkShare(_sharePath, QString(), password);
-    }
-}
-
-void ShareDialog::slotCreatePasswordForLinkShare(const QString &password)
-{
-    const auto shareLinkWidget = qobject_cast<ShareLinkWidget*>(sender());
-    Q_ASSERT(shareLinkWidget);
-    if (shareLinkWidget) {
-        connect(_manager, &ShareManager::linkShareRequiresPassword, shareLinkWidget, &ShareLinkWidget::slotCreateShareRequiresPassword);
-        connect(shareLinkWidget, &ShareLinkWidget::createPasswordProcessed, this, &ShareDialog::slotCreatePasswordForLinkShareProcessed);
-        shareLinkWidget->getLinkShare()->setPassword(password);
-    } else {
-        qCCritical(lcSharing) << "shareLinkWidget is not a sender!";
-    }
-}
-
-void ShareDialog::slotCreatePasswordForLinkShareProcessed()
-{
-    const auto shareLinkWidget = qobject_cast<ShareLinkWidget*>(sender());
-    Q_ASSERT(shareLinkWidget);
-    if (shareLinkWidget) {
-        disconnect(_manager, &ShareManager::linkShareRequiresPassword, shareLinkWidget, &ShareLinkWidget::slotCreateShareRequiresPassword);
-        disconnect(shareLinkWidget, &ShareLinkWidget::createPasswordProcessed, this, &ShareDialog::slotCreatePasswordForLinkShareProcessed);
-    } else {
-        qCCritical(lcSharing) << "shareLinkWidget is not a sender!";
-    }
-}
-
-void ShareDialog::slotLinkShareRequiresPassword(const QString &message)
-{
-    const auto passwordInputDialog = new PasswordInputDialog(tr("Please enter a password for your link share:"), message, this);
-    passwordInputDialog->setWindowTitle(tr("Password for share required"));
-    passwordInputDialog->setAttribute(Qt::WA_DeleteOnClose);
-    passwordInputDialog->open();
-
-    connect(passwordInputDialog, &QDialog::finished, this, [this, passwordInputDialog](const int result) {
-        if (result == QDialog::Accepted && _manager) {
-            // Try to create the link share again with the newly entered password
-            _manager->createLinkShare(_sharePath, QString(), passwordInputDialog->password());
-            return;
-        }
-        emit toggleShareLinkAnimation(false);
-    });
-}
-
-void ShareDialog::slotDeleteShare()
-{
-    auto sharelinkWidget = dynamic_cast<ShareLinkWidget*>(sender());
-    sharelinkWidget->hide();
-    _ui->verticalLayout->removeWidget(sharelinkWidget);
-    _scrollAreaLayout->removeWidget(sharelinkWidget);
-    _linkWidgetList.removeAll(sharelinkWidget);
-    initLinkShareWidget();
-}
-
-void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &reply)
-{
-    if (statusCode != 200) {
-        qCWarning(lcSharing) << "Thumbnail status code: " << statusCode;
-        return;
-    }
-
-    QPixmap p;
-    p.loadFromData(reply, "PNG");
-    p = p.scaledToHeight(thumbnailSize, Qt::SmoothTransformation);
-    _ui->label_icon->setPixmap(p);
-    _ui->label_icon->show();
-}
-
-void ShareDialog::slotAccountStateChanged(int state)
-{
-    bool enabled = (state == AccountState::State::Connected);
-    qCDebug(lcSharing) << "Account connected?" << enabled;
-
-    if (_userGroupWidget) {
-        _userGroupWidget->setEnabled(enabled);
-    }
-
-    if(_linkWidgetList.size() > 0){
-        foreach(ShareLinkWidget *widget, _linkWidgetList){
-            widget->setEnabled(state);
-        }
-    }
-}
-
-void ShareDialog::changeEvent(QEvent *e)
-{
-    switch (e->type()) {
-    case QEvent::StyleChange:
-    case QEvent::PaletteChange:
-    case QEvent::ThemeChange:
-        // Notify the other widgets (Dark-/Light-Mode switching)
-        emit styleChanged();
-        break;
-    default:
-        break;
-    }
-
-    QDialog::changeEvent(e);
-}
-
-void ShareDialog::resizeEvent(QResizeEvent *event)
-{
-    adjustScrollWidget();
-    QDialog::resizeEvent(event);
-}
-
-} // namespace OCC
diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h
deleted file mode 100644 (file)
index 89bb2ef..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
- *
- * 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.
- */
-
-#ifndef SHAREDIALOG_H
-#define SHAREDIALOG_H
-
-#include "accountstate.h"
-#include "sharepermissions.h"
-#include "owncloudgui.h"
-#include "common/syncjournalfilerecord.h"
-
-#include <QSharedPointer>
-#include <QPointer>
-#include <QString>
-#include <QDialog>
-#include <QWidget>
-
-class QProgressIndicator;
-class QVBoxLayout;
-
-namespace OCC {
-
-namespace Ui {
-    class ShareDialog;
-}
-
-class ShareLinkWidget;
-class InternalLinkWidget;
-class ShareUserGroupWidget;
-class ShareManager;
-class LinkShare;
-class Share;
-
-class ShareDialog : public QDialog
-{
-    Q_OBJECT
-
-public:
-    explicit ShareDialog(QPointer<AccountState> accountState,
-        const QString &sharePath,
-        const QString &localPath,
-        SharePermissions maxSharingPermissions,
-        const QByteArray &numericFileId,
-        SyncJournalFileLockInfo filelockState,
-        ShareDialogStartPage startPage,
-        QWidget *parent = nullptr);
-    ~ShareDialog() override;
-
-private slots:
-    void done(int r) override;
-    void slotPropfindReceived(const QVariantMap &result);
-    void slotPropfindError();
-    void slotThumbnailFetched(const int &statusCode, const QByteArray &reply);
-    void slotAccountStateChanged(int state);
-
-    void slotSharesFetched(const QList<QSharedPointer<Share>> &shares);
-    void slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
-    void slotDeleteShare();
-    void slotCreateLinkShare();
-    void slotCreatePasswordForLinkShare(const QString &password);
-    void slotCreatePasswordForLinkShareProcessed();
-    void slotLinkShareRequiresPassword(const QString &message);
-
-signals:
-    void toggleShareLinkAnimation(bool start);
-    void styleChanged();
-
-protected:
-    void changeEvent(QEvent *) override;
-    void resizeEvent(QResizeEvent *event) override;
-
-private:
-    void showSharingUi();
-    void initShareManager();
-    ShareLinkWidget *addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
-    void initLinkShareWidget();
-    void adjustScrollWidget();
-
-    Ui::ShareDialog *_ui;
-
-    QPointer<AccountState> _accountState;
-    QString _sharePath;
-    QString _localPath;
-    SharePermissions _maxSharingPermissions;
-    QByteArray _numericFileId;
-    SyncJournalFileLockInfo _filelockState;
-    QString _privateLinkUrl;
-    ShareDialogStartPage _startPage;
-    ShareManager *_manager = nullptr;
-
-    QList<ShareLinkWidget*> _linkWidgetList;
-    ShareLinkWidget* _emptyShareLinkWidget = nullptr;
-    InternalLinkWidget* _internalLinkWidget = nullptr;
-    ShareUserGroupWidget *_userGroupWidget = nullptr;
-    QProgressIndicator *_progressIndicator = nullptr;
-    
-    QWidget *_scrollAreaViewPort = nullptr;
-    QVBoxLayout *_scrollAreaLayout = nullptr;
-};
-
-} // namespace OCC
-
-#endif // SHAREDIALOG_H
diff --git a/src/gui/sharedialog.ui b/src/gui/sharedialog.ui
deleted file mode 100644 (file)
index 8e4bbbf..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>OCC::ShareDialog</class>
- <widget class="QDialog" name="OCC::ShareDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>385</width>
-    <height>400</height>
-   </rect>
-  </property>
-  <property name="sizePolicy">
-   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-    <horstretch>0</horstretch>
-    <verstretch>0</verstretch>
-   </sizepolicy>
-  </property>
-  <property name="minimumSize">
-   <size>
-    <width>320</width>
-    <height>240</height>
-   </size>
-  </property>
-  <layout class="QVBoxLayout" name="shareDialogVerticalLayout">
-   <property name="spacing">
-    <number>0</number>
-   </property>
-   <property name="sizeConstraint">
-    <enum>QLayout::SetMinimumSize</enum>
-   </property>
-   <item>
-    <layout class="QVBoxLayout" name="verticalLayout">
-     <property name="spacing">
-      <number>0</number>
-     </property>
-     <property name="sizeConstraint">
-      <enum>QLayout::SetDefaultConstraint</enum>
-     </property>
-     <item>
-      <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0" columnstretch="0,0">
-       <property name="leftMargin">
-        <number>0</number>
-       </property>
-       <property name="topMargin">
-        <number>0</number>
-       </property>
-       <property name="rightMargin">
-        <number>0</number>
-       </property>
-       <property name="spacing">
-        <number>2</number>
-       </property>
-       <item row="0" column="1">
-        <widget class="QLabel" name="label_name">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>315</width>
-           <height>0</height>
-          </size>
-         </property>
-         <property name="text">
-          <string>share label</string>
-         </property>
-         <property name="textFormat">
-          <enum>Qt::PlainText</enum>
-         </property>
-         <property name="wordWrap">
-          <bool>true</bool>
-         </property>
-        </widget>
-       </item>
-       <item row="3" column="1">
-        <widget class="QLabel" name="label_lockinfo">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>315</width>
-           <height>0</height>
-          </size>
-         </property>
-         <property name="text">
-          <string notr="true">TextLabel</string>
-         </property>
-         <property name="textFormat">
-          <enum>Qt::PlainText</enum>
-         </property>
-         <property name="wordWrap">
-          <bool>true</bool>
-         </property>
-        </widget>
-       </item>
-       <item row="1" column="1">
-        <widget class="QLabel" name="label_sharePath">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>315</width>
-           <height>0</height>
-          </size>
-         </property>
-         <property name="font">
-          <font>
-           <bold>false</bold>
-          </font>
-         </property>
-         <property name="text">
-          <string>Nextcloud Path:</string>
-         </property>
-         <property name="textFormat">
-          <enum>Qt::PlainText</enum>
-         </property>
-         <property name="wordWrap">
-          <bool>true</bool>
-         </property>
-        </widget>
-       </item>
-       <item row="0" column="0" rowspan="4">
-        <widget class="QLabel" name="label_icon">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>40</width>
-           <height>40</height>
-          </size>
-         </property>
-         <property name="maximumSize">
-          <size>
-           <width>16777215</width>
-           <height>16777215</height>
-          </size>
-         </property>
-         <property name="text">
-          <string>Icon</string>
-         </property>
-        </widget>
-       </item>
-      </layout>
-     </item>
-     <item>
-      <widget class="QScrollArea" name="scrollArea">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>0</width>
-         <height>0</height>
-        </size>
-       </property>
-       <property name="frameShape">
-        <enum>QFrame::NoFrame</enum>
-       </property>
-       <property name="frameShadow">
-        <enum>QFrame::Plain</enum>
-       </property>
-       <property name="verticalScrollBarPolicy">
-        <enum>Qt::ScrollBarAsNeeded</enum>
-       </property>
-       <property name="horizontalScrollBarPolicy">
-        <enum>Qt::ScrollBarAlwaysOff</enum>
-       </property>
-       <property name="sizeAdjustPolicy">
-        <enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
-       </property>
-       <property name="widgetResizable">
-        <bool>true</bool>
-       </property>
-       <widget class="QWidget" name="scrollAreaWidgetContents">
-        <property name="geometry">
-         <rect>
-          <x>0</x>
-          <y>0</y>
-          <width>359</width>
-          <height>320</height>
-         </rect>
-        </property>
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-       </widget>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
index ed447e2a8ebc601bb539e53a4821bdcfa78b75cf..206a9d0e6c86754325aa9da9fc7f13282b6f0b87 100644 (file)
@@ -66,160 +66,4 @@ Sharee::Type Sharee::type() const
     return _type;
 }
 
-ShareeModel::ShareeModel(const AccountPtr &account, const QString &type, QObject *parent)
-    : QAbstractListModel(parent)
-    , _account(account)
-    , _type(type)
-{
-}
-
-void ShareeModel::fetch(const QString &search, const ShareeSet &blacklist, LookupMode lookupMode)
-{
-    _search = search;
-    _shareeBlacklist = blacklist;
-    auto *job = new OcsShareeJob(_account);
-    connect(job, &OcsShareeJob::shareeJobFinished, this, &ShareeModel::shareesFetched);
-    connect(job, &OcsJob::ocsError, this, &ShareeModel::displayErrorMessage);
-    job->getSharees(_search, _type, 1, 50, lookupMode == GlobalSearch ? true : false);
-}
-
-void ShareeModel::shareesFetched(const QJsonDocument &reply)
-{
-    QVector<QSharedPointer<Sharee>> newSharees;
-
-    {
-        const QStringList shareeTypes {"users", "groups", "emails", "remotes", "circles", "rooms"};
-
-        const auto appendSharees = [this, &shareeTypes](const QJsonObject &data, QVector<QSharedPointer<Sharee>>& out) {
-            for (const auto &shareeType : shareeTypes) {
-                const auto category = data.value(shareeType).toArray();
-                for (const auto &sharee : category) {
-                    out.append(parseSharee(sharee.toObject()));
-                }
-            }
-        };
-
-        appendSharees(reply.object().value("ocs").toObject().value("data").toObject(), newSharees);
-        appendSharees(reply.object().value("ocs").toObject().value("data").toObject().value("exact").toObject(), newSharees);
-    }
-
-    // Filter sharees that we have already shared with
-    QVector<QSharedPointer<Sharee>> filteredSharees;
-    foreach (const auto &sharee, newSharees) {
-        bool found = false;
-        foreach (const auto &blacklistSharee, _shareeBlacklist) {
-            if (sharee->type() == blacklistSharee->type() && sharee->shareWith() == blacklistSharee->shareWith()) {
-                found = true;
-                break;
-            }
-        }
-
-        if (found == false) {
-            filteredSharees.append(sharee);
-        }
-    }
-
-    setNewSharees(filteredSharees);
-    shareesReady();
-}
-
-QSharedPointer<Sharee> ShareeModel::parseSharee(const QJsonObject &data)
-{
-    QString displayName = data.value("label").toString();
-    const QString shareWith = data.value("value").toObject().value("shareWith").toString();
-    Sharee::Type type = (Sharee::Type)data.value("value").toObject().value("shareType").toInt();
-    const QString additionalInfo = data.value("value").toObject().value("shareWithAdditionalInfo").toString();
-    if (!additionalInfo.isEmpty()) {
-        displayName = tr("%1 (%2)", "sharee (shareWithAdditionalInfo)").arg(displayName, additionalInfo);
-    }
-
-    return QSharedPointer<Sharee>(new Sharee(shareWith, displayName, type));
-}
-
-
-// Helper function for setNewSharees   (could be a lambda when we can use them)
-static QSharedPointer<Sharee> shareeFromModelIndex(const QModelIndex &idx)
-{
-    return idx.data(Qt::UserRole).value<QSharedPointer<Sharee>>();
-}
-
-struct FindShareeHelper
-{
-    const QSharedPointer<Sharee> &sharee;
-    bool operator()(const QSharedPointer<Sharee> &s2) const
-    {
-        return s2->format() == sharee->format() && s2->displayName() == sharee->format();
-    }
-};
-
-/* Set the new sharee
-
-    Do that while preserving the model index so the selection stays
-*/
-void ShareeModel::setNewSharees(const QVector<QSharedPointer<Sharee>> &newSharees)
-{
-    layoutAboutToBeChanged();
-    const auto persistent = persistentIndexList();
-    QVector<QSharedPointer<Sharee>> oldPersistantSharee;
-    oldPersistantSharee.reserve(persistent.size());
-
-    std::transform(persistent.begin(), persistent.end(), std::back_inserter(oldPersistantSharee),
-        shareeFromModelIndex);
-
-    _sharees = newSharees;
-
-    QModelIndexList newPersistant;
-    newPersistant.reserve(persistent.size());
-    foreach (const QSharedPointer<Sharee> &sharee, oldPersistantSharee) {
-        FindShareeHelper helper = { sharee };
-        auto it = std::find_if(_sharees.constBegin(), _sharees.constEnd(), helper);
-        if (it == _sharees.constEnd()) {
-            newPersistant << QModelIndex();
-        } else {
-            newPersistant << index(std::distance(_sharees.constBegin(), it));
-        }
-    }
-
-    changePersistentIndexList(persistent, newPersistant);
-    layoutChanged();
-}
-
-
-int ShareeModel::rowCount(const QModelIndex &) const
-{
-    return _sharees.size();
-}
-
-QVariant ShareeModel::data(const QModelIndex &index, int role) const
-{
-    if (index.row() < 0 || index.row() > _sharees.size()) {
-        return QVariant();
-    }
-
-    const auto &sharee = _sharees.at(index.row());
-    if (role == Qt::DisplayRole) {
-        return sharee->format();
-
-    } else if (role == Qt::EditRole) {
-        // This role is used by the completer - it should match
-        // the full name and the user name and thus we include both
-        // in the output here. But we need to take care this string
-        // doesn't leak to the user.
-        return QString(sharee->displayName() + " (" + sharee->shareWith() + ")");
-
-    } else if (role == Qt::UserRole) {
-        return QVariant::fromValue(sharee);
-    }
-
-    return QVariant();
-}
-
-QSharedPointer<Sharee> ShareeModel::getSharee(int at)
-{
-    if (at < 0 || at > _sharees.size()) {
-        return QSharedPointer<Sharee>(nullptr);
-    }
-
-    return _sharees.at(at);
-}
 }
index b1aa8f2c2c17f32dcf66a83c1ce767f8e49a9596..2139a91172b085108166eaa8b380db5940454b36 100644 (file)
@@ -61,47 +61,9 @@ private:
     Type _type;
 };
 
-
-class ShareeModel : public QAbstractListModel
-{
-    Q_OBJECT
-public:
-    enum LookupMode {
-        LocalSearch = 0,
-        GlobalSearch = 1
-    };
-
-    explicit ShareeModel(const AccountPtr &account, const QString &type, QObject *parent = nullptr);
-
-    using ShareeSet = QVector<QSharedPointer<Sharee>>; // FIXME: make it a QSet<Sharee> when Sharee can be compared
-    void fetch(const QString &search, const ShareeSet &blacklist, LookupMode lookupMode);
-    [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
-    [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
-
-    QSharedPointer<Sharee> getSharee(int at);
-
-    [[nodiscard]] QString currentSearch() const { return _search; }
-
-signals:
-    void shareesReady();
-    void displayErrorMessage(int code, const QString &);
-
-private slots:
-    void shareesFetched(const QJsonDocument &reply);
-
-private:
-    QSharedPointer<Sharee> parseSharee(const QJsonObject &data);
-    void setNewSharees(const QVector<QSharedPointer<Sharee>> &newSharees);
-
-    AccountPtr _account;
-    QString _search;
-    QString _type;
-
-    QVector<QSharedPointer<Sharee>> _sharees;
-    QVector<QSharedPointer<Sharee>> _shareeBlacklist;
-};
+using ShareePtr = QSharedPointer<OCC::Sharee>;
 }
 
-Q_DECLARE_METATYPE(QSharedPointer<OCC::Sharee>)
+Q_DECLARE_METATYPE(OCC::ShareePtr)
 
 #endif //SHAREE_H
diff --git a/src/gui/sharelinkwidget.cpp b/src/gui/sharelinkwidget.cpp
deleted file mode 100644 (file)
index 3f6b437..0000000
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
- * Copyright (C) 2015 by Klaas Freitag <freitag@owncloud.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.
- */
-
-#include "ui_sharelinkwidget.h"
-#include "sharelinkwidget.h"
-#include "account.h"
-#include "capabilities.h"
-#include "guiutility.h"
-#include "sharemanager.h"
-#include "theme.h"
-#include "elidedlabel.h"
-
-#include "QProgressIndicator.h"
-#include <QBuffer>
-#include <QClipboard>
-#include <QFileInfo>
-#include <QDesktopServices>
-#include <QMessageBox>
-#include <QMenu>
-#include <QTextEdit>
-#include <QToolButton>
-#include <QPropertyAnimation>
-
-namespace {
-    const char *passwordIsSetPlaceholder = "●●●●●●●●";
-}
-
-namespace OCC {
-
-Q_LOGGING_CATEGORY(lcShareLink, "nextcloud.gui.sharelink", QtInfoMsg)
-
-ShareLinkWidget::ShareLinkWidget(AccountPtr account,
-    const QString &sharePath,
-    const QString &localPath,
-    SharePermissions maxSharingPermissions,
-    QWidget *parent)
-    : QWidget(parent)
-    , _ui(new Ui::ShareLinkWidget)
-    , _account(account)
-    , _sharePath(sharePath)
-    , _localPath(localPath)
-    , _linkShare(nullptr)
-    , _passwordRequired(false)
-    , _expiryRequired(false)
-    , _namesSupported(true)
-    , _noteRequired(false)
-    , _linkContextMenu(nullptr)
-    , _readOnlyLinkAction(nullptr)
-    , _allowEditingLinkAction(nullptr)
-    , _allowUploadEditingLinkAction(nullptr)
-    , _allowUploadLinkAction(nullptr)
-    , _passwordProtectLinkAction(nullptr)
-    , _expirationDateLinkAction(nullptr)
-    , _unshareLinkAction(nullptr)
-    , _noteLinkAction(nullptr)
-{
-    _ui->setupUi(this);
-
-    _ui->shareLinkToolButton->hide();
-
-    //Is this a file or folder?
-    QFileInfo fi(localPath);
-    _isFile = fi.isFile();
-
-    connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
-    connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreatePassword);
-    connect(_ui->confirmPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreatePassword);
-    connect(_ui->confirmNote, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreateNote);
-    connect(_ui->confirmExpirationDate, &QAbstractButton::clicked, this, &ShareLinkWidget::slotSetExpireDate);
-
-    _ui->errorLabel->hide();
-
-    if (!_account->capabilities().sharePublicLink()) {
-        qCWarning(lcShareLink) << "Link shares have been disabled";
-    } else if (!(maxSharingPermissions & SharePermissionShare)) {
-        qCWarning(lcShareLink) << "The file can not be shared because it was shared without sharing permission.";
-    }
-
-    _ui->enableShareLink->setChecked(false);
-    _ui->shareLinkToolButton->setEnabled(false);
-    _ui->shareLinkToolButton->hide();
-
-    // Older servers don't support multiple public link shares
-    if (!_account->capabilities().sharePublicLinkMultiple()) {
-        _namesSupported = false;
-    }
-
-    togglePasswordOptions(false);
-    toggleExpireDateOptions(false);
-    toggleNoteOptions(false);
-
-    _ui->noteProgressIndicator->setVisible(false);
-    _ui->passwordProgressIndicator->setVisible(false);
-    _ui->expirationDateProgressIndicator->setVisible(false);
-    _ui->sharelinkProgressIndicator->setVisible(false);
-
-    // check if the file is already inside of a synced folder
-    if (sharePath.isEmpty()) {
-        qCWarning(lcShareLink) << "Unable to share files not in a sync folder.";
-        return;
-    }
-}
-
-ShareLinkWidget::~ShareLinkWidget()
-{
-    delete _ui;
-}
-
-void ShareLinkWidget::slotToggleShareLinkAnimation(const bool start)
-{
-    _ui->sharelinkProgressIndicator->setVisible(start);
-    if (start) {
-        if (!_ui->sharelinkProgressIndicator->isAnimated()) {
-            _ui->sharelinkProgressIndicator->startAnimation();
-        }
-    } else {
-        _ui->sharelinkProgressIndicator->stopAnimation();
-    }
-}
-
-void ShareLinkWidget::toggleButtonAnimation(QToolButton *button, QProgressIndicator *progressIndicator, const QAction *checkedAction) const
-{
-    auto startAnimation = false;
-    const auto actionIsChecked = checkedAction->isChecked();
-    if (!progressIndicator->isAnimated() && actionIsChecked) {
-        progressIndicator->startAnimation();
-        startAnimation = true;
-    } else {
-        progressIndicator->stopAnimation();
-    }
-
-    button->setVisible(!startAnimation && actionIsChecked);
-    progressIndicator->setVisible(startAnimation && actionIsChecked);
-}
-
-void ShareLinkWidget::setLinkShare(QSharedPointer<LinkShare> linkShare)
-{
-    _linkShare = linkShare;
-}
-
-QSharedPointer<LinkShare> ShareLinkWidget::getLinkShare()
-{
-    return _linkShare;
-}
-
-void ShareLinkWidget::focusPasswordLineEdit()
-{
-    _ui->lineEdit_password->setFocus();
-}
-
-void ShareLinkWidget::setupUiOptions()
-{
-    connect(_linkShare.data(), &LinkShare::noteSet, this, &ShareLinkWidget::slotNoteSet);
-    connect(_linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet);
-    connect(_linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError);
-    connect(_linkShare.data(), &LinkShare::labelSet, this, &ShareLinkWidget::slotLabelSet);
-
-    // Prepare permissions check and create group action
-    const QDate expireDate = _linkShare.data()->getExpireDate().isValid() ? _linkShare.data()->getExpireDate() : QDate();
-    const SharePermissions perm = _linkShare.data()->getPermissions();
-    auto checked = false;
-    auto *permissionsGroup = new QActionGroup(this);
-
-    // Prepare sharing menu
-    _linkContextMenu = new QMenu(this);
-
-    // radio button style
-    permissionsGroup->setExclusive(true);
-
-    if (_isFile) {
-        checked = (perm & SharePermissionRead) && (perm & SharePermissionUpdate);
-        _allowEditingLinkAction = _linkContextMenu->addAction(tr("Allow editing"));
-        _allowEditingLinkAction->setCheckable(true);
-        _allowEditingLinkAction->setChecked(checked);
-
-    } else {
-        checked = (perm == SharePermissionRead);
-        _readOnlyLinkAction = permissionsGroup->addAction(tr("View only"));
-        _readOnlyLinkAction->setCheckable(true);
-        _readOnlyLinkAction->setChecked(checked);
-
-        checked = (perm & SharePermissionRead) && (perm & SharePermissionCreate)
-            && (perm & SharePermissionUpdate) && (perm & SharePermissionDelete);
-        _allowUploadEditingLinkAction = permissionsGroup->addAction(tr("Allow upload and editing"));
-        _allowUploadEditingLinkAction->setCheckable(true);
-        _allowUploadEditingLinkAction->setChecked(checked);
-
-        checked = (perm == SharePermissionCreate);
-        _allowUploadLinkAction = permissionsGroup->addAction(tr("File drop (upload only)"));
-        _allowUploadLinkAction->setCheckable(true);
-        _allowUploadLinkAction->setChecked(checked);
-    }
-    
-    _shareLinkElidedLabel = new OCC::ElidedLabel(this);
-    _shareLinkElidedLabel->setElideMode(Qt::ElideRight);
-    displayShareLinkLabel();
-    _ui->horizontalLayout->insertWidget(2, _shareLinkElidedLabel);
-    
-    _shareLinkLayout = new QHBoxLayout(this);
-    
-    _shareLinkLabel = new QLabel(this);
-    _shareLinkLabel->setPixmap(QString(":/client/theme/black/edit.svg"));
-    _shareLinkLayout->addWidget(_shareLinkLabel);
-    
-    _shareLinkEdit = new QLineEdit(this);
-    connect(_shareLinkEdit, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreateLabel);
-    _shareLinkEdit->setPlaceholderText(tr("Link name"));
-    _shareLinkEdit->setText(_linkShare.data()->getLabel());
-    _shareLinkLayout->addWidget(_shareLinkEdit);
-    
-    _shareLinkButton = new QToolButton(this);
-    connect(_shareLinkButton, &QToolButton::clicked, this, &ShareLinkWidget::slotCreateLabel);
-    _shareLinkButton->setIcon(QIcon(":/client/theme/confirm.svg"));
-    _shareLinkButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
-    _shareLinkLayout->addWidget(_shareLinkButton);
-    
-    _shareLinkProgressIndicator = new QProgressIndicator(this);
-    _shareLinkProgressIndicator->setVisible(false);
-    _shareLinkLayout->addWidget(_shareLinkProgressIndicator);
-        
-    _shareLinkDefaultWidget = new QWidget(this);
-    _shareLinkDefaultWidget->setLayout(_shareLinkLayout);
-    
-    _shareLinkWidgetAction = new QWidgetAction(this);
-    _shareLinkWidgetAction->setDefaultWidget(_shareLinkDefaultWidget);
-    _shareLinkWidgetAction->setCheckable(true);
-    _linkContextMenu->addAction(_shareLinkWidgetAction);
-
-    // Adds permissions actions (radio button style)
-    if (_isFile) {
-        _linkContextMenu->addAction(_allowEditingLinkAction);
-    } else {
-        _linkContextMenu->addAction(_readOnlyLinkAction);
-        _linkContextMenu->addAction(_allowUploadEditingLinkAction);
-        _linkContextMenu->addAction(_allowUploadLinkAction);
-    }
-
-    // Adds action to display note widget (check box)
-    _noteLinkAction = _linkContextMenu->addAction(tr("Note to recipient"));
-    _noteLinkAction->setCheckable(true);
-
-    if (_linkShare->getNote().isSimpleText() && !_linkShare->getNote().isEmpty()) {
-        _ui->textEdit_note->setText(_linkShare->getNote());
-        _noteLinkAction->setChecked(true);
-        toggleNoteOptions();
-    }
-
-    // Adds action to display password widget (check box)
-    _passwordProtectLinkAction = _linkContextMenu->addAction(tr("Password protect"));
-    _passwordProtectLinkAction->setCheckable(true);
-
-    if (_linkShare.data()->isPasswordSet()) {
-        _passwordProtectLinkAction->setChecked(true);
-        _ui->lineEdit_password->setPlaceholderText(QString::fromUtf8(passwordIsSetPlaceholder));
-        togglePasswordOptions();
-    }
-
-    // If password is enforced then don't allow users to disable it
-    if (_account->capabilities().sharePublicLinkEnforcePassword()) {
-        if (_linkShare.data()->isPasswordSet()) {
-            _passwordProtectLinkAction->setChecked(true);
-            _passwordProtectLinkAction->setEnabled(false);
-        }
-        _passwordRequired = true;
-    }
-
-    // Adds action to display expiration date widget (check box)
-    _expirationDateLinkAction = _linkContextMenu->addAction(tr("Set expiration date"));
-    _expirationDateLinkAction->setCheckable(true);
-    if (!expireDate.isNull()) {
-        _ui->calendar->setDate(expireDate);
-        _expirationDateLinkAction->setChecked(true);
-        toggleExpireDateOptions();
-    }
-    connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotSetExpireDate);
-    connect(_linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireDateSet);
-    
-
-    // If expiredate is enforced do not allow disable and set max days
-    if (_account->capabilities().sharePublicLinkEnforceExpireDate()) {
-        _ui->calendar->setMaximumDate(QDate::currentDate().addDays(
-            _account->capabilities().sharePublicLinkExpireDateDays()));
-        _expirationDateLinkAction->setChecked(true);
-        _expirationDateLinkAction->setEnabled(false);
-        _expiryRequired = true;
-    }
-
-    // Adds action to unshare widget (check box)
-    _unshareLinkAction.reset(_linkContextMenu->addAction(QIcon(":/client/theme/delete.svg"),
-        tr("Delete link")));
-
-    _linkContextMenu->addSeparator();
-
-    _addAnotherLinkAction.reset(_linkContextMenu->addAction(QIcon(":/client/theme/add.svg"),
-        tr("Add another link")));
-
-    _ui->enableShareLink->setIcon(QIcon(":/client/theme/copy.svg"));
-    disconnect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
-    connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCopyLinkShare);
-
-    connect(_linkContextMenu, &QMenu::triggered,
-        this, &ShareLinkWidget::slotLinkContextMenuActionTriggered);
-
-    _ui->shareLinkToolButton->setMenu(_linkContextMenu);
-    _ui->shareLinkToolButton->setEnabled(true);
-    _ui->enableShareLink->setEnabled(true);
-    _ui->enableShareLink->setChecked(true);
-
-    // show sharing options
-    _ui->shareLinkToolButton->show();
-
-    customizeStyle();
-}
-
-void ShareLinkWidget::slotCreateNote()                                                                              
-{
-    const auto note = _ui->textEdit_note->toPlainText();
-    if (!_linkShare || _linkShare->getNote() == note || note.isEmpty()) {
-        return;
-    }
-    
-    toggleButtonAnimation(_ui->confirmNote, _ui->noteProgressIndicator, _noteLinkAction);
-    _ui->errorLabel->hide();
-    _linkShare->setNote(note);
-}
-
-void ShareLinkWidget::slotNoteSet()
-{
-    toggleButtonAnimation(_ui->confirmNote, _ui->noteProgressIndicator, _noteLinkAction);
-}
-
-void ShareLinkWidget::slotCopyLinkShare(const bool clicked) const
-{
-    Q_UNUSED(clicked);
-
-    QApplication::clipboard()->setText(_linkShare->getLink().toString());
-}
-
-void ShareLinkWidget::slotExpireDateSet()
-{
-    toggleButtonAnimation(_ui->confirmExpirationDate, _ui->expirationDateProgressIndicator, _expirationDateLinkAction);
-}
-
-void ShareLinkWidget::slotSetExpireDate()
-{
-    if (!_linkShare) {
-        return;
-    }
-
-    toggleButtonAnimation(_ui->confirmExpirationDate, _ui->expirationDateProgressIndicator, _expirationDateLinkAction);
-    _ui->errorLabel->hide();
-    _linkShare->setExpireDate(_ui->calendar->date());
-}
-
-void ShareLinkWidget::slotCreatePassword()
-{
-    if (!_linkShare || _ui->lineEdit_password->text().isEmpty()) {
-        return;
-    }
-
-    toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction);
-    _ui->errorLabel->hide();
-    emit createPassword(_ui->lineEdit_password->text());
-}
-
-void ShareLinkWidget::slotCreateShareLink(const bool clicked)
-{
-    Q_UNUSED(clicked);
-    slotToggleShareLinkAnimation(true);
-    emit createLinkShare();
-}
-
-void ShareLinkWidget::slotPasswordSet()
-{
-    toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction);
-
-    _ui->lineEdit_password->setText({});
-
-    if (_linkShare->isPasswordSet()) {
-        _ui->lineEdit_password->setEnabled(true);
-        _ui->lineEdit_password->setPlaceholderText(QString::fromUtf8(passwordIsSetPlaceholder));
-    } else {
-        _ui->lineEdit_password->setPlaceholderText({});
-    }
-
-    emit createPasswordProcessed();
-}
-
-void ShareLinkWidget::slotPasswordSetError(const int code, const QString &message)
-{
-    toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction);
-
-    slotServerError(code, message);
-    togglePasswordOptions();
-    _ui->lineEdit_password->setFocus();
-    emit createPasswordProcessed();
-}
-
-void ShareLinkWidget::slotDeleteShareFetched()
-{
-    slotToggleShareLinkAnimation(false);
-
-    _linkShare.clear();
-    togglePasswordOptions(false);
-    toggleNoteOptions(false);
-    toggleExpireDateOptions(false);
-    emit deleteLinkShare();
-}
-
-void ShareLinkWidget::toggleNoteOptions(const bool enable)
-{
-    _ui->noteLabel->setVisible(enable);
-    _ui->textEdit_note->setVisible(enable);
-    _ui->confirmNote->setVisible(enable);
-    _ui->textEdit_note->setText(enable && _linkShare ? _linkShare->getNote() : QString());
-    
-    if (!enable && _linkShare && !_linkShare->getNote().isEmpty()) {
-        _linkShare->setNote({});
-    } 
-}
-
-void ShareLinkWidget::slotCreateLabel()
-{
-    const auto labelText = _shareLinkEdit->text();
-    if (!_linkShare || _linkShare->getLabel() == labelText || labelText.isEmpty()) {
-        return;
-    }
-    _shareLinkWidgetAction->setChecked(true);
-    toggleButtonAnimation(_shareLinkButton, _shareLinkProgressIndicator, _shareLinkWidgetAction);
-    _ui->errorLabel->hide();
-    _linkShare->setLabel(_shareLinkEdit->text());
-}
-
-void ShareLinkWidget::slotLabelSet()
-{
-    toggleButtonAnimation(_shareLinkButton, _shareLinkProgressIndicator, _shareLinkWidgetAction);
-    displayShareLinkLabel();
-}
-
-void ShareLinkWidget::slotCreateShareRequiresPassword(const QString &message)
-{
-    slotToggleShareLinkAnimation(message.isEmpty());
-
-    if (!message.isEmpty()) {
-        _ui->errorLabel->setText(message);
-        _ui->errorLabel->show();
-    }
-
-    _passwordRequired = true;
-
-    togglePasswordOptions();
-}
-
-void ShareLinkWidget::togglePasswordOptions(const bool enable)
-{
-    _ui->passwordLabel->setVisible(enable);
-    _ui->lineEdit_password->setVisible(enable);
-    _ui->confirmPassword->setVisible(enable);
-    _ui->lineEdit_password->setFocus();
-    
-    if (!enable && _linkShare && _linkShare->isPasswordSet()) {
-        _linkShare->setPassword({});
-    }
-}
-
-void ShareLinkWidget::toggleExpireDateOptions(const bool enable)
-{
-    _ui->expirationLabel->setVisible(enable);
-    _ui->calendar->setVisible(enable);
-    _ui->confirmExpirationDate->setVisible(enable);
-    
-    const auto date = enable ? _linkShare->getExpireDate() : QDate::currentDate().addDays(1);
-    _ui->calendar->setDate(date);
-    _ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
-
-    if(_account->capabilities().sharePublicLinkEnforceExpireDate()) {
-        _ui->calendar->setMaximumDate(QDate::currentDate().addDays(_account->capabilities().sharePublicLinkExpireDateDays()));
-    }
-
-    _ui->calendar->setFocus();
-    
-    if (!enable && _linkShare && _linkShare->getExpireDate().isValid()) {
-        _linkShare->setExpireDate({});
-    }
-}
-
-void ShareLinkWidget::confirmAndDeleteShare()
-{
-    auto messageBox = new QMessageBox(
-        QMessageBox::Question,
-        tr("Confirm Link Share Deletion"),
-        tr("<p>Do you really want to delete the public link share <i>%1</i>?</p>"
-           "<p>Note: This action cannot be undone.</p>")
-            .arg(shareName()),
-        QMessageBox::NoButton,
-        this);
-    QPushButton *yesButton =
-        messageBox->addButton(tr("Delete"), QMessageBox::YesRole);
-    messageBox->addButton(tr("Cancel"), QMessageBox::NoRole);
-
-    connect(messageBox, &QMessageBox::finished, this,
-        [messageBox, yesButton, this]() {
-            if (messageBox->clickedButton() == yesButton) {
-                this->slotToggleShareLinkAnimation(true);
-                this->_linkShare->deleteShare();
-            }
-        });
-    messageBox->open();
-}
-
-QString ShareLinkWidget::shareName() const
-{
-    QString name = _linkShare->getName();
-    if (!name.isEmpty())
-        return name;
-    if (!_namesSupported)
-        return tr("Public link");
-    return _linkShare->getToken();
-}
-
-void ShareLinkWidget::slotContextMenuButtonClicked()
-{
-    _linkContextMenu->exec(QCursor::pos());
-}
-
-void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
-{
-    const auto state = action->isChecked();
-    SharePermissions perm = SharePermissionRead;
-
-    if (action == _addAnotherLinkAction.data()) {
-        emit createLinkShare();
-
-    } else if (action == _readOnlyLinkAction && state) {
-        _linkShare->setPermissions(perm);
-
-    } else if (action == _allowEditingLinkAction && state) {
-        perm |= SharePermissionUpdate;
-        _linkShare->setPermissions(perm);
-
-    } else if (action == _allowUploadEditingLinkAction && state) {
-        perm |= SharePermissionCreate | SharePermissionUpdate | SharePermissionDelete;
-        _linkShare->setPermissions(perm);
-
-    } else if (action == _allowUploadLinkAction && state) {
-        perm = SharePermissionCreate;
-        _linkShare->setPermissions(perm);
-
-    } else if (action == _passwordProtectLinkAction) {
-        togglePasswordOptions(state);
-
-    } else if (action == _expirationDateLinkAction) {
-        toggleExpireDateOptions(state);
-
-    } else if (action == _noteLinkAction) {
-        toggleNoteOptions(state);
-
-    } else if (action == _unshareLinkAction.data()) {
-        confirmAndDeleteShare();
-    }
-}
-
-void ShareLinkWidget::slotServerError(const int code, const QString &message)
-{
-    slotToggleShareLinkAnimation(false);
-
-    qCWarning(lcSharing) << "Error from server" << code << message;
-    displayError(message);
-}
-
-void ShareLinkWidget::displayError(const QString &errMsg)
-{
-    _ui->errorLabel->setText(errMsg);
-    _ui->errorLabel->show();
-}
-
-void ShareLinkWidget::slotStyleChanged()
-{
-    customizeStyle();
-}
-
-void ShareLinkWidget::customizeStyle()
-{
-    if(_unshareLinkAction) {
-        _unshareLinkAction->setIcon(Theme::createColorAwareIcon(":/client/theme/delete.svg"));
-    }
-
-    if(_addAnotherLinkAction) {
-        _addAnotherLinkAction->setIcon(Theme::createColorAwareIcon(":/client/theme/add.svg"));
-    }
-
-    _ui->enableShareLink->setIcon(Theme::createColorAwareIcon(":/client/theme/copy.svg"));
-
-    _ui->shareLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/theme/public.svg"));
-
-    _ui->shareLinkToolButton->setIcon(Theme::createColorAwareIcon(":/client/theme/more.svg"));
-
-    _ui->confirmNote->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
-    _ui->confirmPassword->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
-    _ui->confirmExpirationDate->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
-
-    _ui->passwordProgressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text));
-}
-
-void ShareLinkWidget::displayShareLinkLabel()
-{
-    _shareLinkElidedLabel->clear();
-    if (!_linkShare->getLabel().isEmpty()) {
-        _shareLinkElidedLabel->setText(QString("(%1)").arg(_linkShare->getLabel()));
-    } 
-}
-
-}
diff --git a/src/gui/sharelinkwidget.h b/src/gui/sharelinkwidget.h
deleted file mode 100644 (file)
index 7ecd969..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
- * Copyright (C) 2015 by Klaas Freitag <freitag@owncloud.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.
- */
-
-#ifndef SHARELINKWIDGET_H
-#define SHARELINKWIDGET_H
-
-#include "accountfwd.h"
-#include "sharepermissions.h"
-#include "QProgressIndicator.h"
-#include <QDialog>
-#include <QSharedPointer>
-#include <QList>
-#include <QToolButton>
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QLineEdit>
-#include <QWidgetAction>
-
-class QMenu;
-class QTableWidgetItem;
-
-namespace OCC {
-
-namespace Ui {
-    class ShareLinkWidget;
-}
-
-class AbstractCredentials;
-class SyncResult;
-class LinkShare;
-class Share;
-class ElidedLabel;
-
-/**
- * @brief The ShareDialog class
- * @ingroup gui
- */
-class ShareLinkWidget : public QWidget
-{
-    Q_OBJECT
-
-public:
-    explicit ShareLinkWidget(AccountPtr account,
-        const QString &sharePath,
-        const QString &localPath,
-        SharePermissions maxSharingPermissions,
-        QWidget *parent = nullptr);
-    ~ShareLinkWidget() override;
-
-    void toggleButton(bool show);
-    void setupUiOptions();
-
-    void setLinkShare(QSharedPointer<LinkShare> linkShare);
-    QSharedPointer<LinkShare> getLinkShare();
-
-    void focusPasswordLineEdit();
-
-public slots:
-    void slotDeleteShareFetched();
-    void slotToggleShareLinkAnimation(const bool start);
-    void slotServerError(const int code, const QString &message);
-    void slotCreateShareRequiresPassword(const QString &message);
-    void slotStyleChanged();
-
-private slots:
-    void slotCreateShareLink(const bool clicked);
-    void slotCopyLinkShare(const bool clicked) const;    
-
-    void slotCreatePassword();
-    void slotPasswordSet();
-    void slotPasswordSetError(const int code, const QString &message);
-
-    void slotCreateNote();
-    void slotNoteSet();
-
-    void slotSetExpireDate();
-    void slotExpireDateSet();
-
-    void slotContextMenuButtonClicked();
-    void slotLinkContextMenuActionTriggered(QAction *action);
-    
-    void slotCreateLabel();
-    void slotLabelSet();
-
-signals:
-    void createLinkShare();
-    void deleteLinkShare();
-    void visualDeletionDone();
-    void createPassword(const QString &password);
-    void createPasswordProcessed();
-
-private:
-    void displayError(const QString &errMsg);
-    
-    void togglePasswordOptions(const bool enable = true);
-    void toggleNoteOptions(const bool enable = true);
-    void toggleExpireDateOptions(const bool enable = true);
-    void toggleButtonAnimation(QToolButton *button, QProgressIndicator *progressIndicator, const QAction *checkedAction) const;
-
-    /** Confirm with the user and then delete the share */
-    void confirmAndDeleteShare();
-
-    /** Retrieve a share's name, accounting for _namesSupported */
-    [[nodiscard]] QString shareName() const;
-
-    void customizeStyle();
-    
-    void displayShareLinkLabel();
-
-    Ui::ShareLinkWidget *_ui;
-    AccountPtr _account;
-    QString _sharePath;
-    QString _localPath;
-    QString _shareUrl;
-
-    QSharedPointer<LinkShare> _linkShare;
-
-    bool _isFile;
-    bool _passwordRequired;
-    bool _expiryRequired;
-    bool _namesSupported;
-    bool _noteRequired;
-
-    QMenu *_linkContextMenu;
-    QAction *_readOnlyLinkAction;
-    QAction *_allowEditingLinkAction;
-    QAction *_allowUploadEditingLinkAction;
-    QAction *_allowUploadLinkAction;
-    QAction *_passwordProtectLinkAction;
-    QAction *_expirationDateLinkAction;
-    QScopedPointer<QAction> _unshareLinkAction;
-    QScopedPointer<QAction> _addAnotherLinkAction;
-    QAction *_noteLinkAction;
-    QHBoxLayout *_shareLinkLayout{};
-    QLabel *_shareLinkLabel{};
-    ElidedLabel *_shareLinkElidedLabel{};
-    QLineEdit *_shareLinkEdit{};
-    QToolButton *_shareLinkButton{};
-    QProgressIndicator *_shareLinkProgressIndicator{};
-    QWidget *_shareLinkDefaultWidget{};
-    QWidgetAction *_shareLinkWidgetAction{};
-};
-}
-
-#endif // SHARELINKWIDGET_H
diff --git a/src/gui/sharelinkwidget.ui b/src/gui/sharelinkwidget.ui
deleted file mode 100644 (file)
index a0d5f3d..0000000
+++ /dev/null
@@ -1,439 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>OCC::ShareLinkWidget</class>
- <widget class="QWidget" name="OCC::ShareLinkWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>400</width>
-    <height>238</height>
-   </rect>
-  </property>
-  <property name="sizePolicy">
-   <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
-    <horstretch>0</horstretch>
-    <verstretch>0</verstretch>
-   </sizepolicy>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <property name="spacing">
-    <number>0</number>
-   </property>
-   <property name="leftMargin">
-    <number>12</number>
-   </property>
-   <property name="topMargin">
-    <number>0</number>
-   </property>
-   <property name="rightMargin">
-    <number>20</number>
-   </property>
-   <property name="bottomMargin">
-    <number>0</number>
-   </property>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <property name="spacing">
-      <number>6</number>
-     </property>
-     <property name="rightMargin">
-      <number>0</number>
-     </property>
-     <item>
-      <widget class="QLabel" name="shareLinkIconLabel">
-       <property name="text">
-        <string notr="true"/>
-       </property>
-       <property name="pixmap">
-        <pixmap resource="../../theme.qrc">:/client/theme/public.svg</pixmap>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QLabel" name="shareLinkLabel">
-       <property name="text">
-        <string>Share link</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <spacer name="horizontalSpacer">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>40</width>
-         <height>25</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-     <item>
-      <widget class="QProgressIndicator" name="sharelinkProgressIndicator" native="true">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>28</width>
-         <height>27</height>
-        </size>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <spacer name="horizontalSpacer_2">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>40</width>
-         <height>25</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-     <item>
-      <widget class="QPushButton" name="enableShareLink">
-       <property name="text">
-        <string/>
-       </property>
-       <property name="icon">
-        <iconset resource="../../theme.qrc">
-         <normaloff>:/client/theme/add.svg</normaloff>:/client/theme/add.svg</iconset>
-       </property>
-       <property name="checkable">
-        <bool>false</bool>
-       </property>
-       <property name="flat">
-        <bool>true</bool>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QToolButton" name="shareLinkToolButton">
-       <property name="enabled">
-        <bool>false</bool>
-       </property>
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="icon">
-        <iconset resource="../../theme.qrc">
-         <normaloff>:/client/theme/more.svg</normaloff>:/client/theme/more.svg</iconset>
-       </property>
-       <property name="popupMode">
-        <enum>QToolButton::InstantPopup</enum>
-       </property>
-       <property name="autoRaise">
-        <bool>true</bool>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <layout class="QGridLayout" name="gridLayout">
-     <property name="leftMargin">
-      <number>22</number>
-     </property>
-     <item row="0" column="0">
-      <widget class="QLabel" name="noteLabel">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>78</width>
-         <height>0</height>
-        </size>
-       </property>
-       <property name="text">
-        <string>Note</string>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-       </property>
-       <property name="indent">
-        <number>0</number>
-       </property>
-      </widget>
-     </item>
-     <item row="0" column="1">
-      <widget class="QTextEdit" name="textEdit_note">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>0</width>
-         <height>60</height>
-        </size>
-       </property>
-       <property name="sizeAdjustPolicy">
-        <enum>QAbstractScrollArea::AdjustToContents</enum>
-       </property>
-      </widget>
-     </item>
-     <item row="0" column="2">
-      <widget class="QToolButton" name="confirmNote">
-       <property name="minimumSize">
-        <size>
-         <width>28</width>
-         <height>27</height>
-        </size>
-       </property>
-       <property name="icon">
-        <iconset resource="../../theme.qrc">
-         <normaloff>:/client/theme/confirm.svg</normaloff>:/client/theme/confirm.svg</iconset>
-       </property>
-       <property name="autoRaise">
-        <bool>true</bool>
-       </property>
-      </widget>
-     </item>
-     <item row="0" column="2">
-      <widget class="QProgressIndicator" name="noteProgressIndicator" native="true">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>28</width>
-         <height>27</height>
-        </size>
-       </property>
-      </widget>
-     </item>
-     <item row="1" column="0">
-      <widget class="QLabel" name="passwordLabel">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>78</width>
-         <height>0</height>
-        </size>
-       </property>
-       <property name="text">
-        <string>Set password</string>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-       </property>
-       <property name="indent">
-        <number>0</number>
-       </property>
-      </widget>
-     </item>
-     <item row="1" column="1">
-      <widget class="QLineEdit" name="lineEdit_password">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-         <horstretch>1</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="echoMode">
-        <enum>QLineEdit::Password</enum>
-       </property>
-      </widget>
-     </item>
-     <item row="1" column="2">
-      <widget class="QToolButton" name="confirmPassword">
-       <property name="minimumSize">
-        <size>
-         <width>28</width>
-         <height>27</height>
-        </size>
-       </property>
-       <property name="icon">
-        <iconset resource="../../theme.qrc">
-         <normaloff>:/client/theme/confirm.svg</normaloff>:/client/theme/confirm.svg</iconset>
-       </property>
-       <property name="autoRaise">
-        <bool>true</bool>
-       </property>
-      </widget>
-     </item>
-     <item row="1" column="2">
-      <widget class="QProgressIndicator" name="passwordProgressIndicator" native="true">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>28</width>
-         <height>27</height>
-        </size>
-       </property>
-      </widget>
-     </item>
-     <item row="2" column="0">
-      <widget class="QLabel" name="expirationLabel">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>78</width>
-         <height>0</height>
-        </size>
-       </property>
-       <property name="text">
-        <string>Expires</string>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-       </property>
-       <property name="indent">
-        <number>0</number>
-       </property>
-      </widget>
-     </item>
-     <item row="2" column="1">
-      <widget class="QDateEdit" name="calendar">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-         <horstretch>1</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-      </widget>
-     </item>
-     <item row="2" column="2">
-      <widget class="QToolButton" name="confirmExpirationDate">
-       <property name="minimumSize">
-        <size>
-         <width>28</width>
-         <height>27</height>
-        </size>
-       </property>
-       <property name="icon">
-        <iconset resource="../../theme.qrc">
-         <normaloff>:/client/theme/confirm.svg</normaloff>:/client/theme/confirm.svg</iconset>
-       </property>
-       <property name="autoRaise">
-        <bool>true</bool>
-       </property>
-      </widget>
-     </item>
-     <item row="2" column="2">
-      <widget class="QProgressIndicator" name="expirationDateProgressIndicator" native="true">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="minimumSize">
-        <size>
-         <width>28</width>
-         <height>27</height>
-        </size>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_2">
-     <item>
-      <widget class="QLabel" name="errorLabel">
-       <property name="palette">
-        <palette>
-         <active>
-          <colorrole role="WindowText">
-           <brush brushstyle="SolidPattern">
-            <color alpha="255">
-             <red>255</red>
-             <green>0</green>
-             <blue>0</blue>
-            </color>
-           </brush>
-          </colorrole>
-         </active>
-         <inactive>
-          <colorrole role="WindowText">
-           <brush brushstyle="SolidPattern">
-            <color alpha="255">
-             <red>255</red>
-             <green>0</green>
-             <blue>0</blue>
-            </color>
-           </brush>
-          </colorrole>
-         </inactive>
-         <disabled>
-          <colorrole role="WindowText">
-           <brush brushstyle="SolidPattern">
-            <color alpha="255">
-             <red>123</red>
-             <green>121</green>
-             <blue>134</blue>
-            </color>
-           </brush>
-          </colorrole>
-         </disabled>
-        </palette>
-       </property>
-       <property name="text">
-        <string notr="true">TextLabel</string>
-       </property>
-       <property name="textFormat">
-        <enum>Qt::PlainText</enum>
-       </property>
-       <property name="wordWrap">
-        <bool>true</bool>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <layoutdefault spacing="6" margin="11"/>
- <customwidgets>
-  <customwidget>
-   <class>QProgressIndicator</class>
-   <extends>QWidget</extends>
-   <header>QProgressIndicator.h</header>
-   <container>1</container>
-  </customwidget>
- </customwidgets>
- <resources>
-  <include location="../../theme.qrc"/>
- </resources>
- <connections/>
-</ui>
index 154bcbec31967eec15b46477100369bb93545d34..7a264557d170b35461218d86a4c4f9700870253a 100644 (file)
@@ -58,7 +58,7 @@ Share::Share(AccountPtr account,
     const ShareType shareType,
     bool isPasswordSet,
     const Permissions permissions,
-    const QSharedPointer<Sharee> shareWith)
+    const ShareePtr shareWith)
     : _account(account)
     , _id(id)
     , _uidowner(uidowner)
@@ -101,7 +101,7 @@ Share::ShareType Share::getShareType() const
     return _shareType;
 }
 
-QSharedPointer<Sharee> Share::getShareWith() const
+ShareePtr Share::getShareWith() const
 {
     return _shareWith;
 }
@@ -316,7 +316,7 @@ UserGroupShare::UserGroupShare(AccountPtr account,
     const ShareType shareType,
     bool isPasswordSet,
     const Permissions permissions,
-    const QSharedPointer<Sharee> shareWith,
+    const ShareePtr shareWith,
     const QDate &expireDate,
     const QString &note)
     : Share(account, id, owner, ownerDisplayName, path, shareType, isPasswordSet, permissions, shareWith)
@@ -461,7 +461,7 @@ void ShareManager::slotShareCreated(const QJsonDocument &reply)
 {
     //Parse share
     auto data = reply.object().value("ocs").toObject().value("data").toObject();
-    QSharedPointer<Share> share(parseShare(data));
+    SharePtr share(parseShare(data));
 
     emit shareCreated(share);
 
@@ -482,14 +482,14 @@ void ShareManager::slotSharesFetched(const QJsonDocument &reply)
     const QString versionString = _account->serverVersion();
     qCDebug(lcSharing) << versionString << "Fetched" << tmpShares.count() << "shares";
 
-    QList<QSharedPointer<Share>> shares;
+    QList<SharePtr> shares;
 
     foreach (const auto &share, tmpShares) {
         auto data = share.toObject();
 
         auto shareType = data.value("share_type").toInt();
 
-        QSharedPointer<Share> newShare;
+        SharePtr newShare;
 
         if (shareType == Share::TypeLink) {
             newShare = parseLinkShare(data);
@@ -499,7 +499,7 @@ void ShareManager::slotSharesFetched(const QJsonDocument &reply)
             newShare = parseShare(data);
         }
 
-        shares.append(QSharedPointer<Share>(newShare));
+        shares.append(SharePtr(newShare));
     }
 
     qCDebug(lcSharing) << "Sending " << shares.count() << "shares";
@@ -508,7 +508,7 @@ void ShareManager::slotSharesFetched(const QJsonDocument &reply)
 
 QSharedPointer<UserGroupShare> ShareManager::parseUserGroupShare(const QJsonObject &data)
 {
-    QSharedPointer<Sharee> sharee(new Sharee(data.value("share_with").toString(),
+    ShareePtr sharee(new Sharee(data.value("share_with").toString(),
         data.value("share_with_displayname").toString(),
         static_cast<Sharee::Type>(data.value("share_type").toInt())));
 
@@ -577,13 +577,13 @@ QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QJsonObject &data)
         data.value("label").toString()));
 }
 
-QSharedPointer<Share> ShareManager::parseShare(const QJsonObject &data)
+SharePtr ShareManager::parseShare(const QJsonObject &data) const
 {
-    QSharedPointer<Sharee> sharee(new Sharee(data.value("share_with").toString(),
+    ShareePtr sharee(new Sharee(data.value("share_with").toString(),
         data.value("share_with_displayname").toString(),
         (Sharee::Type)data.value("share_type").toInt()));
 
-    return QSharedPointer<Share>(new Share(_account,
+    return SharePtr(new Share(_account,
         data.value("id").toVariant().toString(), // "id" used to be an integer, support both
         data.value("uid_owner").toVariant().toString(),
         data.value("displayname_owner").toVariant().toString(),
index 16d2e47c3ded4b4942f8b65025fc4bbb21dcac2f..1d9918f0451a951fa7f4038c6315b4d090c9013d 100644 (file)
@@ -36,6 +36,15 @@ class OcsShareJob;
 class Share : public QObject
 {
     Q_OBJECT
+    Q_PROPERTY(AccountPtr account READ account CONSTANT)
+    Q_PROPERTY(QString path READ path CONSTANT)
+    Q_PROPERTY(QString id READ getId CONSTANT)
+    Q_PROPERTY(QString uidOwner READ getUidOwner CONSTANT)
+    Q_PROPERTY(QString ownerDisplayName READ getOwnerDisplayName CONSTANT)
+    Q_PROPERTY(ShareType shareType READ getShareType CONSTANT)
+    Q_PROPERTY(ShareePtr shareWith READ getShareWith CONSTANT)
+    Q_PROPERTY(Permissions permissions READ getPermissions WRITE setPermissions NOTIFY permissionsSet)
+    Q_PROPERTY(bool isPasswordSet READ isPasswordSet NOTIFY passwordSet)
 
 public:
     /**
@@ -43,14 +52,16 @@ public:
      * Need to be in sync with Sharee::Type
      */
     enum ShareType {
+        TypePlaceholderLink = -1,
         TypeUser = Sharee::User,
         TypeGroup = Sharee::Group,
         TypeLink = 3,
         TypeEmail = Sharee::Email,
         TypeRemote = Sharee::Federated,
         TypeCircle = Sharee::Circle,
-        TypeRoom = Sharee::Room
+        TypeRoom = Sharee::Room,
     };
+    Q_ENUM(ShareType);
 
     using Permissions = SharePermissions;
 
@@ -65,7 +76,7 @@ public:
         const ShareType shareType,
         bool isPasswordSet = false,
         const Permissions permissions = SharePermissionDefault,
-        const QSharedPointer<Sharee> shareWith = QSharedPointer<Sharee>(nullptr));
+        const ShareePtr shareWith = ShareePtr(nullptr));
 
     /**
      * The account the share is defined on.
@@ -97,13 +108,36 @@ public:
     /*
      * Get the shareWith
      */
-    [[nodiscard]] QSharedPointer<Sharee> getShareWith() const;
+    [[nodiscard]] ShareePtr getShareWith() const;
 
     /*
      * Get permissions
      */
     [[nodiscard]] Permissions getPermissions() const;
 
+    [[nodiscard]] bool isPasswordSet() const;
+
+     /*
+     * Is it a share with a user or group (local or remote)
+     */
+    [[nodiscard]] static bool isShareTypeUserGroupEmailRoomOrRemote(const ShareType type);
+
+signals:
+    void permissionsSet();
+    void shareDeleted();
+    void serverError(int code, const QString &message);
+    void passwordSet();
+    void passwordSetError(int statusCode, const QString &message);    
+
+public slots:
+    /*
+     * Deletes a share
+     *
+     * On success the shareDeleted signal is emitted
+     * In case of a server error the serverError signal is emitted.
+     */
+    void deleteShare();
+
     /*
      * Set the permissions of a share
      *
@@ -120,28 +154,6 @@ public:
      */
     void setPassword(const QString &password);
 
-    [[nodiscard]] bool isPasswordSet() const;
-
-    /*
-     * Deletes a share
-     *
-     * On success the shareDeleted signal is emitted
-     * In case of a server error the serverError signal is emitted.
-     */
-    void deleteShare();
-
-     /*
-     * Is it a share with a user or group (local or remote)
-     */
-    static bool isShareTypeUserGroupEmailRoomOrRemote(const ShareType type);
-
-signals:
-    void permissionsSet();
-    void shareDeleted();
-    void serverError(int code, const QString &message);
-    void passwordSet();
-    void passwordSetError(int statusCode, const QString &message);
-
 protected:
     AccountPtr _account;
     QString _id;
@@ -151,7 +163,7 @@ protected:
     ShareType _shareType;
     bool _isPasswordSet;
     Permissions _permissions;
-    QSharedPointer<Sharee> _shareWith;
+    ShareePtr _shareWith;
 
 protected slots:
     void slotOcsError(int statusCode, const QString &message);
@@ -163,6 +175,8 @@ private slots:
     void slotPermissionsSet(const QJsonDocument &, const QVariant &value);
 };
 
+using SharePtr = QSharedPointer<Share>;
+
 /**
  * A Link share is just like a regular share but then slightly different.
  * There are several methods in the API that either work differently for
@@ -171,6 +185,16 @@ private slots:
 class LinkShare : public Share
 {
     Q_OBJECT
+    Q_PROPERTY(QUrl link READ getLink CONSTANT)
+    Q_PROPERTY(QUrl directDownloadLink READ getDirectDownloadLink CONSTANT)
+    Q_PROPERTY(bool publicCanUpload READ getPublicUpload CONSTANT)
+    Q_PROPERTY(bool publicCanReadDirectory READ getShowFileListing CONSTANT)
+    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(QDate expireDate READ getExpireDate WRITE setExpireDate NOTIFY expireDateSet)
+    Q_PROPERTY(QString token READ getToken CONSTANT)
+
 public:
     explicit LinkShare(AccountPtr account,
         const QString &id,
@@ -221,6 +245,23 @@ public:
      */
     [[nodiscard]] QString getLabel() const;
 
+    /*
+     * Returns the token of the link share.
+     */
+    [[nodiscard]] QString getToken() const;
+
+    /*
+     * Get the expiration date
+     */
+    [[nodiscard]] QDate getExpireDate() const;
+    
+    /*
+     * Create OcsShareJob and connect to signal/slots
+     */
+    template <typename LinkShareSlot>
+    OcsShareJob *createShareJob(const LinkShareSlot slotFunction);
+    
+public slots:
     /*
      * Set the name of the link share.
      *
@@ -233,16 +274,6 @@ public:
      */
     void setNote(const QString &note);
 
-    /*
-     * Returns the token of the link share.
-     */
-    [[nodiscard]] QString getToken() const;
-
-    /*
-     * Get the expiration date
-     */
-    [[nodiscard]] QDate getExpireDate() const;
-
     /*
      * Set the expiration date
      *
@@ -250,19 +281,12 @@ public:
      * In case of a server error the serverError signal is emitted.
      */
     void setExpireDate(const QDate &expireDate);
-    
+
     /*
      * Set the label of the share link.
      */
     void setLabel(const QString &label);
     
-    /*
-     * Create OcsShareJob and connect to signal/slots
-     */
-    template <typename LinkShareSlot>
-    OcsShareJob *createShareJob(const LinkShareSlot slotFunction);
-    
-    
 signals:
     void expireDateSet();
     void noteSet();
@@ -287,6 +311,8 @@ private:
 class UserGroupShare : public Share
 {
     Q_OBJECT
+    Q_PROPERTY(QString note READ getNote WRITE setNote NOTIFY noteSet)
+    Q_PROPERTY(QDate expireDate READ getExpireDate WRITE setExpireDate NOTIFY expireDateSet)
 public:
     UserGroupShare(AccountPtr account,
         const QString &id,
@@ -296,27 +322,26 @@ public:
         const ShareType shareType,
         bool isPasswordSet,
         const Permissions permissions,
-        const QSharedPointer<Sharee> shareWith,
+        const ShareePtr shareWith,
         const QDate &expireDate,
         const QString &note);
 
-    void setNote(const QString &note);
-
     [[nodiscard]] QString getNote() const;
-
-    void slotNoteSet(const QJsonDocument &, const QVariant &note);
-
-    void setExpireDate(const QDate &date);
-
     [[nodiscard]] QDate getExpireDate() const;
 
-    void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
+public slots:
+    void setNote(const QString &note);
+    void setExpireDate(const QDate &date);
 
 signals:
     void noteSet();
     void noteSetError();
     void expireDateSet();
 
+private slots:
+     void slotNoteSet(const QJsonDocument &json, const QVariant &note);
+     void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
+
 private:
     QString _note;
     QDate _expireDate;
@@ -375,9 +400,9 @@ public:
     void fetchShares(const QString &path);
 
 signals:
-    void shareCreated(const QSharedPointer<Share> &share);
+    void shareCreated(const SharePtr &share);
     void linkShareCreated(const QSharedPointer<LinkShare> &share);
-    void sharesFetched(const QList<QSharedPointer<Share>> &shares);
+    void sharesFetched(const QList<SharePtr> &shares);
     void serverError(int code, const QString &message);
 
     /** Emitted when creating a link share with password fails.
@@ -396,10 +421,12 @@ private slots:
 private:
     QSharedPointer<LinkShare> parseLinkShare(const QJsonObject &data);
     QSharedPointer<UserGroupShare> parseUserGroupShare(const QJsonObject &data);
-    QSharedPointer<Share> parseShare(const QJsonObject &data);
+    SharePtr parseShare(const QJsonObject &data) const;
 
     AccountPtr _account;
 };
 }
 
+Q_DECLARE_METATYPE(OCC::SharePtr);
+
 #endif // SHAREMANAGER_H
diff --git a/src/gui/shareusergroupwidget.cpp b/src/gui/shareusergroupwidget.cpp
deleted file mode 100644 (file)
index 024e126..0000000
+++ /dev/null
@@ -1,1129 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@owncloud.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.
- */
-
-#include "ocsprofileconnector.h"
-#include "sharee.h"
-#include "tray/usermodel.h"
-#include "ui_shareusergroupwidget.h"
-#include "ui_shareuserline.h"
-#include "shareusergroupwidget.h"
-#include "account.h"
-#include "folderman.h"
-#include "folder.h"
-#include "accountmanager.h"
-#include "theme.h"
-#include "configfile.h"
-#include "capabilities.h"
-#include "guiutility.h"
-#include "thumbnailjob.h"
-#include "sharemanager.h"
-#include "theme.h"
-#include "iconutils.h"
-
-#include "QProgressIndicator.h"
-#include <QBuffer>
-#include <QFileIconProvider>
-#include <QClipboard>
-#include <QFileInfo>
-#include <QAbstractProxyModel>
-#include <QCompleter>
-#include <QBoxLayout>
-#include <QIcon>
-#include <QLayout>
-#include <QPropertyAnimation>
-#include <QMenu>
-#include <QAction>
-#include <QDesktopServices>
-#include <QInputDialog>
-#include <QMessageBox>
-#include <QCryptographicHash>
-#include <QColor>
-#include <QPainter>
-#include <QListWidget>
-#include <QSvgRenderer>
-#include <QPushButton>
-#include <QContextMenuEvent>
-
-#include <cstring>
-
-namespace {
-const char *passwordIsSetPlaceholder = "●●●●●●●●";
-
-}
-
-namespace OCC {
-
-AvatarEventFilter::AvatarEventFilter(QObject *parent)
-    : QObject(parent)
-{
-}
-
-
-bool AvatarEventFilter::eventFilter(QObject *obj, QEvent *event)
-{
-    if (event->type() == QEvent::ContextMenu) {
-        const auto contextMenuEvent = dynamic_cast<QContextMenuEvent *>(event);
-        if (!contextMenuEvent) {
-            return false;
-        }
-        emit contextMenu(contextMenuEvent->globalPos());
-        return true;
-    }
-    return QObject::eventFilter(obj, event);
-}
-
-ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
-    const QString &sharePath,
-    const QString &localPath,
-    SharePermissions maxSharingPermissions,
-    const QString &privateLinkUrl,
-    QWidget *parent)
-    : QWidget(parent)
-    , _ui(new Ui::ShareUserGroupWidget)
-    , _account(account)
-    , _sharePath(sharePath)
-    , _localPath(localPath)
-    , _maxSharingPermissions(maxSharingPermissions)
-    , _privateLinkUrl(privateLinkUrl)
-    , _disableCompleterActivated(false)
-{
-    setAttribute(Qt::WA_DeleteOnClose);
-    setObjectName("SharingDialogUG"); // required as group for saveGeometry call
-
-    _ui->setupUi(this);
-
-    //Is this a file or folder?
-    _isFile = QFileInfo(localPath).isFile();
-
-    _completer = new QCompleter(this);
-    _completerModel = new ShareeModel(_account,
-        _isFile ? QLatin1String("file") : QLatin1String("folder"),
-        _completer);
-    connect(_completerModel, &ShareeModel::shareesReady, this, &ShareUserGroupWidget::slotShareesReady);
-    connect(_completerModel, &ShareeModel::displayErrorMessage, this, &ShareUserGroupWidget::displayError);
-
-    _completer->setModel(_completerModel);
-    _completer->setCaseSensitivity(Qt::CaseInsensitive);
-    _completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
-    _ui->shareeLineEdit->setCompleter(_completer);
-
-    _searchGloballyAction.reset(new QAction(_ui->shareeLineEdit));
-    _searchGloballyAction->setIcon(Theme::createColorAwareIcon(":/client/theme/magnifying-glass.svg"));
-    _searchGloballyAction->setToolTip(tr("Search globally"));
-
-    connect(_searchGloballyAction.data(), &QAction::triggered, this, [this]() {
-        searchForSharees(ShareeModel::GlobalSearch);
-    });
-
-    _ui->shareeLineEdit->addAction(_searchGloballyAction.data(), QLineEdit::LeadingPosition);
-
-    _manager = new ShareManager(_account, this);
-    connect(_manager, &ShareManager::sharesFetched, this, &ShareUserGroupWidget::slotSharesFetched);
-    connect(_manager, &ShareManager::shareCreated, this, &ShareUserGroupWidget::slotShareCreated);
-    connect(_manager, &ShareManager::serverError, this, &ShareUserGroupWidget::displayError);
-    connect(_ui->shareeLineEdit, &QLineEdit::returnPressed, this, &ShareUserGroupWidget::slotLineEditReturn);
-    connect(_ui->confirmShare, &QAbstractButton::clicked, this, &ShareUserGroupWidget::slotLineEditReturn);
-    //TODO connect(_ui->privateLinkText, &QLabel::linkActivated, this, &ShareUserGroupWidget::slotPrivateLinkShare);
-
-    // By making the next two QueuedConnections we can override
-    // the strings the completer sets on the line edit.
-    connect(_completer, SIGNAL(activated(QModelIndex)), SLOT(slotCompleterActivated(QModelIndex)),
-        Qt::QueuedConnection);
-    connect(_completer, SIGNAL(highlighted(QModelIndex)), SLOT(slotCompleterHighlighted(QModelIndex)),
-        Qt::QueuedConnection);
-
-    // Queued connection so this signal is recieved after textChanged
-    connect(_ui->shareeLineEdit, &QLineEdit::textEdited,
-        this, &ShareUserGroupWidget::slotLineEditTextEdited, Qt::QueuedConnection);
-    _ui->shareeLineEdit->installEventFilter(this);
-    connect(&_completionTimer, &QTimer::timeout, this, [this]() {
-        searchForSharees(ShareeModel::LocalSearch);
-    });
-    _completionTimer.setSingleShot(true);
-    _completionTimer.setInterval(600);
-
-    _ui->errorLabel->hide();
-    
-    _parentScrollArea = parentWidget()->findChild<QScrollArea*>("scrollArea");
-    _shareUserGroup = new QVBoxLayout(_parentScrollArea);
-    _shareUserGroup->setContentsMargins(0, 0, 0, 0);
-    customizeStyle();
-}
-
-QVBoxLayout *ShareUserGroupWidget::shareUserGroupLayout()
-{
-    return _shareUserGroup;
-}   
-
-ShareUserGroupWidget::~ShareUserGroupWidget()
-{
-    delete _ui;
-}
-
-void ShareUserGroupWidget::on_shareeLineEdit_textChanged(const QString &)
-{
-    _completionTimer.stop();
-    emit togglePublicLinkShare(false);
-}
-
-void ShareUserGroupWidget::slotLineEditTextEdited(const QString &text)
-{
-    _disableCompleterActivated = false;
-    // First textChanged is called first and we stopped the timer when the text is changed, programatically or not
-    // Then we restart the timer here if the user touched a key
-    if (!text.isEmpty()) {
-        _completionTimer.start();
-        emit togglePublicLinkShare(true);
-    }
-}
-
-void ShareUserGroupWidget::slotLineEditReturn()
-{
-    _disableCompleterActivated = false;
-    // did the user type in one of the options?
-    const auto text = _ui->shareeLineEdit->text();
-    for (int i = 0; i < _completerModel->rowCount(); ++i) {
-        const auto sharee = _completerModel->getSharee(i);
-        if (sharee->format() == text
-            || sharee->displayName() == text
-            || sharee->shareWith() == text) {
-            slotCompleterActivated(_completerModel->index(i));
-            // make sure we do not send the same item twice (because return is called when we press
-            // return to activate an item inthe completer)
-            _disableCompleterActivated = true;
-            return;
-        }
-    }
-
-    // nothing found? try to refresh completion
-    _completionTimer.start();
-}
-
-void ShareUserGroupWidget::searchForSharees(ShareeModel::LookupMode lookupMode)
-{
-    if (_ui->shareeLineEdit->text().isEmpty()) {
-        return;
-    }
-
-    _ui->shareeLineEdit->setEnabled(false);
-    _completionTimer.stop();
-    _pi_sharee.startAnimation();
-    ShareeModel::ShareeSet blacklist;
-
-    // Add the current user to _sharees since we can't share with ourself
-    QSharedPointer<Sharee> currentUser(new Sharee(_account->credentials()->user(), "", Sharee::Type::User));
-    blacklist << currentUser;
-
-    foreach (auto sw, _parentScrollArea->findChildren<ShareUserLine *>()) {
-        blacklist << sw->share()->getShareWith();
-    }
-    _ui->errorLabel->hide();
-    _completerModel->fetch(_ui->shareeLineEdit->text(), blacklist, lookupMode);
-}
-
-void ShareUserGroupWidget::getShares()
-{
-    _manager->fetchShares(_sharePath);
-}
-
-void ShareUserGroupWidget::slotShareCreated(const QSharedPointer<Share> &share)
-{
-    if (share && _account->capabilities().shareEmailPasswordEnabled() && !_account->capabilities().shareEmailPasswordEnforced()) {
-        // remember this share Id so we can set it's password Line Edit to focus later
-        _lastCreatedShareId = share->getId();
-    }
-    // fetch all shares including the one we've just created
-    getShares();
-}
-
-void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
-{
-    int x = 0;
-    QList<QString> linkOwners({});
-
-    ShareUserLine *justCreatedShareThatNeedsPassword = nullptr;
-       
-    while (QLayoutItem *shareUserLine = _shareUserGroup->takeAt(0)) {
-        delete shareUserLine->widget();
-        delete shareUserLine;
-    }
-    
-    foreach (const auto &share, shares) {
-        // We don't handle link shares, only TypeUser or TypeGroup
-        if (share->getShareType() == Share::TypeLink) {
-            if(!share->getUidOwner().isEmpty() &&
-                    share->getUidOwner() != share->account()->davUser()){
-                linkOwners.append(share->getOwnerDisplayName());
-             }
-            continue;
-        }
-
-        // the owner of the file that shared it first
-        // leave out if it's the current user
-        if(x == 0 && !share->getUidOwner().isEmpty() && !(share->getUidOwner() == _account->credentials()->user())) {
-            _ui->mainOwnerLabel->setText(QString("Shared with you by ").append(share->getOwnerDisplayName()));
-        }
-
-
-        Q_ASSERT(Share::isShareTypeUserGroupEmailRoomOrRemote(share->getShareType()));
-        auto userGroupShare = qSharedPointerDynamicCast<UserGroupShare>(share);
-        auto *s = new ShareUserLine(_account, userGroupShare, _maxSharingPermissions, _isFile, _parentScrollArea);
-        connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
-        s->setBackgroundRole(_shareUserGroup->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase);
-
-        // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
-        connect(this, &ShareUserGroupWidget::styleChanged, s, &ShareUserLine::slotStyleChanged);
-        _shareUserGroup->addWidget(s);
-
-        if (!_lastCreatedShareId.isEmpty() && share->getId() == _lastCreatedShareId) {
-            _lastCreatedShareId = QString();
-            if (_account->capabilities().shareEmailPasswordEnabled() && !_account->capabilities().shareEmailPasswordEnforced()) {
-                justCreatedShareThatNeedsPassword = s;
-            }
-        }
-
-        x++;
-    }
-
-    foreach (const QString &owner, linkOwners) {
-        auto ownerLabel = new QLabel(QString(owner + " shared via link"));
-        _shareUserGroup->addWidget(ownerLabel);
-        ownerLabel->setVisible(true);
-    }
-    
-    _disableCompleterActivated = false;
-    activateShareeLineEdit();
-
-    if (justCreatedShareThatNeedsPassword) {
-        // always set focus to a password Line Edit when the new email share is created on a server with optional passwords enabled for email shares
-        justCreatedShareThatNeedsPassword->focusPasswordLineEdit();
-    }
-}
-
-void ShareUserGroupWidget::slotPrivateLinkShare()
-{
-    auto menu = new QMenu(this);
-    menu->setAttribute(Qt::WA_DeleteOnClose);
-
-    // this icon is not handled by slotStyleChanged() -> customizeStyle but we can live with that
-    menu->addAction(Theme::createColorAwareIcon(":/client/theme/copy.svg"),
-                    tr("Copy link"),
-        this, SLOT(slotPrivateLinkCopy()));
-
-    menu->exec(QCursor::pos());
-}
-
-void ShareUserGroupWidget::slotShareesReady()
-{
-    activateShareeLineEdit();
-
-    _pi_sharee.stopAnimation();
-    if (_completerModel->rowCount() == 0) {
-        displayError(0, tr("No results for \"%1\"").arg(_completerModel->currentSearch()));
-    }
-
-    // if no rows are present in the model - complete() will hide the completer
-    _completer->complete();
-}
-
-void ShareUserGroupWidget::slotCompleterActivated(const QModelIndex &index)
-{
-    if (_disableCompleterActivated)
-        return;
-    // The index is an index from the QCompletion model which is itelf a proxy
-    // model proxying the _completerModel
-    auto sharee = qvariant_cast<QSharedPointer<Sharee>>(index.data(Qt::UserRole));
-    if (sharee.isNull()) {
-        return;
-    }
-
-    /*
-     * Don't send the reshare permissions for federated shares for servers <9.1
-     * https://github.com/owncloud/core/issues/22122#issuecomment-185637344
-     * https://github.com/owncloud/client/issues/4996
-     */
-    _lastCreatedShareId = QString();
-    
-    QString password;
-    if (sharee->type() == Sharee::Email && _account->capabilities().shareEmailPasswordEnforced()) {
-        _ui->shareeLineEdit->clear();
-        // always show a dialog for password-enforced email shares
-        bool ok = false;
-        
-        do {
-            password = QInputDialog::getText(
-                this,
-                tr("Password for share required"),
-                tr("Please enter a password for your email share:"),
-                QLineEdit::Password,
-                QString(),
-                &ok);
-        } while (password.isEmpty() && ok);
-        
-        if (!ok) {
-            return;
-        }
-    }
-    
-    _manager->createShare(_sharePath, Share::ShareType(sharee->type()),
-        sharee->shareWith(), _maxSharingPermissions, password);
-
-    _ui->shareeLineEdit->setEnabled(false);
-    _ui->shareeLineEdit->clear();
-}
-
-void ShareUserGroupWidget::slotCompleterHighlighted(const QModelIndex &index)
-{
-    // By default the completer would set the text to EditRole,
-    // override that here.
-    _ui->shareeLineEdit->setText(index.data(Qt::DisplayRole).toString());
-}
-
-void ShareUserGroupWidget::displayError(int code, const QString &message)
-{
-    _pi_sharee.stopAnimation();
-
-    // Also remove the spinner in the widget list, if any
-    foreach (auto pi, _parentScrollArea->findChildren<QProgressIndicator *>()) {
-        delete pi;
-    }
-
-    qCWarning(lcSharing) << "Sharing error from server" << code << message;
-    _ui->errorLabel->setText(message);
-    _ui->errorLabel->show();
-    activateShareeLineEdit();
-}
-
-void ShareUserGroupWidget::slotPrivateLinkOpenBrowser()
-{
-    Utility::openBrowser(_privateLinkUrl, this);
-}
-
-void ShareUserGroupWidget::slotPrivateLinkCopy()
-{
-    QApplication::clipboard()->setText(_privateLinkUrl);
-}
-
-void ShareUserGroupWidget::slotPrivateLinkEmail()
-{
-    Utility::openEmailComposer(
-        tr("I shared something with you"),
-        _privateLinkUrl,
-        this);
-}
-
-void ShareUserGroupWidget::slotStyleChanged()
-{
-    customizeStyle();
-
-    // Notify the other widgets (ShareUserLine in this case, Dark-/Light-Mode switching)
-    emit styleChanged();
-}
-
-void ShareUserGroupWidget::customizeStyle()
-{
-    _searchGloballyAction->setIcon(Theme::createColorAwareIcon(":/client/theme/magnifying-glass.svg"));
-
-    _ui->confirmShare->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
-
-    _pi_sharee.setColor(QGuiApplication::palette().color(QPalette::Text));
-
-    foreach (auto pi, _parentScrollArea->findChildren<QProgressIndicator *>()) {
-        pi->setColor(QGuiApplication::palette().color(QPalette::Text));;
-    }
-}
-
-void ShareUserGroupWidget::activateShareeLineEdit()
-{
-    _ui->shareeLineEdit->setEnabled(true);
-    _ui->shareeLineEdit->setFocus();
-}
-
-ShareUserLine::ShareUserLine(AccountPtr account, QSharedPointer<UserGroupShare> share,
-    SharePermissions maxSharingPermissions, bool isFile, QWidget *parent)
-    : QWidget(parent)
-    , _ui(new Ui::ShareUserLine)
-    , _account(account)
-    , _share(share)
-    , _isFile(isFile)
-    , _profilePageMenu(account, share->getShareWith()->shareWith())
-{
-    Q_ASSERT(_share);
-    _ui->setupUi(this);
-
-    _ui->sharedWith->setElideMode(Qt::ElideRight);
-    _ui->sharedWith->setText(share->getShareWith()->format());
-
-    // adds permissions
-    // can edit permission
-    bool enabled = (maxSharingPermissions & SharePermissionUpdate);
-    if(!_isFile) enabled = enabled && (maxSharingPermissions & SharePermissionCreate &&
-                                      maxSharingPermissions & SharePermissionDelete);
-    _ui->permissionsEdit->setEnabled(enabled);
-    connect(_ui->permissionsEdit, &QAbstractButton::clicked, this, &ShareUserLine::slotEditPermissionsChanged);
-    connect(_ui->noteConfirmButton, &QAbstractButton::clicked, this, &ShareUserLine::onNoteConfirmButtonClicked);
-    connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareUserLine::setExpireDate);
-
-    connect(_share.data(), &UserGroupShare::noteSet, this, &ShareUserLine::disableProgessIndicatorAnimation);
-    connect(_share.data(), &UserGroupShare::noteSetError, this, &ShareUserLine::disableProgessIndicatorAnimation);
-    connect(_share.data(), &UserGroupShare::expireDateSet, this, &ShareUserLine::disableProgessIndicatorAnimation);
-
-    connect(_ui->confirmPassword, &QToolButton::clicked, this, &ShareUserLine::slotConfirmPasswordClicked);
-    connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareUserLine::slotLineEditPasswordReturnPressed);
-
-    // create menu with checkable permissions
-    auto *menu = new QMenu(this);
-    _permissionReshare= new QAction(tr("Can reshare"), this);
-    _permissionReshare->setCheckable(true);
-    _permissionReshare->setEnabled(maxSharingPermissions & SharePermissionShare);
-    menu->addAction(_permissionReshare);
-    connect(_permissionReshare, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
-
-    showNoteOptions(false);
-
-    const bool isNoteSupported = _share->getShareType() != Share::ShareType::TypeEmail && _share->getShareType() != Share::ShareType::TypeRoom;
-
-    if (isNoteSupported) {
-        _noteLinkAction = new QAction(tr("Note to recipient"));
-        _noteLinkAction->setCheckable(true);
-        menu->addAction(_noteLinkAction);
-        connect(_noteLinkAction, &QAction::triggered, this, &ShareUserLine::toggleNoteOptions);
-        if (!_share->getNote().isEmpty()) {
-            _noteLinkAction->setChecked(true);
-            showNoteOptions(true);
-        }
-    }
-
-    showExpireDateOptions(false);
-
-    const bool isExpirationDateSupported = _share->getShareType() != Share::ShareType::TypeEmail;
-
-    if (isExpirationDateSupported) {
-        // email shares do not support expiration dates
-        _expirationDateLinkAction = new QAction(tr("Set expiration date"));
-        _expirationDateLinkAction->setCheckable(true);
-        menu->addAction(_expirationDateLinkAction);
-        connect(_expirationDateLinkAction, &QAction::triggered, this, &ShareUserLine::toggleExpireDateOptions);
-        const auto expireDate = _share->getExpireDate().isValid() ? share.data()->getExpireDate() : QDate();
-        if (!expireDate.isNull()) {
-            _expirationDateLinkAction->setChecked(true);
-            showExpireDateOptions(true, expireDate);
-        }
-    }
-
-    menu->addSeparator();
-
-      // Adds action to delete share widget
-      QIcon deleteicon = QIcon::fromTheme(QLatin1String("user-trash"),QIcon(QLatin1String(":/client/theme/delete.svg")));
-      _deleteShareButton= new QAction(deleteicon,tr("Unshare"), this);
-
-    menu->addAction(_deleteShareButton);
-    connect(_deleteShareButton, &QAction::triggered, this, &ShareUserLine::on_deleteShareButton_clicked);
-
-    /*
-     * Files can't have create or delete permissions
-     */
-    if (!_isFile) {
-        _permissionCreate = new QAction(tr("Can create"), this);
-        _permissionCreate->setCheckable(true);
-        _permissionCreate->setEnabled(maxSharingPermissions & SharePermissionCreate);
-        menu->addAction(_permissionCreate);
-        connect(_permissionCreate, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
-
-        _permissionChange = new QAction(tr("Can change"), this);
-        _permissionChange->setCheckable(true);
-        _permissionChange->setEnabled(maxSharingPermissions & SharePermissionUpdate);
-        menu->addAction(_permissionChange);
-        connect(_permissionChange, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
-
-        _permissionDelete = new QAction(tr("Can delete"), this);
-        _permissionDelete->setCheckable(true);
-        _permissionDelete->setEnabled(maxSharingPermissions & SharePermissionDelete);
-        menu->addAction(_permissionDelete);
-        connect(_permissionDelete, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
-    }
-
-    // Adds action to display password widget (check box)
-    if (_share->getShareType() == Share::TypeEmail && (_share->isPasswordSet() || _account->capabilities().shareEmailPasswordEnabled())) {
-        _passwordProtectLinkAction = new QAction(tr("Password protect"), this);
-        _passwordProtectLinkAction->setCheckable(true);
-        _passwordProtectLinkAction->setChecked(_share->isPasswordSet());
-        // checkbox can be checked/unchedkec if the password is not yet set or if it's not enforced
-        _passwordProtectLinkAction->setEnabled(!_share->isPasswordSet() || !_account->capabilities().shareEmailPasswordEnforced());
-
-        menu->addAction(_passwordProtectLinkAction);
-        connect(_passwordProtectLinkAction, &QAction::triggered, this, &ShareUserLine::slotPasswordCheckboxChanged);
-
-        refreshPasswordLineEditPlaceholder();
-
-        connect(_share.data(), &Share::passwordSet, this, &ShareUserLine::slotPasswordSet);
-        connect(_share.data(), &Share::passwordSetError, this, &ShareUserLine::slotPasswordSetError);
-    }
-
-    refreshPasswordOptions();
-
-    _ui->errorLabel->hide();
-
-    _ui->permissionToolButton->setMenu(menu);
-    _ui->permissionToolButton->setPopupMode(QToolButton::InstantPopup);
-
-    _ui->passwordProgressIndicator->setVisible(false);
-
-    // Set the permissions checkboxes
-    displayPermissions();
-
-    /*
-     * We don't show permission share for federated shares with server <9.1
-     * https://github.com/owncloud/core/issues/22122#issuecomment-185637344
-     * https://github.com/owncloud/client/issues/4996
-     */
-    if (share->getShareType() == Share::TypeRemote
-        && share->account()->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) {
-        _permissionReshare->setVisible(false);
-        _ui->permissionToolButton->setVisible(false);
-    }
-
-    connect(share.data(), &Share::permissionsSet, this, &ShareUserLine::slotPermissionsSet);
-    connect(share.data(), &Share::shareDeleted, this, &ShareUserLine::slotShareDeleted);
-
-    if (!share->account()->capabilities().shareResharing()) {
-        _permissionReshare->setVisible(false);
-    }
-
-    const auto avatarEventFilter = new AvatarEventFilter(_ui->avatar);
-    connect(avatarEventFilter, &AvatarEventFilter::contextMenu, this, &ShareUserLine::onAvatarContextMenu);
-    _ui->avatar->installEventFilter(avatarEventFilter);
-
-    loadAvatar();
-
-    customizeStyle();
-}
-
-void ShareUserLine::onAvatarContextMenu(const QPoint &globalPosition)
-{
-    if (_share->getShareType() == Share::TypeUser) {
-        _profilePageMenu.exec(globalPosition);
-    }
-}
-
-void ShareUserLine::loadAvatar()
-{
-    const int avatarSize = 36;
-
-    // Set size of the placeholder
-    _ui->avatar->setMinimumHeight(avatarSize);
-    _ui->avatar->setMinimumWidth(avatarSize);
-    _ui->avatar->setMaximumHeight(avatarSize);
-    _ui->avatar->setMaximumWidth(avatarSize);
-    _ui->avatar->setAlignment(Qt::AlignCenter);
-
-    setDefaultAvatar(avatarSize);
-
-    /* Start the network job to fetch the avatar data.
-     *
-     * Currently only regular users can have avatars.
-     */
-    if (_share->getShareWith()->type() == Sharee::User) {
-        auto *job = new AvatarJob(_share->account(), _share->getShareWith()->shareWith(), avatarSize, this);
-        connect(job, &AvatarJob::avatarPixmap, this, &ShareUserLine::slotAvatarLoaded);
-        job->start();
-    }
-}
-
-void ShareUserLine::setDefaultAvatar(int avatarSize)
-{
-    /* Create the fallback avatar.
-     *
-     * This will be shown until the avatar image data arrives.
-     */
-
-    // See core/js/placeholder.js for details on colors and styling
-    const auto backgroundColor = backgroundColorForShareeType(_share->getShareWith()->type());
-    const QString style = QString(R"(* {
-        color: #fff;
-        background-color: %1;
-        border-radius: %2px;
-        text-align: center;
-        line-height: %2px;
-        font-size: %2px;
-    })").arg(backgroundColor.name(), QString::number(avatarSize / 2));
-    _ui->avatar->setStyleSheet(style);
-
-    const auto pixmap = pixmapForShareeType(_share->getShareWith()->type(), backgroundColor);
-
-    if (!pixmap.isNull()) {
-        _ui->avatar->setPixmap(pixmap);
-    } else {
-        qCDebug(lcSharing) << "pixmap is null for share type: " << _share->getShareWith()->type();
-
-        // The avatar label is the first character of the user name.
-        const auto text = _share->getShareWith()->displayName();
-        _ui->avatar->setText(text.at(0).toUpper());
-    }
-}
-
-void ShareUserLine::slotAvatarLoaded(QImage avatar)
-{
-    if (avatar.isNull())
-        return;
-
-    avatar = AvatarJob::makeCircularAvatar(avatar);
-    _ui->avatar->setPixmap(QPixmap::fromImage(avatar));
-
-    // Remove the stylesheet for the fallback avatar
-    _ui->avatar->setStyleSheet("");
-}
-
-void ShareUserLine::on_deleteShareButton_clicked()
-{
-    setEnabled(false);
-    _share->deleteShare();
-}
-
-ShareUserLine::~ShareUserLine()
-{
-    delete _ui;
-}
-
-void ShareUserLine::slotEditPermissionsChanged()
-{
-    setEnabled(false);
-
-    // Can never manually be set to "partial".
-    // This works because the state cycle for clicking is
-    // unchecked -> partial -> checked -> unchecked.
-    if (_ui->permissionsEdit->checkState() == Qt::PartiallyChecked) {
-        _ui->permissionsEdit->setCheckState(Qt::Checked);
-    }
-
-    Share::Permissions permissions = SharePermissionRead;
-
-    //  folders edit = CREATE, READ, UPDATE, DELETE
-    //  files edit = READ + UPDATE
-    if (_ui->permissionsEdit->checkState() == Qt::Checked) {
-
-        /*
-         * Files can't have create or delete permisisons
-         */
-        if (!_isFile) {
-            if (_permissionChange->isEnabled())
-                permissions |= SharePermissionUpdate;
-            if (_permissionCreate->isEnabled())
-                permissions |= SharePermissionCreate;
-            if (_permissionDelete->isEnabled())
-                permissions |= SharePermissionDelete;
-        } else {
-            permissions |= SharePermissionUpdate;
-        }
-    }
-
-    if(_isFile && _permissionReshare->isEnabled() && _permissionReshare->isChecked())
-        permissions |= SharePermissionShare;
-
-    _share->setPermissions(permissions);
-}
-
-void ShareUserLine::slotPermissionsChanged()
-{
-    setEnabled(false);
-
-    Share::Permissions permissions = SharePermissionRead;
-
-    if (_permissionReshare->isChecked())
-        permissions |= SharePermissionShare;
-
-    if (!_isFile) {
-        if (_permissionChange->isChecked())
-            permissions |= SharePermissionUpdate;
-        if (_permissionCreate->isChecked())
-            permissions |= SharePermissionCreate;
-        if (_permissionDelete->isChecked())
-            permissions |= SharePermissionDelete;
-    } else {
-        if (_ui->permissionsEdit->isChecked())
-            permissions |= SharePermissionUpdate;
-    }
-
-    _share->setPermissions(permissions);
-}
-
-void ShareUserLine::slotPasswordCheckboxChanged()
-{
-    if (!_passwordProtectLinkAction->isChecked()) {
-        _ui->errorLabel->hide();
-        _ui->errorLabel->clear();
-
-        if (!_share->isPasswordSet()) {
-            _ui->lineEdit_password->clear();
-            refreshPasswordOptions();
-        } else {
-            // do not call refreshPasswordOptions here, as it will be called after the network request is complete
-            togglePasswordSetProgressAnimation(true);
-            _share->setPassword(QString());
-        }
-    } else {
-        refreshPasswordOptions();
-
-        if (_ui->lineEdit_password->isVisible() && _ui->lineEdit_password->isEnabled()) {
-            focusPasswordLineEdit();
-        }
-    }
-}
-
-void ShareUserLine::slotDeleteAnimationFinished()
-{
-    emit resizeRequested();
-    emit visualDeletionDone();
-    deleteLater();
-
-    // There is a painting bug where a small line of this widget isn't
-    // properly cleared. This explicit repaint() call makes sure any trace of
-    // the share widget is removed once it's destroyed. #4189
-    connect(this, SIGNAL(destroyed(QObject *)), parentWidget(), SLOT(repaint()));
-}
-
-void ShareUserLine::refreshPasswordOptions()
-{
-    const bool isPasswordEnabled = _share->getShareType() == Share::TypeEmail && _passwordProtectLinkAction->isChecked();
-
-    _ui->passwordLabel->setVisible(isPasswordEnabled);
-    _ui->lineEdit_password->setEnabled(isPasswordEnabled);
-    _ui->lineEdit_password->setVisible(isPasswordEnabled);
-    _ui->confirmPassword->setVisible(isPasswordEnabled);
-
-    emit resizeRequested();
-}
-
-void ShareUserLine::refreshPasswordLineEditPlaceholder()
-{
-    if (_share->isPasswordSet()) {
-        _ui->lineEdit_password->setPlaceholderText(QString::fromUtf8(passwordIsSetPlaceholder));
-    } else {
-        _ui->lineEdit_password->setPlaceholderText("");
-    }
-}
-
-void ShareUserLine::slotPasswordSet()
-{
-    togglePasswordSetProgressAnimation(false);
-    _ui->lineEdit_password->setEnabled(true);
-    _ui->confirmPassword->setEnabled(true);
-
-    _ui->lineEdit_password->setText("");
-
-    _passwordProtectLinkAction->setEnabled(!_share->isPasswordSet() || !_account->capabilities().shareEmailPasswordEnforced());
-
-    refreshPasswordLineEditPlaceholder();
-
-    refreshPasswordOptions();
-}
-
-void ShareUserLine::slotPasswordSetError(int statusCode, const QString &message)
-{
-    qCWarning(lcSharing) << "Error from server" << statusCode << message;
-
-    togglePasswordSetProgressAnimation(false);
-
-    _ui->lineEdit_password->setEnabled(true);
-    _ui->confirmPassword->setEnabled(true);
-
-    refreshPasswordLineEditPlaceholder();
-
-    refreshPasswordOptions();
-
-    focusPasswordLineEdit();
-
-    _ui->errorLabel->show();
-    _ui->errorLabel->setText(message);
-
-    emit resizeRequested();
-}
-
-void ShareUserLine::slotShareDeleted()
-{
-    auto *animation = new QPropertyAnimation(this, "maximumHeight", this);
-
-    animation->setDuration(500);
-    animation->setStartValue(height());
-    animation->setEndValue(0);
-
-    connect(animation, &QAbstractAnimation::finished, this, &ShareUserLine::slotDeleteAnimationFinished);
-    connect(animation, &QVariantAnimation::valueChanged, this, &ShareUserLine::resizeRequested);
-
-    animation->start();
-}
-
-void ShareUserLine::slotPermissionsSet()
-{
-    displayPermissions();
-    setEnabled(true);
-}
-
-QSharedPointer<Share> ShareUserLine::share() const
-{
-    return _share;
-}
-
-void ShareUserLine::displayPermissions()
-{
-    auto perm = _share->getPermissions();
-
-//  folders edit = CREATE, READ, UPDATE, DELETE
-//  files edit = READ + UPDATE
-    if (perm & SharePermissionUpdate && (_isFile ||
-                                         (perm & SharePermissionCreate && perm & SharePermissionDelete))) {
-        _ui->permissionsEdit->setCheckState(Qt::Checked);
-    } else if (!_isFile && perm & (SharePermissionUpdate | SharePermissionCreate | SharePermissionDelete)) {
-        _ui->permissionsEdit->setCheckState(Qt::PartiallyChecked);
-    } else if(perm & SharePermissionRead) {
-        _ui->permissionsEdit->setCheckState(Qt::Unchecked);
-    }
-
-//  edit is independent of reshare
-    if (perm & SharePermissionShare)
-        _permissionReshare->setChecked(true);
-
-    if(!_isFile){
-        _permissionCreate->setChecked(perm & SharePermissionCreate);
-        _permissionChange->setChecked(perm & SharePermissionUpdate);
-        _permissionDelete->setChecked(perm & SharePermissionDelete);
-    }
-}
-
-void ShareUserLine::slotStyleChanged()
-{
-    customizeStyle();
-}
-
-void ShareUserLine::focusPasswordLineEdit()
-{
-    _ui->lineEdit_password->setFocus();
-}
-
-void ShareUserLine::customizeStyle()
-{
-    _ui->permissionToolButton->setIcon(Theme::createColorAwareIcon(":/client/theme/more.svg"));
-
-    QIcon deleteicon = QIcon::fromTheme(QLatin1String("user-trash"),Theme::createColorAwareIcon(QLatin1String(":/client/theme/delete.svg")));
-    _deleteShareButton->setIcon(deleteicon);
-
-    _ui->noteConfirmButton->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
-    _ui->progressIndicator->setColor(QGuiApplication::palette().color(QPalette::WindowText));
-
-    // make sure to force BackgroundRole to QPalette::WindowText for a lable, because it's parent always has a different role set that applies to children unless customized
-    _ui->errorLabel->setBackgroundRole(QPalette::WindowText);
-}
-
-QPixmap ShareUserLine::pixmapForShareeType(Sharee::Type type, const QColor &backgroundColor) const
-{
-    switch (type) {
-    case Sharee::Room:
-        return Ui::IconUtils::pixmapForBackground(QStringLiteral("talk-app.svg"), backgroundColor);
-    case Sharee::Email:
-        return Ui::IconUtils::pixmapForBackground(QStringLiteral("email.svg"), backgroundColor);
-    case Sharee::Group:
-    case Sharee::Federated:
-    case Sharee::Circle:
-    case Sharee::User:
-        break;
-    }
-
-    return {};
-}
-
-QColor ShareUserLine::backgroundColorForShareeType(Sharee::Type type) const
-{
-    switch (type) {
-    case Sharee::Room:
-        return Theme::instance()->wizardHeaderBackgroundColor();
-    case Sharee::Email:
-        return Theme::instance()->wizardHeaderTitleColor();
-    case Sharee::Group:
-    case Sharee::Federated:
-    case Sharee::Circle:
-    case Sharee::User:
-        break;
-    }
-
-    const auto calculateBackgroundBasedOnText = [this]() {
-        const auto hash = QCryptographicHash::hash(_ui->sharedWith->text().toUtf8(), QCryptographicHash::Md5);
-        Q_ASSERT(hash.size() > 0);
-        if (hash.size() == 0) {
-            qCWarning(lcSharing) << "Failed to calculate hash color for share:" << _share->path();
-            return QColor{};
-        }
-        const double hue = static_cast<quint8>(hash[0]) / 255.;
-        return QColor::fromHslF(hue, 0.7, 0.68);
-    };
-
-    return calculateBackgroundBasedOnText();
-}
-
-void ShareUserLine::showNoteOptions(bool show)
-{
-    _ui->noteLabel->setVisible(show);
-    _ui->noteTextEdit->setVisible(show);
-    _ui->noteConfirmButton->setVisible(show);
-
-    if (show) {
-        const auto note = _share->getNote();
-        _ui->noteTextEdit->setText(note);
-        _ui->noteTextEdit->setFocus();
-    }
-
-    emit resizeRequested();
-}
-
-
-void ShareUserLine::toggleNoteOptions(bool enable)
-{
-    showNoteOptions(enable);
-
-    if (!enable) {
-        // Delete note
-        _share->setNote(QString());
-    }
-}
-
-void ShareUserLine::onNoteConfirmButtonClicked()
-{
-    setNote(_ui->noteTextEdit->toPlainText());
-}
-
-void ShareUserLine::setNote(const QString &note)
-{
-    enableProgessIndicatorAnimation(true);
-    _share->setNote(note);
-}
-
-void ShareUserLine::toggleExpireDateOptions(bool enable)
-{
-    showExpireDateOptions(enable);
-
-    if (!enable) {
-        _share->setExpireDate(QDate());
-    }
-}
-
-void ShareUserLine::showExpireDateOptions(bool show, const QDate &initialDate)
-{
-    _ui->expirationLabel->setVisible(show);
-    _ui->calendar->setVisible(show);
-
-    if (show) {
-        _ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
-        _ui->calendar->setDate(initialDate.isValid() ? initialDate : _ui->calendar->minimumDate());
-        _ui->calendar->setFocus();
-
-        if (enforceExpirationDateForShare(_share->getShareType())) {
-            _ui->calendar->setMaximumDate(maxExpirationDateForShare(_share->getShareType(), _ui->calendar->maximumDate()));
-            _expirationDateLinkAction->setChecked(true);
-            _expirationDateLinkAction->setEnabled(false);
-        }
-    }
-
-    emit resizeRequested();
-}
-
-void ShareUserLine::setExpireDate()
-{
-    enableProgessIndicatorAnimation(true);
-    _share->setExpireDate(_ui->calendar->date());
-}
-
-void ShareUserLine::enableProgessIndicatorAnimation(bool enable)
-{
-    if (enable) {
-        if (!_ui->progressIndicator->isAnimated()) {
-            _ui->progressIndicator->startAnimation();
-        }
-    } else {
-        _ui->progressIndicator->stopAnimation();
-    }
-}
-
-void ShareUserLine::togglePasswordSetProgressAnimation(bool show)
-{
-    // button and progress indicator are interchanged depending on if the network request is in progress or not
-    _ui->confirmPassword->setVisible(!show && _passwordProtectLinkAction->isChecked());
-    _ui->passwordProgressIndicator->setVisible(show);
-    if (show) {
-        if (!_ui->passwordProgressIndicator->isAnimated()) {
-            _ui->passwordProgressIndicator->startAnimation();
-        }
-    } else {
-        _ui->passwordProgressIndicator->stopAnimation();
-    }
-}
-
-void ShareUserLine::disableProgessIndicatorAnimation()
-{
-    enableProgessIndicatorAnimation(false);
-}
-
-QDate ShareUserLine::maxExpirationDateForShare(const Share::ShareType type, const QDate &fallbackDate) const
-{
-    auto daysToExpire = 0;
-    if (type == Share::ShareType::TypeRemote) {
-        daysToExpire = _account->capabilities().shareRemoteExpireDateDays();
-    } else if (type == Share::ShareType::TypeEmail) {
-       daysToExpire = _account->capabilities().sharePublicLinkExpireDateDays();
-    } else {
-        daysToExpire = _account->capabilities().shareInternalExpireDateDays();
-    }
-
-    if (daysToExpire > 0) {
-        return QDate::currentDate().addDays(daysToExpire);
-    }
-
-    return fallbackDate;
-}
-
-bool ShareUserLine::enforceExpirationDateForShare(const Share::ShareType type) const
-{
-    if (type == Share::ShareType::TypeRemote) {
-        return _account->capabilities().shareRemoteEnforceExpireDate();
-    } else if (type == Share::ShareType::TypeEmail) {
-        return _account->capabilities().sharePublicLinkEnforceExpireDate();
-    }
-
-    return _account->capabilities().shareInternalEnforceExpireDate();
-}
-
-void ShareUserLine::setPasswordConfirmed()
-{
-    if (_ui->lineEdit_password->text().isEmpty()) {
-        return;
-    }
-
-    _ui->lineEdit_password->setEnabled(false);
-    _ui->confirmPassword->setEnabled(false);
-
-    _ui->errorLabel->hide();
-    _ui->errorLabel->clear();
-
-    togglePasswordSetProgressAnimation(true);
-    _share->setPassword(_ui->lineEdit_password->text());
-}
-
-void ShareUserLine::slotLineEditPasswordReturnPressed()
-{
-    setPasswordConfirmed();
-}
-
-void ShareUserLine::slotConfirmPasswordClicked()
-{
-    setPasswordConfirmed();
-}
-}
diff --git a/src/gui/shareusergroupwidget.h b/src/gui/shareusergroupwidget.h
deleted file mode 100644 (file)
index 9613200..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@owncloud.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.
- */
-
-#ifndef SHAREUSERGROUPWIDGET_H
-#define SHAREUSERGROUPWIDGET_H
-
-#include "accountfwd.h"
-#include "sharemanager.h"
-#include "sharepermissions.h"
-#include "sharee.h"
-#include "profilepagewidget.h"
-#include "QProgressIndicator.h"
-#include <QDialog>
-#include <QWidget>
-#include <QSharedPointer>
-#include <QList>
-#include <QVector>
-#include <QTimer>
-#include <qpushbutton.h>
-#include <qscrollarea.h>
-
-class QAction;
-class QCompleter;
-class QModelIndex;
-
-namespace OCC {
-
-namespace Ui {
-    class ShareUserGroupWidget;
-    class ShareUserLine;
-}
-
-class AbstractCredentials;
-class SyncResult;
-class Share;
-class ShareManager;
-
-class AvatarEventFilter : public QObject
-{
-    Q_OBJECT
-
-public:
-    explicit AvatarEventFilter(QObject *parent = nullptr);
-
-signals:
-    void clicked();
-    void contextMenu(const QPoint &globalPosition);
-
-protected:
-    bool eventFilter(QObject *obj, QEvent *event) override;
-};
-
-/**
- * @brief The ShareDialog (user/group) class
- * @ingroup gui
- */
-class ShareUserGroupWidget : public QWidget
-{
-    Q_OBJECT
-
-public:
-    explicit ShareUserGroupWidget(AccountPtr account,
-        const QString &sharePath,
-        const QString &localPath,
-        SharePermissions maxSharingPermissions,
-        const QString &privateLinkUrl,
-        QWidget *parent = nullptr);
-    ~ShareUserGroupWidget() override;
-    
-    QVBoxLayout *shareUserGroupLayout();
-
-signals:
-    void togglePublicLinkShare(bool);
-    void styleChanged();
-
-public slots:
-    void getShares();
-    void slotShareCreated(const QSharedPointer<Share> &share);
-    void slotStyleChanged();
-
-private slots:
-    void slotSharesFetched(const QList<QSharedPointer<Share>> &shares);
-
-    void on_shareeLineEdit_textChanged(const QString &text);
-    void searchForSharees(ShareeModel::LookupMode lookupMode);
-    void slotLineEditTextEdited(const QString &text);
-
-    void slotLineEditReturn();
-    void slotCompleterActivated(const QModelIndex &index);
-    void slotCompleterHighlighted(const QModelIndex &index);
-    void slotShareesReady();
-    void slotPrivateLinkShare();
-    void displayError(int code, const QString &message);
-
-    void slotPrivateLinkOpenBrowser();
-    void slotPrivateLinkCopy();
-    void slotPrivateLinkEmail();
-
-private:
-    void customizeStyle();
-
-    void activateShareeLineEdit();
-
-    Ui::ShareUserGroupWidget *_ui;
-    QScopedPointer<QAction> _searchGloballyAction;
-    QScrollArea *_parentScrollArea;
-    QVBoxLayout *_shareUserGroup;
-    AccountPtr _account;
-    QString _sharePath;
-    QString _localPath;
-    SharePermissions _maxSharingPermissions;
-    QString _privateLinkUrl;
-
-    QCompleter *_completer;
-    ShareeModel *_completerModel;
-    QTimer _completionTimer;
-
-    bool _isFile;
-    bool _disableCompleterActivated; // in order to avoid that we share the contents twice
-    ShareManager *_manager;
-
-    QProgressIndicator _pi_sharee;
-
-    QString _lastCreatedShareId;
-};
-
-/**
- * The widget displayed for each user/group share
- */
-class ShareUserLine : public QWidget
-{
-    Q_OBJECT
-
-public:
-    explicit ShareUserLine(AccountPtr account,
-        QSharedPointer<UserGroupShare> Share,
-        SharePermissions maxSharingPermissions,
-        bool isFile,
-        QWidget *parent = nullptr);
-    ~ShareUserLine() override;
-
-    [[nodiscard]] QSharedPointer<Share> share() const;
-
-signals:
-    void visualDeletionDone();
-    void resizeRequested();
-
-public slots:
-    void slotStyleChanged();
-
-    void focusPasswordLineEdit();
-
-private slots:
-    void on_deleteShareButton_clicked();
-    void slotPermissionsChanged();
-    void slotEditPermissionsChanged();
-    void slotPasswordCheckboxChanged();
-    void slotDeleteAnimationFinished();
-
-    void refreshPasswordOptions();
-
-    void refreshPasswordLineEditPlaceholder();
-
-    void slotPasswordSet();
-    void slotPasswordSetError(int statusCode, const QString &message);
-
-    void slotShareDeleted();
-    void slotPermissionsSet();
-
-    void slotAvatarLoaded(QImage avatar);
-
-    void setPasswordConfirmed();
-
-    void slotLineEditPasswordReturnPressed();
-
-    void slotConfirmPasswordClicked();
-
-    void onAvatarContextMenu(const QPoint &globalPosition);
-
-private:
-    void displayPermissions();
-    void loadAvatar();
-    void setDefaultAvatar(int avatarSize);
-    void customizeStyle();
-
-    [[nodiscard]] QPixmap pixmapForShareeType(Sharee::Type type, const QColor &backgroundColor = QColor()) const;
-    [[nodiscard]] QColor backgroundColorForShareeType(Sharee::Type type) const;
-
-  void showNoteOptions(bool show);
-  void toggleNoteOptions(bool enable);
-  void onNoteConfirmButtonClicked();
-  void setNote(const QString &note);
-
-  void toggleExpireDateOptions(bool enable);
-  void showExpireDateOptions(bool show, const QDate &initialDate = QDate());
-  void setExpireDate();
-
-  void togglePasswordSetProgressAnimation(bool show);
-
-  void enableProgessIndicatorAnimation(bool enable);
-  void disableProgessIndicatorAnimation();
-
-  [[nodiscard]] QDate maxExpirationDateForShare(const Share::ShareType type, const QDate &fallbackDate) const;
-  [[nodiscard]] bool enforceExpirationDateForShare(const Share::ShareType type) const;
-
-  Ui::ShareUserLine *_ui;
-  AccountPtr _account;
-  QSharedPointer<UserGroupShare> _share;
-  bool _isFile;
-
-  ProfilePageMenu _profilePageMenu;
-
-  // _permissionEdit is a checkbox
-  QAction *_permissionReshare;
-  QAction *_deleteShareButton;
-  QAction *_permissionCreate;
-  QAction *_permissionChange;
-  QAction *_permissionDelete;
-  QAction *_noteLinkAction;
-  QAction *_expirationDateLinkAction;
-  QAction *_passwordProtectLinkAction;
-};
-}
-
-#endif // SHAREUSERGROUPWIDGET_H
diff --git a/src/gui/shareusergroupwidget.ui b/src/gui/shareusergroupwidget.ui
deleted file mode 100644 (file)
index 38c45a2..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>OCC::ShareUserGroupWidget</class>
- <widget class="QWidget" name="OCC::ShareUserGroupWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>350</width>
-    <height>106</height>
-   </rect>
-  </property>
-  <property name="sizePolicy">
-   <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
-    <horstretch>0</horstretch>
-    <verstretch>0</verstretch>
-   </sizepolicy>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <property name="spacing">
-    <number>6</number>
-   </property>
-   <property name="leftMargin">
-    <number>6</number>
-   </property>
-   <property name="topMargin">
-    <number>6</number>
-   </property>
-   <property name="rightMargin">
-    <number>6</number>
-   </property>
-   <property name="bottomMargin">
-    <number>6</number>
-   </property>
-   <item>
-    <widget class="QLabel" name="mainOwnerLabel">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="text">
-      <string/>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="shareeHorizontalLayout" stretch="0,0">
-     <property name="spacing">
-      <number>6</number>
-     </property>
-     <property name="leftMargin">
-      <number>0</number>
-     </property>
-     <property name="topMargin">
-      <number>0</number>
-     </property>
-     <property name="rightMargin">
-      <number>0</number>
-     </property>
-     <property name="bottomMargin">
-      <number>0</number>
-     </property>
-     <item>
-      <widget class="QLineEdit" name="shareeLineEdit">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="placeholderText">
-        <string>Share with users or groups …</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QToolButton" name="confirmShare">
-       <property name="icon">
-        <iconset resource="../../theme.qrc">
-         <normaloff>:/client/theme/confirm.svg</normaloff>:/client/theme/confirm.svg</iconset>
-       </property>
-       <property name="autoRaise">
-        <bool>true</bool>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <widget class="QLabel" name="errorLabel">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="palette">
-      <palette>
-       <active>
-        <colorrole role="WindowText">
-         <brush brushstyle="SolidPattern">
-          <color alpha="255">
-           <red>255</red>
-           <green>0</green>
-           <blue>0</blue>
-          </color>
-         </brush>
-        </colorrole>
-       </active>
-       <inactive>
-        <colorrole role="WindowText">
-         <brush brushstyle="SolidPattern">
-          <color alpha="255">
-           <red>255</red>
-           <green>0</green>
-           <blue>0</blue>
-          </color>
-         </brush>
-        </colorrole>
-       </inactive>
-       <disabled>
-        <colorrole role="WindowText">
-         <brush brushstyle="SolidPattern">
-          <color alpha="255">
-           <red>123</red>
-           <green>121</green>
-           <blue>134</blue>
-          </color>
-         </brush>
-        </colorrole>
-       </disabled>
-      </palette>
-     </property>
-     <property name="text">
-      <string notr="true">Placeholder for Error text</string>
-     </property>
-     <property name="textFormat">
-      <enum>Qt::PlainText</enum>
-     </property>
-     <property name="wordWrap">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <layoutdefault spacing="6" margin="11"/>
- <resources>
-  <include location="../../theme.qrc"/>
- </resources>
- <connections/>
-</ui>
index 4de3317a2584e9419bd36f74e4cddcd06a819589..e023dd375991fe9fe45f1cc2cb981809fa910fa9 100644 (file)
@@ -497,10 +497,10 @@ void SocketApi::broadcastMessage(const QString &msg, bool doWait)
 void SocketApi::processFileActivityRequest(const QString &localFile)
 {
     const auto fileData = FileData::get(localFile);
-    emit fileActivityCommandReceived(fileData.serverRelativePath, fileData.journalRecord().numericFileId().toInt());
+    emit fileActivityCommandReceived(fileData.localPath);
 }
 
-void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage)
+void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener)
 {
     auto theme = Theme::instance();
 
@@ -537,7 +537,7 @@ void SocketApi::processShareRequest(const QString &localFile, SocketListener *li
         const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
         listener->sendMessage(message);
 
-        emit shareCommandReceived(remotePath, fileData.localPath, startPage);
+        emit shareCommandReceived(fileData.localPath);
     }
 }
 
@@ -581,7 +581,7 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList
 
 void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener)
 {
-    processShareRequest(localFile, listener, ShareDialogStartPage::UsersAndGroups);
+    processShareRequest(localFile, listener);
 }
 
 void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *listener)
@@ -593,7 +593,7 @@ void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *liste
 
 void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener)
 {
-    processShareRequest(localFile, listener, ShareDialogStartPage::PublicLinks);
+    processShareRequest(localFile, listener);
 }
 
 void SocketApi::command_VERSION(const QString &, SocketListener *listener)
@@ -673,7 +673,7 @@ public:
     }
 
 private slots:
-    void sharesFetched(const QList<QSharedPointer<Share>> &shares)
+    void sharesFetched(const QList<SharePtr> &shares)
     {
         auto shareName = SocketApi::tr("Context menu share");
 
@@ -783,7 +783,7 @@ void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListene
     connect(job, &GetOrCreatePublicLinkShare::done, this,
         [](const QString &url) { copyUrlToClipboard(url); });
     connect(job, &GetOrCreatePublicLinkShare::error, this,
-        [=]() { emit shareCommandReceived(fileData.serverRelativePath, fileData.localPath, ShareDialogStartPage::PublicLinks); });
+        [=]() { emit shareCommandReceived(fileData.localPath); });
     job->run();
 }
 
index f3529f870d492c0f5dd9180ca89871510fbcbbbb..5f16a00fcc13bee61683d32440637c4c73a1e432 100644 (file)
@@ -17,7 +17,6 @@
 
 #include "syncfileitem.h"
 #include "common/syncfilestatus.h"
-#include "sharedialog.h" // for the ShareDialogStartPage
 #include "common/syncjournalfilerecord.h"
 
 #include "config.h"
@@ -63,8 +62,8 @@ public slots:
     void broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus);
 
 signals:
-    void shareCommandReceived(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage);
-    void fileActivityCommandReceived(const QString &objectName, const int objectId);
+    void shareCommandReceived(const QString &localPath);
+    void fileActivityCommandReceived(const QString &localPath);
 
 private slots:
     void slotNewConnection();
@@ -102,7 +101,7 @@ private:
     void broadcastMessage(const QString &msg, bool doWait = false);
 
     // opens share dialog, sends reply
-    void processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage);
+    void processShareRequest(const QString &localFile, SocketListener *listener);
     void processFileActivityRequest(const QString &localFile);
 
     Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener);
index e52f62bc81b54c75db7c77b3be293f716fee6d0a..d3cb9a22cb051da0bea1613ad00f4242498c6dc4 100644 (file)
@@ -285,6 +285,91 @@ void Systray::destroyEditFileLocallyLoadingDialog()
     _editFileLocallyLoadingDialog = nullptr;
 }
 
+bool Systray::raiseDialogs()
+{
+    if(_dialogs.empty()) {
+        return false;
+    }
+
+    QVector<QSharedPointer<QQuickWindow>> liveDialogs;
+
+    for(const auto &dialog : _dialogs) {
+        if(dialog.isNull()) {
+            continue;
+        } else if(!dialog->isVisible()) {
+            destroyDialog(dialog.data());
+            continue;
+        }
+
+        liveDialogs.append(dialog);
+
+        dialog->show();
+        dialog->raise();
+        dialog->requestActivate();
+    }
+
+    _dialogs = liveDialogs;
+
+    // If it is empty then we have raised no dialogs, so return false (and viceversa)
+    return !liveDialogs.empty();
+}
+
+void Systray::createFileDetailsDialog(const QString &localPath)
+{
+    qCDebug(lcSystray) << "Opening new file details dialog for " << localPath;
+
+    if(!_trayEngine) {
+        qCWarning(lcSystray) << "Could not open file details dialog for" << localPath << "as no tray engine was available";
+        return;
+    }
+
+    const auto folder = FolderMan::instance()->folderForPath(localPath);
+    if (!folder) {
+        qCWarning(lcSystray) << "Could not open file details dialog for" << localPath << "no responsible folder found";
+        return;
+    }
+
+    const QVariantMap initialProperties{
+        {"accountState", QVariant::fromValue(folder->accountState())},
+        {"localPath", localPath},
+    };
+
+    const auto fileDetailsDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/filedetails/FileDetailsWindow.qml"));
+
+    if (fileDetailsDialog && !fileDetailsDialog->isError()) {
+        const auto createdDialog = fileDetailsDialog->createWithInitialProperties(initialProperties);
+        const QSharedPointer<QQuickWindow> dialog(qobject_cast<QQuickWindow*>(createdDialog));
+
+        if(dialog.isNull()) {
+            qCWarning(lcSystray) << "File details dialog window resulted in creation of object that was not a window!";
+            return;
+        }
+
+        _dialogs.append(dialog);
+
+        dialog->show();
+        dialog->raise();
+        dialog->requestActivate();
+
+    } else if (fileDetailsDialog) {
+        qCWarning(lcSystray) << fileDetailsDialog->errorString();
+    } else {
+        qCWarning(lcSystray) << "Unable to open share dialog for unknown reasons...";
+    }
+}
+
+void Systray::createShareDialog(const QString &localPath)
+{
+    createFileDetailsDialog(localPath);
+    Q_EMIT showFileDetailsPage(localPath, FileDetailsPage::Sharing);
+}
+
+void Systray::createFileActivityDialog(const QString &localPath)
+{
+    createFileDetailsDialog(localPath);
+    Q_EMIT showFileDetailsPage(localPath, FileDetailsPage::Activity);
+}
+
 void Systray::slotCurrentUserChanged()
 {
     if (_trayEngine) {
index 7350011fc8527cdd53b6aa515b6c4d034d890aaf..d890f9fb420026dcfd112e96b6f347eb26479a6b 100644 (file)
@@ -82,12 +82,17 @@ public:
     enum class WindowPosition { Default, Center };
     Q_ENUM(WindowPosition);
 
+    enum class FileDetailsPage { Activity, Sharing };
+    Q_ENUM(FileDetailsPage);
+
     Q_REQUIRED_RESULT QString windowTitle() const;
     Q_REQUIRED_RESULT bool useNormalWindow() const;
 
     Q_REQUIRED_RESULT bool syncIsPaused() const;
     Q_REQUIRED_RESULT bool isOpen() const;
 
+    Q_REQUIRED_RESULT bool raiseDialogs();
+
 signals:
     void currentUserChanged();
     void openAccountWizard();
@@ -95,8 +100,7 @@ signals:
     void openHelp();
     void shutdown();
 
-    void openShareDialog(const QString &sharePath, const QString &localPath);
-    void showFileActivityDialog(const QString &objectName, const int objectId);
+    void showFileDetailsPage(const QString &fileLocalPath, const FileDetailsPage page);
     void sendChatMessage(const QString &token, const QString &message, const QString &replyTo);
     void showErrorMessageDialog(const QString &error);
 
@@ -132,6 +136,9 @@ public slots:
     void setSyncIsPaused(const bool syncIsPaused);
     void setIsOpen(const bool isOpen);
 
+    void createShareDialog(const QString &localPath);
+    void createFileActivityDialog(const QString &localPath);
+
 private slots:
     void slotUnpauseAllFolders();
     void slotPauseAllFolders();
@@ -143,6 +150,7 @@ private:
     Systray();
 
     void setupContextMenu();
+    void createFileDetailsDialog(const QString &localPath);
 
     [[nodiscard]] QScreen *currentScreen() const;
     [[nodiscard]] QRect currentScreenRect() const;
@@ -164,8 +172,8 @@ private:
     AccessManagerFactory _accessManagerFactory;
 
     QSet<qlonglong> _callsAlreadyNotified;
-
     QPointer<QObject> _editFileLocallyLoadingDialog;
+    QVector<QSharedPointer<QQuickWindow>> _dialogs;
 };
 
 } // namespace OCC
index 020555ed36dac7565b735b5f8576f6f9fadad981..eb06c7213c39e858acc03d648fa677d48b13585f 100644 (file)
@@ -10,6 +10,8 @@ ItemDelegate {
 
     property Flickable flickable
 
+    property int iconSize: Style.trayListItemIconSize
+
     property bool isFileActivityList: false
 
     readonly property bool isChatActivity: model.objectType === "chat" || model.objectType === "room" || model.objectType === "call"
@@ -45,9 +47,11 @@ ItemDelegate {
 
             showDismissButton: model.links.length > 0
 
+            iconSize: root.iconSize
+
             activityData: model
 
-            onShareButtonClicked: Systray.openShareDialog(model.displayPath, model.path)
+            onShareButtonClicked: Systray.createShareDialog(model.openablePath)
 
             onDismissButtonClicked: activityModel.slotTriggerDismiss(model.activityIndex)
         }
index 51c653312e5b37fda14de1e8cfbbc37c2a85326e..80bc931fc7460bf728290d9e6fe770f60c421733 100644 (file)
@@ -17,6 +17,8 @@ RowLayout {
 
     property bool childHovered: shareButton.hovered || dismissActionButton.hovered
 
+    property int iconSize: Style.trayListItemIconSize
+
     signal dismissButtonClicked()
     signal shareButtonClicked()
 
@@ -25,8 +27,8 @@ RowLayout {
     Item {
         id: thumbnailItem
         Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
-        Layout.preferredWidth: Style.trayListItemIconSize
-        Layout.preferredHeight: model.thumbnail && model.thumbnail.isMimeTypeIcon ? Style.trayListItemIconSize * 0.9 : Style.trayListItemIconSize
+        Layout.preferredWidth: root.iconSize
+        Layout.preferredHeight: model.thumbnail && model.thumbnail.isMimeTypeIcon ? root.iconSize * 0.9 : root.iconSize
         readonly property int imageWidth: width * (1 - Style.thumbnailImageSizeReduction)
         readonly property int imageHeight: height * (1 - Style.thumbnailImageSizeReduction)
         readonly property int thumbnailRadius: model.thumbnail && model.thumbnail.isUserAvatar ? width / 2 : 3
index b108f3cca03221a7689be3619941a9bb88ee1aed..d01c566e1b7ab6cd6a7cb686f9cb42cc14d9a9ac 100644 (file)
@@ -1,6 +1,7 @@
 import QtQuick 2.15
 import QtQuick.Controls 2.15
 
+import Style 1.0
 import com.nextcloud.desktopclient 1.0 as NC
 import Style 1.0
 
@@ -9,6 +10,7 @@ ScrollView {
     property alias model: sortedActivityList.activityListModel
 
     property bool isFileActivityList: false
+    property int iconSize: Style.trayListItemIconSize
 
     signal openFile(string filePath)
     signal activityItemClicked(int index)
@@ -55,6 +57,7 @@ ScrollView {
 
         delegate: ActivityItem {
             isFileActivityList: controlRoot.isFileActivityList
+            iconSize: controlRoot.iconSize
             width: activityList.contentWidth
             flickable: activityList
             onHoveredChanged: if (hovered) {
diff --git a/src/gui/tray/FileActivityDialog.qml b/src/gui/tray/FileActivityDialog.qml
deleted file mode 100644 (file)
index 1d75ff2..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-import QtQml 2.15
-import QtQuick 2.15
-import QtQuick.Window 2.15
-
-import Style 1.0
-import com.nextcloud.desktopclient 1.0 as NC
-
-Window {
-    id: dialog
-
-    property alias model: activityModel
-
-    NC.FileActivityListModel {
-        id: activityModel
-    }   
-
-    width: 500
-    height: 500
-
-    Rectangle {
-        id: background
-        anchors.fill: parent
-        color: Style.backgroundColor
-    }
-
-    ActivityList {
-        isFileActivityList: true
-        anchors.fill: parent
-        model: dialog.model
-    }
-
-    Component.onCompleted: {
-        dialog.show();
-        dialog.raise();
-        dialog.requestActivate();
-
-        Systray.forceWindowInit(dialog);
-        Systray.positionWindowAtScreenCenter(dialog);
-    }
-}
index 94611c6474a41fe249c2e39ddba72dbe5002a7ff..c462b305f9910569bddc5cf0f529b811a0b0c031 100644 (file)
@@ -21,16 +21,8 @@ ApplicationWindow {
     color:      "transparent"\r
     flags:      Systray.useNormalWindow ? Qt.Window : Qt.Dialog | Qt.FramelessWindowHint\r
 \r
-    property int fileActivityDialogObjectId: -1\r
-\r
     readonly property int maxMenuHeight: Style.trayWindowHeight - Style.trayWindowHeaderHeight - 2 * Style.trayWindowBorderWidth\r
 \r
-    function openFileActivityDialog(objectName, objectId) {\r
-        fileActivityDialogLoader.objectName = objectName;\r
-        fileActivityDialogLoader.objectId = objectId;\r
-        fileActivityDialogLoader.refresh();\r
-    }\r
-\r
     Component.onCompleted: Systray.forceWindowInit(trayWindow)\r
 \r
     // Close tray window when focus is lost (e.g. click somewhere else on the screen)\r
@@ -91,10 +83,6 @@ ApplicationWindow {
             }\r
         }\r
 \r
-        function onShowFileActivityDialog(objectName, objectId) {\r
-            openFileActivityDialog(objectName, objectId)\r
-        }\r
-\r
         function onShowErrorMessageDialog(error) {\r
             var newErrorDialog = errorMessageDialog.createObject(trayWindow)\r
             newErrorDialog.text = error\r
@@ -819,26 +807,5 @@ ApplicationWindow {
                 model.slotTriggerDefaultAction(index)\r
             }\r
         }\r
-\r
-        Loader {\r
-            id: fileActivityDialogLoader\r
-\r
-            property string objectName: ""\r
-            property int objectId: -1\r
-\r
-            function refresh() {\r
-                active = true\r
-                item.model.load(activityModel.accountState, objectId)\r
-                item.show()\r
-            }\r
-\r
-            active: false\r
-            sourceComponent: FileActivityDialog {\r
-                title: qsTr("%1 - File activity").arg(fileActivityDialogLoader.objectName)\r
-                onClosing: fileActivityDialogLoader.active = false\r
-            }\r
-\r
-            onLoaded: refresh()\r
-        }\r
     } // Item trayWindowMainItem\r
 }\r
index 8ebdcfef573fd31ab9841c80c7a39eeea01b4185..352f23a559e282aa7fe16ed244fd754ed15d666e 100644 (file)
@@ -93,6 +93,7 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
 void ActivityListModel::setAccountState(AccountState *state)
 {
     _accountState = state;
+    Q_EMIT accountStateChanged();
 }
 
 void ActivityListModel::setCurrentItem(const int currentItem)
index 1c070487da918263e52b4b0ce2ba47e73ab665ed..e030445ed10a8170d40c34b6987a30bacb6981be 100644 (file)
@@ -39,9 +39,8 @@ class InvalidFilenameDialog;
 class ActivityListModel : public QAbstractListModel
 {
     Q_OBJECT
-
     Q_PROPERTY(quint32 maxActionButtons READ maxActionButtons CONSTANT)
-    Q_PROPERTY(AccountState *accountState READ accountState CONSTANT)
+    Q_PROPERTY(AccountState *accountState READ accountState WRITE setAccountState NOTIFY accountStateChanged)
 
 public:
     enum DataRole {
@@ -123,6 +122,8 @@ public slots:
     void setCurrentItem(const int currentItem);
 
 signals:
+    void accountStateChanged();
+
     void activityJobStatusCode(int statusCode);
     void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
 
index f393c837a70a2d13b71e5426d6fa804a630af4bf..6bcee6778cbb854396fc35236a46a27c83bfa45e 100644 (file)
@@ -63,17 +63,30 @@ void AsyncImageResponse::processNextImage()
         return;
     }
 
-    if (_imagePaths.at(_index).startsWith(QStringLiteral(":/client"))) {
-        setImageAndEmitFinished(QIcon(_imagePaths.at(_index)).pixmap(_requestedImageSize).toImage());
+    const auto imagePath = _imagePaths.at(_index);
+    if (imagePath.startsWith(QStringLiteral(":/client"))) {
+        setImageAndEmitFinished(QIcon(imagePath).pixmap(_requestedImageSize).toImage());
         return;
+    } else if (imagePath.startsWith(QStringLiteral(":/fileicon"))) {
+        const auto filePath = imagePath.mid(10);
+        const auto fileInfo = QFileInfo(filePath);
+        setImageAndEmitFinished(_fileIconProvider.icon(fileInfo).pixmap(_requestedImageSize).toImage());
+        return;
+    }
+
+    OCC::AccountPtr accountInRequestedServer;
+
+    for (const auto &account : OCC::AccountManager::instance()->accounts()) {
+        if (account && account->account() && imagePath.startsWith(account->account()->url().toString())) {
+           accountInRequestedServer = account->account();
+        }
     }
 
-    const auto currentUser = OCC::UserModel::instance()->currentUser();
-    if (currentUser && currentUser->account()) {
+    if (accountInRequestedServer) {
         const QUrl iconUrl(_imagePaths.at(_index));
         if (iconUrl.isValid() && !iconUrl.scheme().isEmpty()) {
             // fetch the remote resource
-            const auto reply = currentUser->account()->sendRawRequest(QByteArrayLiteral("GET"), iconUrl);
+            const auto reply = accountInRequestedServer->sendRawRequest(QByteArrayLiteral("GET"), iconUrl);
             connect(reply, &QNetworkReply::finished, this, &AsyncImageResponse::slotProcessNetworkReply);
             ++_index;
             return;
index e27c7b99c394a0feb509a1432bfeb7cf54825e51..9d2d60003f19d55074774217ef1ba554b9ae72bf 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <QImage>
 #include <QQuickImageProvider>
+#include <QFileIconProvider>
 
 class AsyncImageResponse : public QQuickImageResponse
 {
@@ -34,5 +35,6 @@ private slots:
     QStringList _imagePaths;
     QSize _requestedImageSize;
     QColor _svgRecolor;
+    QFileIconProvider _fileIconProvider;
     int _index = 0;
 };