Fix dark mode stuff relating to unified search, fix macOS auto dark/light theme switching not always working, fix Windows detection of dark/light theme switching

Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
This commit is contained in:
Claudio Cambra 2022-03-21 17:34:21 +01:00 committed by Matthieu Gallien (Rebase PR Action)
parent 1d66385119
commit 3d086ae305
20 changed files with 207 additions and 153 deletions

View File

@ -105,6 +105,31 @@ namespace {
// ----------------------------------------------------------------------------------
#ifdef Q_OS_WIN
class WindowsNativeEventFilter : public QAbstractNativeEventFilter {
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) {
const auto msg = static_cast<MSG *>(message);
if(msg->message == WM_SYSCOLORCHANGE || msg->message == WM_SETTINGCHANGE) {
if(!_guiAppInstance) {
const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance());
if(ptr) {
_guiAppInstance.reset(ptr);
}
}
if(_guiAppInstance) {
emit _guiAppInstance->paletteChanged(_guiAppInstance->palette());
}
}
return false;
}
private:
QScopedPointer<QGuiApplication> _guiAppInstance;
};
#endif
bool Application::configVersionMigration()
{
QStringList deleteKeys, ignoreKeys;
@ -192,6 +217,9 @@ Application::Application(int &argc, char **argv)
// Ensure OpenSSL config file is only loaded from app directory
QString opensslConf = QCoreApplication::applicationDirPath() + QString("/openssl.cnf");
qputenv("OPENSSL_CONF", opensslConf.toLocal8Bit());
// Set up event listener for Windows theme changing
installNativeEventFilter(new WindowsNativeEventFilter());
#endif
// TODO: Can't set this without breaking current config paths

View File

@ -130,12 +130,6 @@ Systray::Systray()
});
#endif
const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance());
if(ptr) {
_guiAppInstance.reset(ptr);
connect(ptr, &QGuiApplication::paletteChanged, this, &Systray::darkModeChanged);
}
connect(UserModel::instance(), &UserModel::newUserSelected,
this, &Systray::slotNewUserSelected);
connect(UserModel::instance(), &UserModel::addAccount,
@ -231,24 +225,6 @@ bool Systray::useNormalWindow() const
return cfg.showMainDialogAsNormalWindow();
}
bool Systray::darkMode()
{
#if defined(Q_OS_MACOS)
return osXInDarkMode();
// Windows: Check registry for dark mode
#elif defined(Q_OS_WIN)
const auto darkModeSubkey = QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
if (!Utility::registryKeyExists(HKEY_CURRENT_USER, darkModeSubkey)) {
return false;
}
const auto darkMode = !Utility::registryGetKeyValue(HKEY_CURRENT_USER, darkModeSubkey, QStringLiteral("AppsUseLightTheme")).toBool();
return darkMode;
// Probably Linux
#else
return Theme::isDarkColor(QGuiApplication::palette().window().color());
#endif
}
Q_INVOKABLE void Systray::setOpened()
{
_isOpen = true;

View File

@ -40,7 +40,6 @@ public:
};
#ifdef Q_OS_OSX
bool osXInDarkMode();
bool canOsXSendUserNotification();
void sendOsXUserNotification(const QString &title, const QString &message);
void setTrayWindowLevelAndVisibleOnAllSpaces(QWindow *window);
@ -58,7 +57,6 @@ class Systray
Q_PROPERTY(QString windowTitle READ windowTitle CONSTANT)
Q_PROPERTY(bool useNormalWindow READ useNormalWindow CONSTANT)
Q_PROPERTY(bool darkMode READ darkMode NOTIFY darkModeChanged)
public:
static Systray *instance();
@ -74,7 +72,6 @@ public:
bool isOpen();
QString windowTitle() const;
bool useNormalWindow() const;
bool darkMode();
Q_INVOKABLE void pauseResumeSync();
Q_INVOKABLE bool syncIsPaused();
@ -97,8 +94,6 @@ signals:
void showFileActivityDialog(const QString &objectName, const int objectId);
void sendChatMessage(const QString &token, const QString &message, const QString &replyTo);
void darkModeChanged();
public slots:
void slotNewUserSelected();

View File

@ -77,11 +77,7 @@ RowLayout {
anchors.bottom: if(model.thumbnail !== undefined) parent.bottom
cache: true
property string sourceUrl: Systray.darkMode ?
model.icon.replace("__COLOR__", "white").replace("__WHITE_GOES_HERE__", "-white") :
model.icon.replace("__COLOR__", "black").replace("__WHITE_GOES_HERE__", "")
source: sourceUrl
source: Theme.darkMode ? model.darkIcon : model.lightIcon
sourceSize.height: 64
sourceSize.width: 64
}
@ -146,9 +142,9 @@ RowLayout {
id: talkReplyMessage
anchors.fill: parent
}
}
}
}
}
Button {
id: dismissActionButton
@ -184,7 +180,7 @@ RowLayout {
contentItem: Image {
anchors.fill: parent
source: parent.hovered ? Systray.darkMode ?
source: parent.hovered ? Theme.darkMode ?
"image://svgimage-custom-color/clear.svg/white" : "image://svgimage-custom-color/clear.svg/black" :
"image://svgimage-custom-color/clear.svg/grey"
sourceSize.width: 24

View File

@ -2,6 +2,7 @@ import QtQml 2.15
import QtQuick 2.15
import QtQuick.Controls 2.3
import Style 1.0
import com.nextcloud.desktopclient 1.0
MouseArea {
id: unifiedSearchResultMouseArea
@ -60,8 +61,8 @@ MouseArea {
height: unifiedSearchResultMouseArea.height
title: model.resultTitle
subline: model.subline
icons: model.icons
iconPlaceholder: model.imagePlaceholder
icons: Theme.darkMode ? model.darkIcons : model.lightIcons
iconPlaceholder: Theme.darkMode ? model.darkImagePlaceholder : model.lightImagePlaceholder
isRounded: model.isRounded
textLeftMargin: unifiedSearchResultMouseArea.textLeftMargin
textRightMargin: unifiedSearchResultMouseArea.textRightMargin

View File

@ -61,7 +61,7 @@ MenuItem {
Layout.leftMargin: 7
verticalAlignment: Qt.AlignCenter
cache: false
source: model.avatar != "" ? model.avatar : Systray.darkMode ? "image://avatars/fallbackWhite" : "image://avatars/fallbackBlack"
source: model.avatar != "" ? model.avatar : Theme.darkMode ? "image://avatars/fallbackWhite" : "image://avatars/fallbackBlack"
Layout.preferredHeight: Style.accountAvatarSize
Layout.preferredWidth: Style.accountAvatarSize
Rectangle {

View File

@ -239,7 +239,7 @@ Window {
Image {
Layout.leftMargin: 12
verticalAlignment: Qt.AlignCenter
source: Systray.darkMode ? "qrc:///client/theme/white/add.svg" : "qrc:///client/theme/black/add.svg"
source: Theme.darkMode ? "qrc:///client/theme/white/add.svg" : "qrc:///client/theme/black/add.svg"
sourceSize.width: Style.headerButtonIconSize
sourceSize.height: Style.headerButtonIconSize
}

View File

@ -63,9 +63,25 @@ OCC::Activity Activity::fromActivityJson(const QJsonObject json, const AccountPt
activity._file = json.value(QStringLiteral("object_name")).toString();
activity._link = QUrl(json.value(QStringLiteral("link")).toString());
activity._dateTime = QDateTime::fromString(json.value(QStringLiteral("datetime")).toString(), Qt::ISODate);
activity._icon = json.value(QStringLiteral("icon")).toString();
activity._darkIcon = json.value(QStringLiteral("icon")).toString(); // We have both dark and light for theming purposes
activity._lightIcon = json.value(QStringLiteral("icon")).toString(); // Some icons get changed in the ActivityListModel
activity._isCurrentUserFileActivity = activity._objectType == QStringLiteral("files") && activityUser == account->davUser();
const auto darkIconPath = QStringLiteral("qrc://:/client/theme/white/");
const auto lightIconPath = QStringLiteral("qrc://:/client/theme/black/");
if(activity._darkIcon.contains("change.svg")) {
activity._darkIcon = darkIconPath + QStringLiteral("change.svg");
activity._lightIcon = lightIconPath + QStringLiteral("change.svg");
} else if(activity._darkIcon.contains("calendar.svg")) {
activity._darkIcon = darkIconPath + QStringLiteral("calendar.svg");
activity._lightIcon = lightIconPath + QStringLiteral("calendar.svg");
} else if(activity._darkIcon.contains("personal.svg")) {
activity._darkIcon = darkIconPath + QStringLiteral("user.svg");
activity._lightIcon = lightIconPath + QStringLiteral("user.svg");
} else if(activity._darkIcon.contains("core/img/actions")) {
activity._darkIcon.insert(activity._darkIcon.indexOf(".svg"), "-white");
}
auto richSubjectData = json.value(QStringLiteral("subject_rich")).toArray();
if(richSubjectData.size() > 1) {
@ -128,15 +144,23 @@ OCC::Activity Activity::fromActivityJson(const QJsonObject json, const AccountPt
}
if(!previewsData.isEmpty()) {
if(activity._icon.contains(QStringLiteral("add-color.svg"))) {
activity._icon = "qrc:///client/theme/colored/add-bordered.svg";
} else if(activity._icon.contains(QStringLiteral("delete-color.svg"))) {
activity._icon = "qrc:///client/theme/colored/delete-bordered.svg";
} else if(activity._icon.contains(QStringLiteral("change.svg"))) {
activity._icon = "qrc:///client/theme/colored/change-bordered.svg";
if(activity._darkIcon.contains(QStringLiteral("add-color.svg"))) {
activity._darkIcon = "qrc:///client/theme/colored/add-bordered.svg";
activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg";
} else if(activity._darkIcon.contains(QStringLiteral("delete-color.svg"))) {
activity._darkIcon = "qrc:///client/theme/colored/delete-bordered.svg";
activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg";
} else if(activity._darkIcon.contains(QStringLiteral("change.svg"))) {
activity._darkIcon = "qrc:///client/theme/colored/change-bordered.svg";
activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg";
}
}
auto actions = json.value("actions").toArray();
foreach (auto action, actions) {
activity._links.append(ActivityLink::createFomJsonObject(action.toObject()));
}
return activity;
}

View File

@ -133,7 +133,8 @@ public:
QDateTime _dateTime;
qint64 _expireAtMsecs = -1;
QString _accName;
QString _icon;
QString _darkIcon;
QString _lightIcon;
bool _isCurrentUserFileActivity = false;
QVector<PreviewData> _previews;

View File

@ -62,7 +62,8 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
roles[LinkRole] = "link";
roles[MessageRole] = "message";
roles[ActionRole] = "type";
roles[ActionIconRole] = "icon";
roles[DarkIconRole] = "darkIcon";
roles[LightIconRole] = "lightIcon";
roles[ActionTextRole] = "subject";
roles[ActionsLinksRole] = "links";
roles[ActionsLinksContextMenuRole] = "linksContextMenu";
@ -192,31 +193,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
});
};
switch (role) {
case DisplayPathRole:
return getDisplayPath();
case PathRole:
return QFileInfo(getFilePath()).path();
case DisplayLocationRole:
return displayLocation();
case ActionsLinksRole: {
QList<QVariant> customList;
foreach (ActivityLink activityLink, a._links) {
customList << QVariant::fromValue(activityLink);
}
return customList;
}
case ActionsLinksContextMenuRole: {
return ActivityListModel::convertLinksToMenuEntries(a);
}
case ActionsLinksForActionButtonsRole: {
return ActivityListModel::convertLinksToActionButtons(a);
}
case ActionIconRole: {
auto colorIconPath = QStringLiteral("qrc:///client/theme/__COLOR__/"); // We will replace __COLOR__ in QML
const auto generateIconPath = [&]() {
auto colorIconPath = role == DarkIconRole ? QStringLiteral("qrc:///client/theme/white/") : QStringLiteral("qrc:///client/theme/black/");
if (a._type == Activity::NotificationType) {
colorIconPath.append("bell.svg");
return colorIconPath;
@ -255,14 +233,40 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
}
} else {
// We have an activity
if (a._icon.isEmpty()) {
if (a._darkIcon.isEmpty()) {
colorIconPath.append("activity.svg");
return colorIconPath;
}
return a._icon;
return role == DarkIconRole ? a._darkIcon : a._lightIcon;
}
};
switch (role) {
case DisplayPathRole:
return getDisplayPath();
case PathRole:
return QFileInfo(getFilePath()).path();
case DisplayLocationRole:
return displayLocation();
case ActionsLinksRole: {
QList<QVariant> customList;
foreach (ActivityLink activityLink, a._links) {
customList << QVariant::fromValue(activityLink);
}
return customList;
}
case ActionsLinksContextMenuRole: {
return ActivityListModel::convertLinksToMenuEntries(a);
}
case ActionsLinksForActionButtonsRole: {
return ActivityListModel::convertLinksToActionButtons(a);
}
case DarkIconRole:
case LightIconRole:
return generateIconPath();
case ObjectTypeRole:
return a._objectType;
case ObjectIdRole:
@ -400,20 +404,6 @@ void ActivityListModel::ingestActivities(const QJsonArray &activities)
auto a = Activity::fromActivityJson(json, _accountState->account());
auto colorIconPath = QStringLiteral("qrc:///client/theme/__COLOR__/");
if(a._icon.contains("change.svg")) {
colorIconPath.append("change.svg");
a._icon = colorIconPath;
} else if(a._icon.contains("calendar.svg")) {
colorIconPath.append("calendar.svg");
a._icon = colorIconPath;
} else if(a._icon.contains("personal.svg")) {
colorIconPath.append("user.svg");
a._icon = colorIconPath;
} else if(a._icon.contains("core/img/actions")) {
a._icon.insert(a._icon.indexOf(".svg"), "__WHITE_GOES_HERE__");
}
list.append(a);
_currentItem = list.last()._id;

View File

@ -45,7 +45,8 @@ class ActivityListModel : public QAbstractListModel
Q_PROPERTY(AccountState *accountState READ accountState CONSTANT)
public:
enum DataRole {
ActionIconRole = Qt::UserRole + 1,
DarkIconRole = Qt::UserRole + 1,
LightIconRole,
AccountRole,
ObjectTypeRole,
ObjectIdRole,

View File

@ -92,15 +92,11 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
ActivityList list;
foreach (auto element, notifies) {
Activity a;
auto json = element.toObject();
auto a = Activity::fromActivityJson(json, ai->account());
a._type = Activity::NotificationType;
a._accName = ai->account()->displayName();
a._id = json.value("notification_id").toInt();
//need to know, specially for remote_share
a._objectType = json.value("object_type").toString();
// 2 cases to consider:
// - server == 24 & has Talk: notification type chat/call contains conversationToken/messageId in object_type
// - server < 24 & has Talk: notification type chat/call contains _only_ the conversationToken in object_type
@ -117,10 +113,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
a._status = 0;
a._subject = json.value("subject").toString();
a._message = json.value("message").toString();
a._icon = json.value("icon").toString();
QUrl link(json.value("link").toString());
if (!link.isEmpty()) {
if (link.host().isEmpty()) {
@ -132,12 +124,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
}
}
a._link = link;
a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate);
auto actions = json.value("actions").toArray();
foreach (auto action, actions) {
a._links.append(ActivityLink::createFomJsonObject(action.toObject()));
}
// Add another action to dismiss notification on server
// https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user

View File

@ -42,7 +42,8 @@ struct UnifiedSearchResult
bool _isRounded = false;
qint32 _order = std::numeric_limits<qint32>::max();
QUrl _resourceUrl;
QString _icons;
QString _darkIcons;
QString _lightIcons;
Type _type = Type::Default;
};
}

View File

@ -19,7 +19,6 @@
#include "guiutility.h"
#include "folderman.h"
#include "networkjobs.h"
#include "systray.h"
#include <algorithm>
@ -27,47 +26,49 @@
#include <QDesktopServices>
namespace {
QString imagePlaceholderUrlForProviderId(const QString &providerId)
QString imagePlaceholderUrlForProviderId(const QString &providerId, const bool darkMode)
{
const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/");
if (providerId.contains(QStringLiteral("message"), Qt::CaseInsensitive)
|| providerId.contains(QStringLiteral("talk"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/wizard-talk.svg") : QStringLiteral("qrc:///client/theme/black/wizard-talk.svg");
return colorIconPath % QStringLiteral("wizard-talk.svg");
} else if (providerId.contains(QStringLiteral("file"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/edit.svg") : QStringLiteral("qrc:///client/theme/black/edit.svg");
return colorIconPath % QStringLiteral("edit.svg");
} else if (providerId.contains(QStringLiteral("deck"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/deck.svg") : QStringLiteral("qrc:///client/theme/black/deck.svg");
return colorIconPath % QStringLiteral("deck.svg");
} else if (providerId.contains(QStringLiteral("calendar"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/calendar.svg") : QStringLiteral("qrc:///client/theme/black/calendar.svg");
return colorIconPath % QStringLiteral("calendar.svg");
} else if (providerId.contains(QStringLiteral("mail"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/email.svg") : QStringLiteral("qrc:///client/theme/black/email.svg");
return colorIconPath % QStringLiteral("email.svg");
} else if (providerId.contains(QStringLiteral("comment"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/comment.svg") : QStringLiteral("qrc:///client/theme/black/comment.svg");
return colorIconPath % QStringLiteral("comment.svg");
}
return QStringLiteral("qrc:///client/theme/change.svg");
return colorIconPath % QStringLiteral("change.svg");
}
QString localIconPathFromIconPrefix(const QString &iconNameWithPrefix)
QString localIconPathFromIconPrefix(const QString &iconNameWithPrefix, const bool darkMode)
{
const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/");
if (iconNameWithPrefix.contains(QStringLiteral("message"), Qt::CaseInsensitive)
|| iconNameWithPrefix.contains(QStringLiteral("talk"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/wizard-talk.svg") : QStringLiteral(":/client/theme/black/wizard-talk.svg");
return colorIconPath % QStringLiteral("wizard-talk.svg");
} else if (iconNameWithPrefix.contains(QStringLiteral("folder"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/folder.svg") : QStringLiteral(":/client/theme/black/folder.svg");
return colorIconPath % QStringLiteral("folder.svg");
} else if (iconNameWithPrefix.contains(QStringLiteral("deck"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/deck.svg") : QStringLiteral(":/client/theme/black/deck.svg");
return colorIconPath % QStringLiteral("deck.svg");
} else if (iconNameWithPrefix.contains(QStringLiteral("contacts"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/wizard-groupware.svg") : QStringLiteral(":/client/theme/black/wizard-groupware.svg");
return colorIconPath % QStringLiteral("wizard-groupware.svg");
} else if (iconNameWithPrefix.contains(QStringLiteral("calendar"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/calendar.svg") : QStringLiteral(":/client/theme/black/calendar.svg");
return colorIconPath % QStringLiteral("calendar.svg");
} else if (iconNameWithPrefix.contains(QStringLiteral("mail"), Qt::CaseInsensitive)) {
return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/email.svg") : QStringLiteral(":/client/theme/black/email.svg");
return colorIconPath % QStringLiteral("email.svg");
}
return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/change.svg") : QStringLiteral(":/client/theme/change.svg");
return colorIconPath % QStringLiteral("change.svg");
}
QString iconUrlForDefaultIconName(const QString &defaultIconName)
QString iconUrlForDefaultIconName(const QString &defaultIconName, const bool darkMode)
{
const QUrl urlForIcon{defaultIconName};
@ -75,15 +76,16 @@ QString iconUrlForDefaultIconName(const QString &defaultIconName)
return defaultIconName;
}
const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/");
if (defaultIconName.startsWith(QStringLiteral("icon-"))) {
const auto parts = defaultIconName.split(QLatin1Char('-'));
if (parts.size() > 1) {
const QString blackOrWhite = OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/");
const QString blackIconFilePath = blackOrWhite + parts[1] + QStringLiteral(".svg");
const QString blackOrWhiteIconFilePath = colorIconPath + parts[1] + QStringLiteral(".svg");
if (QFile::exists(blackIconFilePath)) {
return blackIconFilePath;
if (QFile::exists(blackOrWhiteIconFilePath)) {
return blackOrWhiteIconFilePath;
}
const QString iconFilePath = QStringLiteral(":/client/theme/") + parts[1] + QStringLiteral(".svg");
@ -93,14 +95,14 @@ QString iconUrlForDefaultIconName(const QString &defaultIconName)
}
}
const auto iconNameFromIconPrefix = localIconPathFromIconPrefix(defaultIconName);
const auto iconNameFromIconPrefix = localIconPathFromIconPrefix(defaultIconName, darkMode);
if (!iconNameFromIconPrefix.isEmpty()) {
return iconNameFromIconPrefix;
}
}
return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/change.svg") : QStringLiteral(":/client/theme/change.svg");
return colorIconPath % QStringLiteral("change.svg");
}
QString generateUrlForThumbnail(const QString &thumbnailUrl, const QUrl &serverUrl)
@ -125,7 +127,7 @@ QString generateUrlForThumbnail(const QString &thumbnailUrl, const QUrl &serverU
return thumbnailUrlCopy;
}
QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl)
QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl, const bool darkMode)
{
auto serverUrlCopy = serverUrl;
@ -144,7 +146,7 @@ QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl)
}
} else if (!fallbackIconCopy.isEmpty()) {
// could be one of names for standard icons (e.g. icon-mail)
const auto defaultIconUrl = iconUrlForDefaultIconName(fallbackIconCopy);
const auto defaultIconUrl = iconUrlForDefaultIconName(fallbackIconCopy, darkMode);
if (!defaultIconUrl.isEmpty()) {
fallbackIconCopy = defaultIconUrl;
}
@ -153,7 +155,7 @@ QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl)
return fallbackIconCopy;
}
QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QString &fallbackIcon, const QUrl &serverUrl)
QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QString &fallbackIcon, const QUrl &serverUrl, const bool darkMode)
{
if (thumbnailUrl.isEmpty() && fallbackIcon.isEmpty()) {
return {};
@ -165,7 +167,7 @@ QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QSt
}
const auto urlForThumbnail = generateUrlForThumbnail(thumbnailUrl, serverUrl);
const auto urlForFallbackIcon = generateUrlForIcon(fallbackIcon, serverUrl);
const auto urlForFallbackIcon = generateUrlForIcon(fallbackIcon, serverUrl, darkMode);
qDebug() << "SEARCH" << urlForThumbnail << urlForFallbackIcon;
@ -204,10 +206,14 @@ QVariant UnifiedSearchResultsListModel::data(const QModelIndex &index, int role)
return _results.at(index.row())._providerName;
case ProviderIdRole:
return _results.at(index.row())._providerId;
case ImagePlaceholderRole:
return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId);
case IconsRole:
return _results.at(index.row())._icons;
case DarkImagePlaceholderRole:
return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId, true);
case LightImagePlaceholderRole:
return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId, false);
case DarkIconsRole:
return _results.at(index.row())._darkIcons;
case LightIconsRole:
return _results.at(index.row())._lightIcons;
case TitleRole:
return _results.at(index.row())._title;
case SublineRole:
@ -239,8 +245,10 @@ QHash<int, QByteArray> UnifiedSearchResultsListModel::roleNames() const
auto roles = QAbstractListModel::roleNames();
roles[ProviderNameRole] = "providerName";
roles[ProviderIdRole] = "providerId";
roles[IconsRole] = "icons";
roles[ImagePlaceholderRole] = "imagePlaceholder";
roles[DarkIconsRole] = "darkIcons";
roles[LightIconsRole] = "lightIcons";
roles[DarkImagePlaceholderRole] = "darkImagePlaceholder";
roles[LightImagePlaceholderRole] = "lightImagePlaceholder";
roles[TitleRole] = "resultTitle";
roles[SublineRole] = "subline";
roles[ResourceUrlRole] = "resourceUrlRole";
@ -577,8 +585,10 @@ void UnifiedSearchResultsListModel::parseResultsForProvider(const QJsonObject &d
const auto accountUrl = (_accountState && _accountState->account()) ? _accountState->account()->url() : QUrl();
result._resourceUrl = makeResourceUrl(resourceUrl, accountUrl);
result._icons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(),
entryMap.value(QStringLiteral("icon")).toString(), accountUrl);
result._darkIcons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(),
entryMap.value(QStringLiteral("icon")).toString(), accountUrl, true);
result._lightIcons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(),
entryMap.value(QStringLiteral("icon")).toString(), accountUrl, false);
newEntries.push_back(result);
}

View File

@ -53,8 +53,10 @@ public:
enum DataRole {
ProviderNameRole = Qt::UserRole + 1,
ProviderIdRole,
ImagePlaceholderRole,
IconsRole,
DarkImagePlaceholderRole,
LightImagePlaceholderRole,
DarkIconsRole,
LightIconsRole,
TitleRole,
SublineRole,
ResourceUrlRole,

View File

@ -343,6 +343,9 @@ QString Theme::hidpiFileName(const QString &iconName, const QColor &backgroundCo
Theme::Theme()
: QObject(nullptr)
{
#if defined(Q_OS_WIN)
reserveDarkPalette = QPalette(QColor(49,49,49,255), QColor(35,35,35,255)); // Windows 11 button and window dark colours
#endif
}
// If this option returns true, the client only supports one folder to sync.
@ -899,16 +902,44 @@ QColor Theme::errorBoxBorderColor() const
return QColor{"black"};
}
QPalette Theme::systemPalette()
void Theme::connectToPaletteSignal()
{
if(!_guiAppInstance) {
const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance());
if(ptr) {
_guiAppInstance.reset(ptr);
connect(ptr, &QGuiApplication::paletteChanged, this, &Theme::systemPaletteChanged);
connect(_guiAppInstance.data(), &QGuiApplication::paletteChanged, this, &Theme::systemPaletteChanged);
connect(_guiAppInstance.data(), &QGuiApplication::paletteChanged, this, &Theme::darkModeChanged);
}
}
}
QPalette Theme::systemPalette()
{
connectToPaletteSignal();
#if defined(Q_OS_WIN)
if(darkMode()) {
return reserveDarkPalette;
}
#endif
return QGuiApplication::palette();
}
bool Theme::darkMode()
{
connectToPaletteSignal();
// Windows: Check registry for dark mode
#if defined(Q_OS_WIN)
const auto darkModeSubkey = QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
if (Utility::registryKeyExists(HKEY_CURRENT_USER, darkModeSubkey) &&
!Utility::registryGetKeyValue(HKEY_CURRENT_USER, darkModeSubkey, QStringLiteral("AppsUseLightTheme")).toBool()) {
return true;
}
return false;
#else
return Theme::isDarkColor(QGuiApplication::palette().window().color());
#endif
}
} // end namespace client

View File

@ -69,6 +69,7 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject
Q_PROPERTY(QColor errorBoxBorderColor READ errorBoxBorderColor CONSTANT)
Q_PROPERTY(QPalette systemPalette READ systemPalette NOTIFY systemPaletteChanged)
Q_PROPERTY(bool darkMode READ darkMode NOTIFY darkModeChanged)
public:
enum CustomMediaType {
oCSetupTop, // ownCloud connect page
@ -594,6 +595,7 @@ public:
static constexpr const char *themePrefix = ":/client/theme/";
QPalette systemPalette();
bool darkMode();
protected:
#ifndef TOKEN_AUTH_ONLY
@ -612,14 +614,21 @@ protected:
signals:
void systrayUseMonoIconsChanged(bool);
void systemPaletteChanged(const QPalette &palette);
void darkModeChanged();
private:
Theme(Theme const &);
Theme &operator=(Theme const &);
void connectToPaletteSignal();
#if defined(Q_OS_WIN)
QPalette reserveDarkPalette; // Windows 11 button and window dark colours
#endif
static Theme *_instance;
bool _mono = false;
QScopedPointer<QGuiApplication> _guiAppInstance;
#ifndef TOKEN_AUTH_ONLY
mutable QHash<QString, QIcon> _iconCache;
#endif

View File

@ -598,7 +598,8 @@ private slots:
QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty());
QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty());
QVERIFY(!index.data(OCC::ActivityListModel::ActionIconRole).toString().isEmpty());
QVERIFY(!index.data(OCC::ActivityListModel::DarkIconRole).toString().isEmpty());
QVERIFY(!index.data(OCC::ActivityListModel::LightIconRole).toString().isEmpty());
QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty());
QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>());

View File

@ -44,6 +44,7 @@
<file>theme/white/state-sync-64.png</file>
<file>theme/white/state-sync-128.png</file>
<file>theme/white/state-sync-256.png</file>
<file>theme/black/change.svg</file>
<file>theme/black/clear.svg</file>
<file>theme/black/comment.svg</file>
<file>theme/black/search.svg</file>
@ -90,6 +91,7 @@
<file>theme/white/folder@2x.png</file>
<file>theme/colored/folder.png</file>
<file>theme/colored/folder@2x.png</file>
<file>theme/black/confirm.svg</file>
<file>theme/black/control-next.svg</file>
<file>theme/black/control-prev.svg</file>
<file>theme/black/settings.svg</file>

View File

@ -12,8 +12,8 @@ QtObject {
readonly property color ncTextColor: Theme.systemPalette.windowText
readonly property color ncSecondaryTextColor: "#808080"
readonly property color ncHeaderTextColor: "white"
readonly property color lightHover: Systray.darkMode ? Qt.lighter(backgroundColor, 2) : Qt.darker(backgroundColor, 1.05)
readonly property color menuBorder: Systray.darkMode ? Qt.lighter(backgroundColor, 3) : Qt.darker(backgroundColor, 1.5)
readonly property color lightHover: Theme.darkMode ? Qt.lighter(backgroundColor, 2) : Qt.darker(backgroundColor, 1.05)
readonly property color menuBorder: ncSecondaryTextColor
readonly property color backgroundColor: Theme.systemPalette.base
// ErrorBox colors