Allow loading image only after explicit interactions

This commit is contained in:
Nicolas Werner 2024-08-21 00:14:34 +02:00
parent c1038a3e4a
commit d1eb351975
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
10 changed files with 132 additions and 8 deletions

View file

@ -20,6 +20,8 @@ AbstractButton {
required property int containerHeight required property int containerHeight
property double divisor: EventDelegateChooser.isReply ? 10 : 4 property double divisor: EventDelegateChooser.isReply ? 10 : 4
property bool showImage: room.showImage()
EventDelegateChooser.keepAspectRatio: true EventDelegateChooser.keepAspectRatio: true
EventDelegateChooser.maxWidth: originalWidth EventDelegateChooser.maxWidth: originalWidth
EventDelegateChooser.maxHeight: containerHeight / divisor EventDelegateChooser.maxHeight: containerHeight / divisor
@ -113,7 +115,7 @@ AbstractButton {
visible: !mxcimage.loaded visible: !mxcimage.loaded
anchors.fill: parent anchors.fill: parent
source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : "" source: (url != "" && showImage) ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
horizontalAlignment: Image.AlignLeft horizontalAlignment: Image.AlignLeft
@ -130,7 +132,7 @@ AbstractButton {
visible: loaded visible: loaded
roomm: room roomm: room
play: !Settings.animateImagesOnHover || parent.hovered play: !Settings.animateImagesOnHover || parent.hovered
eventId: parent.eventId eventId: showImage ? parent.eventId : ""
anchors.fill: parent anchors.fill: parent
} }
@ -147,7 +149,9 @@ AbstractButton {
anchors.fill: parent anchors.fill: parent
} }
onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight); onClicked: {
Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight);
}
Item { Item {
id: overlay id: overlay
@ -180,4 +184,13 @@ AbstractButton {
} }
Button {
anchors.centerIn: parent
visible: !showImage && !parent.EventDelegateChooser.isReply
enabled: visible
text: qsTr("Show")
onClicked: {
showImage = true;
}
}
} }

View file

@ -133,6 +133,12 @@ UserSettings::load(std::optional<QString> profile)
presenceValue = 0; presenceValue = 0;
presence_ = static_cast<Presence>(presenceValue); presence_ = static_cast<Presence>(presenceValue);
auto tempShowImage = settings.value(prefix + "user/show_images", "").toString().toStdString();
auto showImageValue = QMetaEnum::fromType<ShowImage>().keyToValue(tempShowImage.c_str());
if (showImageValue < 0)
showImageValue = 0;
showImage_ = static_cast<ShowImage>(showImageValue);
collapsedSpaces_.clear(); collapsedSpaces_.clear();
auto tempSpaces = settings.value(prefix + "user/collapsed_spaces", QList<QVariant>{}).toList(); auto tempSpaces = settings.value(prefix + "user/collapsed_spaces", QList<QVariant>{}).toList();
for (const auto &e : std::as_const(tempSpaces)) for (const auto &e : std::as_const(tempSpaces))
@ -612,6 +618,16 @@ UserSettings::setPresence(Presence state)
save(); save();
} }
void
UserSettings::setShowImage(ShowImage state)
{
if (state == showImage_)
return;
showImage_ = state;
emit showImageChanged(state);
save();
}
void void
UserSettings::setTheme(QString theme) UserSettings::setTheme(QString theme)
{ {
@ -955,6 +971,9 @@ UserSettings::save()
settings.setValue( settings.setValue(
prefix + "user/presence", prefix + "user/presence",
QString::fromUtf8(QMetaEnum::fromType<Presence>().valueToKey(static_cast<int>(presence_)))); QString::fromUtf8(QMetaEnum::fromType<Presence>().valueToKey(static_cast<int>(presence_))));
settings.setValue(
prefix + "user/show_images",
QString::fromUtf8(QMetaEnum::fromType<ShowImage>().valueToKey(static_cast<int>(showImage_))));
QVariantList v; QVariantList v;
v.reserve(collapsedSpaces_.size()); v.reserve(collapsedSpaces_.size());
@ -1024,6 +1043,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr("Enable small Avatars"); return tr("Enable small Avatars");
case AnimateImagesOnHover: case AnimateImagesOnHover:
return tr("Play animated images only on hover"); return tr("Play animated images only on hover");
case ShowImage:
return tr("Show images automatically");
case TypingNotifications: case TypingNotifications:
return tr("Typing notifications"); return tr("Typing notifications");
case SortByImportance: case SortByImportance:
@ -1182,6 +1203,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return i->smallAvatars(); return i->smallAvatars();
case AnimateImagesOnHover: case AnimateImagesOnHover:
return i->animateImagesOnHover(); return i->animateImagesOnHover();
case ShowImage:
return static_cast<int>(i->showImage());
case TypingNotifications: case TypingNotifications:
return i->typingNotifications(); return i->typingNotifications();
case SortByImportance: case SortByImportance:
@ -1349,6 +1372,10 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr("Avatars are resized to fit above the message."); return tr("Avatars are resized to fit above the message.");
case AnimateImagesOnHover: case AnimateImagesOnHover:
return tr("Plays media like GIFs or WEBPs only when explicitly hovering over them."); return tr("Plays media like GIFs or WEBPs only when explicitly hovering over them.");
case ShowImage:
return tr("If images should be automatically displayed. You can select between always "
"showing images by default, only show them by default in private rooms or "
"always require interaction to show images.");
case TypingNotifications: case TypingNotifications:
return tr( return tr(
"Show who is typing in a room.\nThis will also enable or disable sending typing " "Show who is typing in a room.\nThis will also enable or disable sending typing "
@ -1504,6 +1531,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
case CameraResolution: case CameraResolution:
case CameraFrameRate: case CameraFrameRate:
case Ringtone: case Ringtone:
case ShowImage:
return Options; return Options;
case TimelineMaxWidth: case TimelineMaxWidth:
case PrivacyScreenTimeout: case PrivacyScreenTimeout:
@ -1631,6 +1659,12 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
QStringLiteral("Dark"), QStringLiteral("Dark"),
QStringLiteral("System"), QStringLiteral("System"),
}; };
case ShowImage:
return QStringList{
tr("Always"),
tr("Only in private rooms"),
tr("Never"),
};
case Microphone: case Microphone:
return vecToList(CallDevices::instance().names(false, i->microphone().toStdString())); return vecToList(CallDevices::instance().names(false, i->microphone().toStdString()));
case Camera: case Camera:
@ -1710,6 +1744,16 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
} else } else
return false; return false;
} }
case ShowImage: {
auto showImageValue = value.toInt();
if (showImageValue < 0 ||
QMetaEnum::fromType<UserSettings::ShowImage>().keyCount() <= showImageValue)
return false;
i->setShowImage(static_cast<UserSettings::ShowImage>(showImageValue));
return true;
}
case MessageHoverHighlight: { case MessageHoverHighlight: {
if (value.userType() == QMetaType::Bool) { if (value.userType() == QMetaType::Bool) {
i->setMessageHoverHighlight(value.toBool()); i->setMessageHoverHighlight(value.toBool());
@ -2241,6 +2285,9 @@ UserSettingsModel::UserSettingsModel(QObject *p)
connect(s.get(), &UserSettings::animateImagesOnHoverChanged, this, [this]() { connect(s.get(), &UserSettings::animateImagesOnHoverChanged, this, [this]() {
emit dataChanged(index(AnimateImagesOnHover), index(AnimateImagesOnHover), {Value}); emit dataChanged(index(AnimateImagesOnHover), index(AnimateImagesOnHover), {Value});
}); });
connect(s.get(), &UserSettings::showImageChanged, this, [this]() {
emit dataChanged(index(ShowImage), index(ShowImage), {Value});
});
connect(s.get(), &UserSettings::typingNotificationsChanged, this, [this]() { connect(s.get(), &UserSettings::typingNotificationsChanged, this, [this]() {
emit dataChanged(index(TypingNotifications), index(TypingNotifications), {Value}); emit dataChanged(index(TypingNotifications), index(TypingNotifications), {Value});
}); });

View file

@ -75,6 +75,7 @@ class UserSettings final : public QObject
Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged) Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged)
Q_PROPERTY(QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged) Q_PROPERTY(QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged)
Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged) Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
Q_PROPERTY(ShowImage showImage READ showImage WRITE setShowImage NOTIFY showImageChanged)
Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged) Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged)
Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged) Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged)
Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged) Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged)
@ -157,6 +158,14 @@ public:
}; };
Q_ENUM(Presence) Q_ENUM(Presence)
enum class ShowImage
{
Always,
OnlyPrivate,
Never,
};
Q_ENUM(ShowImage)
void save(); void save();
void load(std::optional<QString> profile); void load(std::optional<QString> profile);
void applyTheme(); void applyTheme();
@ -196,6 +205,7 @@ public:
void setPrivacyScreen(bool state); void setPrivacyScreen(bool state);
void setPrivacyScreenTimeout(int state); void setPrivacyScreenTimeout(int state);
void setPresence(Presence state); void setPresence(Presence state);
void setShowImage(ShowImage state);
void setRingtone(QString ringtone); void setRingtone(QString ringtone);
void setMicrophone(QString microphone); void setMicrophone(QString microphone);
void setCamera(QString camera); void setCamera(QString camera);
@ -273,6 +283,7 @@ public:
return emojiFont_; return emojiFont_;
} }
Presence presence() const { return presence_; } Presence presence() const { return presence_; }
ShowImage showImage() const { return showImage_; }
QString ringtone() const { return ringtone_; } QString ringtone() const { return ringtone_; }
QString microphone() const { return microphone_; } QString microphone() const { return microphone_; }
QString camera() const { return camera_; } QString camera() const { return camera_; }
@ -343,6 +354,7 @@ signals:
void fontChanged(QString state); void fontChanged(QString state);
void emojiFontChanged(QString state); void emojiFontChanged(QString state);
void presenceChanged(Presence state); void presenceChanged(Presence state);
void showImageChanged(ShowImage state);
void ringtoneChanged(QString ringtone); void ringtoneChanged(QString ringtone);
void microphoneChanged(QString microphone); void microphoneChanged(QString microphone);
void cameraChanged(QString camera); void cameraChanged(QString camera);
@ -418,6 +430,7 @@ private:
QString font_; QString font_;
QString emojiFont_; QString emojiFont_;
Presence presence_; Presence presence_;
ShowImage showImage_;
QString ringtone_; QString ringtone_;
QString microphone_; QString microphone_;
QString camera_; QString camera_;
@ -490,6 +503,7 @@ class UserSettingsModel : public QAbstractListModel
TimelineSection, TimelineSection,
TimelineMaxWidth, TimelineMaxWidth,
EnlargeEmojiOnlyMessages, EnlargeEmojiOnlyMessages,
ShowImage,
OpenImageExternal, OpenImageExternal,
OpenVideoExternal, OpenVideoExternal,
ButtonsInTimeline, ButtonsInTimeline,

View file

@ -5,9 +5,33 @@
#include "notifications/Manager.h" #include "notifications/Manager.h"
#include "Cache.h" #include "Cache.h"
#include "Cache_p.h"
#include "EventAccessors.h" #include "EventAccessors.h"
#include "UserSettingsPage.h"
#include "Utils.h" #include "Utils.h"
bool
NotificationsManager::allowShowingImages(const mtx::responses::Notification &notification)
{
auto show = UserSettings::instance()->showImage();
switch (show) {
case UserSettings::ShowImage::Always:
return true;
case UserSettings::ShowImage::OnlyPrivate: {
auto accessRules = cache::client()
->getStateEvent<mtx::events::state::JoinRules>(notification.room_id)
.value_or(mtx::events::StateEvent<mtx::events::state::JoinRules>{})
.content;
return accessRules.join_rule != mtx::events::state::JoinRule::Public;
}
case UserSettings::ShowImage::Never:
default:
return false;
}
}
QString QString
NotificationsManager::getMessageTemplate(const mtx::responses::Notification &notification) NotificationsManager::getMessageTemplate(const mtx::responses::Notification &notification)
{ {

View file

@ -66,7 +66,6 @@ private:
const QString &text, const QString &text,
const QImage &icon); const QImage &icon);
void closeNotification(uint id); void closeNotification(uint id);
const bool hasMarkup_; const bool hasMarkup_;
const bool hasImages_; const bool hasImages_;
#endif #endif
@ -111,6 +110,7 @@ private slots:
private: private:
QString getMessageTemplate(const mtx::responses::Notification &notification); QString getMessageTemplate(const mtx::responses::Notification &notification);
bool allowShowingImages(const mtx::responses::Notification &notification);
// notification ID to (room ID, event ID) // notification ID to (room ID, event ID)
// Only populated on Linux atm // Only populated on Linux atm

View file

@ -114,7 +114,7 @@ NotificationsManager::postNotification(const mtx::responses::Notification &notif
} }
if (hasMarkup_) { if (hasMarkup_) {
if (hasImages_ && if (hasImages_ && allowShowingImages(notification) &&
(mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image || (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image ||
mtx::accessors::event_type(notification.event) == mtx::events::EventType::Sticker)) { mtx::accessors::event_type(notification.event) == mtx::events::EventType::Sticker)) {
MxcImageProvider::download( MxcImageProvider::download(

View file

@ -93,7 +93,9 @@ NotificationsManager::postNotification(const mtx::responses::Notification &notif
} else { } else {
const QString messageInfo = const QString messageInfo =
(isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender); (isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender);
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) if (allowShowingImages(notification) &&
(mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image ||
mtx::accessors::event_type(notification.event) == mtx::events::EventType::Sticker))
MxcImageProvider::download( MxcImageProvider::download(
QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"), QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"),
QSize(200, 80), QSize(200, 80),

View file

@ -83,8 +83,9 @@ NotificationsManager::postNotification(const mtx::responses::Notification &notif
if (!icon.save(iconPath)) if (!icon.save(iconPath))
iconPath.clear(); iconPath.clear();
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image || if (allowShowingImages(notification) &&
mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) { (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image ||
mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image)) {
MxcImageProvider::download( MxcImageProvider::download(
QString::fromStdString(mtx::accessors::url(notification.event)) QString::fromStdString(mtx::accessors::url(notification.event))
.remove(QStringLiteral("mxc://")), .remove(QStringLiteral("mxc://")),

View file

@ -3387,4 +3387,26 @@ TimelineModel::parentSpace()
return parentSummary.get(); return parentSummary.get();
} }
bool
TimelineModel::showImage() const
{
auto show = UserSettings::instance()->showImage();
switch (show) {
case UserSettings::ShowImage::Always:
return true;
case UserSettings::ShowImage::OnlyPrivate: {
auto accessRules = cache::client()
->getStateEvent<mtx::events::state::JoinRules>(room_id_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::JoinRules>{})
.content;
return accessRules.join_rule != mtx::events::state::JoinRule::Public;
}
case UserSettings::ShowImage::Never:
default:
return false;
}
}
#include "moc_TimelineModel.cpp" #include "moc_TimelineModel.cpp"

View file

@ -323,6 +323,7 @@ public:
const mtx::events::StateEvent<mtx::events::msc2545::ImagePack> &event) const; const mtx::events::StateEvent<mtx::events::msc2545::ImagePack> &event) const;
Q_INVOKABLE QString formatPolicyRule(const QString &id) const; Q_INVOKABLE QString formatPolicyRule(const QString &id) const;
Q_INVOKABLE QVariantMap formatRedactedEvent(const QString &id); Q_INVOKABLE QVariantMap formatRedactedEvent(const QString &id);
Q_INVOKABLE bool showImage() const;
Q_INVOKABLE void viewRawMessage(const QString &id); Q_INVOKABLE void viewRawMessage(const QString &id);
Q_INVOKABLE void forwardMessage(const QString &eventId, QString roomId); Q_INVOKABLE void forwardMessage(const QString &eventId, QString roomId);