diff --git a/src/gui/filedetails/ShareView.qml b/src/gui/filedetails/ShareView.qml index e7e6046b7f38e..51bbb891a8897 100644 --- a/src/gui/filedetails/ShareView.qml +++ b/src/gui/filedetails/ShareView.qml @@ -139,6 +139,34 @@ ColumnLayout { } } + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: root.horizontalPadding + Layout.rightMargin: root.horizontalPadding + + Image { + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + source: shareModel.shareOwnerAvatar + } + + ColumnLayout { + EnforcedPlainTextLabel { + Layout.fillWidth: true + visible: shareModel.displayShareOwner + text: qsTr("Shared with you by %1").arg(shareModel.shareOwnerDisplayName) + font.bold: true + } + EnforcedPlainTextLabel { + Layout.fillWidth: true + visible: shareModel.sharedWithMeExpires + text: qsTr("Expires in %1").arg(shareModel.sharedWithMeRemainingTimeString) + } + } + + visible: shareModel.displayShareOwner + } + ShareeSearchField { id: shareeSearchField Layout.fillWidth: true diff --git a/src/gui/filedetails/sharemodel.cpp b/src/gui/filedetails/sharemodel.cpp index 2a314c3faec65..a199231998ad7 100644 --- a/src/gui/filedetails/sharemodel.cpp +++ b/src/gui/filedetails/sharemodel.cpp @@ -220,11 +220,21 @@ void ShareModel::resetData() _fetchOngoing = false; _hasInitialShareFetchCompleted = false; _sharees.clear(); + _displayShareOwner = false; + _shareOwnerDisplayName.clear(); + _shareOwnerAvatar.clear(); + _sharedWithMeExpires = false; + _sharedWithMeRemainingTimeString.clear(); Q_EMIT sharePermissionsChanged(); Q_EMIT fetchOngoingChanged(); Q_EMIT hasInitialShareFetchCompletedChanged(); Q_EMIT shareesChanged(); + Q_EMIT displayShareOwnerChanged(); + Q_EMIT shareOwnerDisplayNameChanged(); + Q_EMIT shareOwnerAvatarChanged(); + Q_EMIT sharedWithMeExpiresChanged(); + Q_EMIT sharedWithMeRemainingTimeStringChanged(); endResetModel(); } @@ -319,7 +329,9 @@ void ShareModel::updateData() auto job = new PropfindJob(_accountState->account(), _sharePath); job->setProperties(QList() << "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"); + << "http://owncloud.org/ns:privatelink" + << "http://owncloud.org/ns:owner-id" + << "http://owncloud.org/ns:owner-display-name"); job->setTimeout(10 * 1000); connect(job, &PropfindJob::result, this, &ShareModel::slotPropfindReceived); connect(job, &PropfindJob::finishedWithError, this, [&](const QNetworkReply *reply) { @@ -477,14 +489,38 @@ void ShareModel::slotSharesFetched(const QList &shares) qCInfo(lcSharing) << "Fetched" << shares.count() << "shares"; for (const auto &share : shares) { - if (share.isNull() || - share->account().isNull() || - share->getUidOwner() != share->account()->davUser()) { - + if (share.isNull()) { continue; + } else if (const auto selfUserId = share->account()->davUser(); share->getUidOwner() != selfUserId) { + _displayShareOwner = true; + Q_EMIT displayShareOwnerChanged(); + _shareOwnerDisplayName = share->getOwnerDisplayName(); + Q_EMIT shareOwnerDisplayNameChanged(); + _shareOwnerAvatar = "image://avatars/user-id=" + + share->getUidOwner() + + "/local-account:" + + share->account()->displayName(); + Q_EMIT shareOwnerAvatarChanged(); + + if (share->getShareType() == Share::TypeUser && + share->getShareWith() && + share->getShareWith()->shareWith() == selfUserId) + { + const auto userShare = share.objectCast(); + const auto expireDate = userShare->getExpireDate(); + const auto daysToExpire = QDate::currentDate().daysTo(expireDate); + _sharedWithMeExpires = expireDate.isValid(); + Q_EMIT sharedWithMeExpiresChanged(); + _sharedWithMeRemainingTimeString = daysToExpire > 1 + ? tr("%1 days").arg(daysToExpire) + : daysToExpire > 0 + ? tr("1 day") + : tr("Today"); + Q_EMIT sharedWithMeRemainingTimeStringChanged(); + } + } else { + slotAddShare(share); } - - slotAddShare(share); } // Perform forward pass on shares and check for duplicate display names; store these indeces so @@ -1379,6 +1415,31 @@ bool ShareModel::isShareDisabledEncryptedFolder() const return _isShareDisabledEncryptedFolder; } +bool ShareModel::displayShareOwner() const +{ + return _displayShareOwner; +} + +QString ShareModel::shareOwnerDisplayName() const +{ + return _shareOwnerDisplayName; +} + +QString ShareModel::shareOwnerAvatar() const +{ + return _shareOwnerAvatar; +} + +QString ShareModel::sharedWithMeRemainingTimeString() const +{ + return _sharedWithMeRemainingTimeString; +} + +bool ShareModel::sharedWithMeExpires() const +{ + return _sharedWithMeExpires; +} + QVariantList ShareModel::sharees() const { QVariantList returnSharees; diff --git a/src/gui/filedetails/sharemodel.h b/src/gui/filedetails/sharemodel.h index 09d136f1ea0f9..5357a9366d6f3 100644 --- a/src/gui/filedetails/sharemodel.h +++ b/src/gui/filedetails/sharemodel.h @@ -38,6 +38,11 @@ class ShareModel : public QAbstractListModel Q_PROPERTY(bool hasInitialShareFetchCompleted READ hasInitialShareFetchCompleted NOTIFY hasInitialShareFetchCompletedChanged) Q_PROPERTY(bool serverAllowsResharing READ serverAllowsResharing NOTIFY serverAllowsResharingChanged) Q_PROPERTY(QVariantList sharees READ sharees NOTIFY shareesChanged) + Q_PROPERTY(bool displayShareOwner READ displayShareOwner NOTIFY displayShareOwnerChanged) + Q_PROPERTY(QString shareOwnerDisplayName READ shareOwnerDisplayName NOTIFY shareOwnerDisplayNameChanged) + Q_PROPERTY(QString shareOwnerAvatar READ shareOwnerAvatar NOTIFY shareOwnerAvatarChanged) + Q_PROPERTY(bool sharedWithMeExpires READ sharedWithMeExpires NOTIFY sharedWithMeExpiresChanged) + Q_PROPERTY(QString sharedWithMeRemainingTimeString READ sharedWithMeRemainingTimeString NOTIFY sharedWithMeRemainingTimeStringChanged) public: enum Roles { @@ -126,6 +131,12 @@ class ShareModel : public QAbstractListModel [[nodiscard]] QVariantList sharees() const; + [[nodiscard]] bool displayShareOwner() const; + [[nodiscard]] QString shareOwnerDisplayName() const; + [[nodiscard]] QString shareOwnerAvatar() const; + [[nodiscard]] bool sharedWithMeExpires() const; + [[nodiscard]] QString sharedWithMeRemainingTimeString() const; + [[nodiscard]] Q_INVOKABLE static QString generatePassword(); signals: @@ -143,6 +154,11 @@ class ShareModel : public QAbstractListModel void shareesChanged(); void internalLinkReady(); void serverAllowsResharingChanged(); + void displayShareOwnerChanged(); + void shareOwnerDisplayNameChanged(); + void shareOwnerAvatarChanged(); + void sharedWithMeExpiresChanged(); + void sharedWithMeRemainingTimeStringChanged(); void serverError(const int code, const QString &message) const; void passwordSetError(const QString &shareId, const int code, const QString &message); @@ -246,6 +262,11 @@ private slots: SyncJournalFileLockInfo _filelockState; QString _privateLinkUrl; QByteArray _fileRemoteId; + bool _displayShareOwner = false; + QString _shareOwnerDisplayName; + QString _shareOwnerAvatar; + bool _sharedWithMeExpires = false; + QString _sharedWithMeRemainingTimeString; QSharedPointer _manager; diff --git a/src/gui/ocssharejob.cpp b/src/gui/ocssharejob.cpp index 2eee30c1ab093..1bc81dfd8d732 100644 --- a/src/gui/ocssharejob.cpp +++ b/src/gui/ocssharejob.cpp @@ -34,6 +34,7 @@ void OcsShareJob::getShares(const QString &path, const QMap &p addParam(QString::fromLatin1("path"), path); addParam(QString::fromLatin1("reshares"), QString("true")); + addParam(QString::fromLatin1("shared_with_me"), QString("true")); for (auto it = std::cbegin(params); it != std::cend(params); ++it) { addParam(it.key(), it.value()); diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp index c39b1406e57c3..30b3d699f81ee 100644 --- a/src/gui/tray/usermodel.cpp +++ b/src/gui/tray/usermodel.cpp @@ -1618,35 +1618,82 @@ int UserModel::findUserIdForAccount(AccountState *account) const } /*-------------------------------------------------------------------------------------*/ -ImageProvider::ImageProvider() - : QQuickImageProvider(QQuickImageProvider::Image) -{ -} +class ImageResponse : public QQuickImageResponse +{ +public: + ImageResponse(const QString &id, const QSize &requestedSize, QThreadPool *pool) + { + Q_UNUSED(pool) + + const auto makeIcon = [](const QString &path) { + QImage image(128, 128, QImage::Format_ARGB32); + image.fill(Qt::GlobalColor::transparent); + QPainter painter(&image); + QSvgRenderer renderer(path); + renderer.render(&painter); + return image; + }; + + if (id == QLatin1String("fallbackWhite")) { + handleDone(makeIcon(QStringLiteral(":/client/theme/white/user.svg"))); + return; + } else if (id == QLatin1String("fallbackBlack")) { + handleDone(makeIcon(QStringLiteral(":/client/theme/black/user.svg"))); + return; + } -QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) -{ - Q_UNUSED(size) - Q_UNUSED(requestedSize) + if (id.startsWith("user-id=")) { + // Format is "image://avatars/user-id=avatar-requested-user/local-user-id:0" + const auto userIdsString = id.split('='); + const auto userIds = userIdsString.last().split("/local-account:"); + const auto avatarUserId = userIds.first(); + const auto accountString = userIds.last(); + const auto accountState = AccountManager::instance()->account(accountString); + Q_ASSERT(accountState); + Q_ASSERT(accountState->account()); + if (!accountState || !accountState->account()) { + qCWarning(lcActivity) << "Invalid account:" << accountString; + return; + } - const auto makeIcon = [](const QString &path) { - QImage image(128, 128, QImage::Format_ARGB32); - image.fill(Qt::GlobalColor::transparent); - QPainter painter(&image); - QSvgRenderer renderer(path); - renderer.render(&painter); - return image; - }; + const auto account = accountState->account(); + const auto qnam = account->networkAccessManager(); + + QMetaObject::invokeMethod(qnam, [this, requestedSize, avatarUserId, account]() { + const auto avatarSize = requestedSize.width() > 0 ? requestedSize.width() : 64; + const auto avatarJob = new AvatarJob(account, avatarUserId, avatarSize); + connect(avatarJob, &AvatarJob::avatarPixmap, this, [&](const QImage &avatarImg) { + QMetaObject::invokeMethod(this, [this, avatarImg] { + handleDone(AvatarJob::makeCircularAvatar(avatarImg)); + }); + }); + avatarJob->start(); + }); + return; + } + + handleDone(UserModel::instance()->avatarById(id.toInt())); + } - if (id == QLatin1String("fallbackWhite")) { - return makeIcon(QStringLiteral(":/client/theme/white/user.svg")); + void handleDone(const QImage &image) + { + _image = image; + emit finished(); } - if (id == QLatin1String("fallbackBlack")) { - return makeIcon(QStringLiteral(":/client/theme/black/user.svg")); + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(_image); } - const int uid = id.toInt(); - return UserModel::instance()->avatarById(uid); +private: + QImage _image; +}; + +QQuickImageResponse *ImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) +{ + const auto response = new class ImageResponse(id, requestedSize, &_pool); + return response; } /*-------------------------------------------------------------------------------------*/ @@ -1725,3 +1772,4 @@ QHash UserAppsModel::roleNames() const return roles; } } + diff --git a/src/gui/tray/usermodel.h b/src/gui/tray/usermodel.h index 466cd6ab81976..3f2170ab0748e 100644 --- a/src/gui/tray/usermodel.h +++ b/src/gui/tray/usermodel.h @@ -276,11 +276,16 @@ public slots: void buildUserList(); }; -class ImageProvider : public QQuickImageProvider +class ImageProvider : public QQuickAsyncImageProvider { + Q_OBJECT + public: - ImageProvider(); - QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + ImageProvider() = default; + QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; + +private: + QThreadPool _pool; }; class UserAppsModel : public QAbstractListModel