From 87a4602b9ac2fc2f6c631e3027377689c033b40a Mon Sep 17 00:00:00 2001 From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> Date: Mon, 14 Sep 2020 13:04:57 -0400 Subject: [PATCH] notifications: implement incoming message notifications - Note: adds forcing the main window to the top to overcome a Qt/QML bug which prevents this from working when activating from a notification window source. Change-Id: Ib5a93d3937fe5a4de0229f7aeae8469d1ffdfba3 --- jami-qt.pro | 1 - src/MainApplicationWindow.qml | 28 ++++++- src/accountadapter.cpp | 55 +++++++------ src/accountadapter.h | 21 +++-- src/accountlistmodel.h | 2 + src/calladapter.cpp | 82 +++++++++---------- src/calladapter.h | 5 +- src/conversationsadapter.cpp | 72 ++++++++++++++-- src/conversationsadapter.h | 4 + src/globalsystemtray.cpp | 45 ---------- src/globalsystemtray.h | 28 +++---- src/lrcinstance.h | 12 +++ src/mainapplication.cpp | 22 ++++- src/mainview/MainView.qml | 5 ++ src/mainview/components/AccountComboBox.qml | 28 +++---- .../components/ConversationSmartListView.qml | 7 +- src/mainview/components/SidePanel.qml | 9 +- src/messagesadapter.cpp | 19 ++--- src/qtutils.h | 6 +- src/utils.cpp | 48 ++++++----- src/utils.h | 38 ++++----- 21 files changed, 306 insertions(+), 231 deletions(-) delete mode 100644 src/globalsystemtray.cpp diff --git a/jami-qt.pro b/jami-qt.pro index afc1348fc..d3933c6e7 100644 --- a/jami-qt.pro +++ b/jami-qt.pro @@ -162,7 +162,6 @@ SOURCES += \ src/runguard.cpp \ src/webchathelpers.cpp \ src/main.cpp \ - src/globalsystemtray.cpp \ src/smartlistmodel.cpp \ src/utils.cpp \ src/pixbufmanipulator.cpp \ diff --git a/src/MainApplicationWindow.qml b/src/MainApplicationWindow.qml index 695328f9c..8d0da0d6f 100644 --- a/src/MainApplicationWindow.qml +++ b/src/MainApplicationWindow.qml @@ -145,11 +145,31 @@ ApplicationWindow { Connections { target: LRCInstance + function restore(window) { + window.show() + window.raise(); + window.requestActivate() + } + function onRestoreAppRequested() { - if (mainViewLoader.item) - mainViewLoader.item.show() - else - wizardView.show() + var window = mainViewLoader.item ? mainViewLoader.item : wizardView + restore(window) + } + + function onNotificationClicked(forceToTop) { + var window = mainViewLoader.item ? mainViewLoader.item : wizardView + // This is a hack to bring the window to the front which is normally done + // with QWindow::requestActivate but is thwarted for qml windows by the + // notification being clicked. Native solutions are preferable. + if (forceToTop && (!window.visible + || window.visibility & Qt.WindowMinimized + || window.visibility === Qt.WindowNoState)) { + var tmpFlags = window.flags + window.hide() + window.flags = Qt.WindowStaysOnTopHint + window.flags = tmpFlags + } + restore(window) } } } diff --git a/src/accountadapter.cpp b/src/accountadapter.cpp index 46d9172c1..a516f56d8 100644 --- a/src/accountadapter.cpp +++ b/src/accountadapter.cpp @@ -35,7 +35,16 @@ AccountAdapter::AccountAdapter(QObject* parent) void AccountAdapter::safeInit() { - setSelectedAccountId(LRCInstance::getCurrAccId()); + connect(&LRCInstance::instance(), + &LRCInstance::currentAccountChanged, + this, + &AccountAdapter::onCurrentAccountChanged); + + backToWelcomePage(); + + auto accountId = LRCInstance::getCurrAccId(); + setProperties(accountId); + connectAccount(accountId); } lrc::api::NewAccountModel* @@ -60,8 +69,10 @@ void AccountAdapter::accountChanged(int index) { auto accountList = LRCInstance::accountModel().getAccountList(); - if (accountList.size() > index) - setSelectedAccountId(accountList.at(index)); + if (accountList.size() > index) { + LRCInstance::setSelectedAccountId(accountList.at(index)); + backToWelcomePage(); + } } void @@ -121,8 +132,6 @@ AccountAdapter::createJamiAccount(QString registeredName, } else { LRCInstance::setAvatarForAccount(QPixmap::fromImage(avatarImg), accountId); } - - }); connectFailure(); @@ -287,6 +296,14 @@ AccountAdapter::setSelectedConvId(const QString& convId) LRCInstance::setSelectedConvId(convId); } +void +AccountAdapter::onCurrentAccountChanged() +{ + auto accountId = LRCInstance::getCurrAccId(); + setProperties(accountId); + connectAccount(accountId); +} + bool AccountAdapter::hasPassword() { @@ -329,23 +346,6 @@ AccountAdapter::passwordSetStatusMessageBox(bool success, QString title, QString } } -void -AccountAdapter::setSelectedAccountId(const QString& accountId) -{ - LRCInstance::setSelectedAccountId(accountId); - - setProperty("currentAccountId", accountId); - auto accountType = LRCInstance::getAccountInfo(accountId).profileInfo.type; - setProperty("currentAccountType", lrc::api::profile::to_string(accountType)); - - connectAccount(accountId); - - emit contactModelChanged(); - emit deviceModelChanged(); - - backToWelcomePage(); -} - void AccountAdapter::backToWelcomePage() { @@ -366,7 +366,6 @@ AccountAdapter::deselectConversation() return; } - currentConversationModel->selectConversation(""); LRCInstance::setSelectedConvId(); } @@ -428,3 +427,13 @@ AccountAdapter::connectAccount(const QString& accountId) qWarning() << "Couldn't get account: " << accountId; } } + +void +AccountAdapter::setProperties(const QString& accountId) +{ + setProperty("currentAccountId", accountId); + auto accountType = LRCInstance::getAccountInfo(accountId).profileInfo.type; + setProperty("currentAccountType", lrc::api::profile::to_string(accountType)); + emit contactModelChanged(); + emit deviceModelChanged(); +} diff --git a/src/accountadapter.h b/src/accountadapter.h index da7d0949f..0f7ff21e1 100644 --- a/src/accountadapter.h +++ b/src/accountadapter.h @@ -32,10 +32,8 @@ class AccountAdapter final : public QmlAdapterBase Q_OBJECT Q_PROPERTY(lrc::api::NewAccountModel* model READ getModel NOTIFY modelChanged) - Q_PROPERTY(lrc::api::ContactModel* contactModel READ getContactModel NOTIFY contactModelChanged) Q_PROPERTY(lrc::api::NewDeviceModel* deviceModel READ getDeviceModel NOTIFY deviceModelChanged) - Q_PROPERTY(QString currentAccountId MEMBER currentAccountId_ NOTIFY currentAccountIdChanged) Q_PROPERTY(lrc::api::profile::Type currentAccountType MEMBER currentAccountType_ NOTIFY currentAccountTypeChanged) @@ -62,6 +60,7 @@ public: protected: void safeInit() override; +public: /* * Change to account corresponding to combox box index. */ @@ -105,7 +104,6 @@ protected: Q_INVOKABLE bool hasVideoCall(); Q_INVOKABLE bool isPreviewing(); Q_INVOKABLE void setCurrAccDisplayName(const QString& text); - Q_INVOKABLE void setSelectedAccountId(const QString& accountId = {}); Q_INVOKABLE void setSelectedConvId(const QString& convId = {}); signals: @@ -121,10 +119,13 @@ signals: void navigateToWelcomePageRequested(); void accountAdded(bool showBackUp, int index); +private slots: + void onCurrentAccountChanged(); + private: - QString currentAccountId_; - lrc::api::profile::Type currentAccountType_; - int accountListSize_; + QString currentAccountId_ {}; + lrc::api::profile::Type currentAccountType_ {}; + int accountListSize_ {}; void backToWelcomePage(); void deselectConversation(); @@ -133,8 +134,14 @@ private: * Make account signal connections. */ void connectAccount(const QString& accountId); + + /* + * Make account signal connections. + */ + void setProperties(const QString& accountId); + /* - * Implement what to do when creat accout fails. + * Implement what to do when account creation fails. */ void connectFailure(); diff --git a/src/accountlistmodel.h b/src/accountlistmodel.h index 976d116e3..6741871d1 100644 --- a/src/accountlistmodel.h +++ b/src/accountlistmodel.h @@ -31,6 +31,7 @@ class AccountListModel : public QAbstractListModel public: enum Role { Alias = Qt::UserRole + 1, Username, Picture, Type, Status, ID }; + Q_ENUM(Role) explicit AccountListModel(QObject* parent = 0); ~AccountListModel(); @@ -41,6 +42,7 @@ public: int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + /* * Override role name as access point in qml. */ diff --git a/src/calladapter.cpp b/src/calladapter.cpp index 58a125ab1..e330cd5c8 100644 --- a/src/calladapter.cpp +++ b/src/calladapter.cpp @@ -109,9 +109,8 @@ CallAdapter::slotShowIncomingCallView(const QString& accountId, const conversati auto selectedAccountId = LRCInstance::getCurrAccId(); auto* callModel = LRCInstance::getCurrentCallModel(); if (!callModel->hasCall(convInfo.callId)) { - if (QApplication::focusObject() == nullptr || accountId != selectedAccountId) { - showNotification(accountId, convInfo); + showNotification(accountId, convInfo.uid); return; } @@ -125,16 +124,16 @@ CallAdapter::slotShowIncomingCallView(const QString& accountId, const conversati // Check INCOMING / OUTGOING call in current conversation if (currentConvHasCall) { auto currentCall = callModel->getCall(currentConvInfo.callId); - if (currentCall.status == lrc::api::call::Status::CONNECTED || - currentCall.status == lrc::api::call::Status::IN_PROGRESS) { - showNotification(accountId, convInfo); + if (currentCall.status == lrc::api::call::Status::CONNECTED + || currentCall.status == lrc::api::call::Status::IN_PROGRESS) { + showNotification(accountId, convInfo.uid); return; } } emit incomingCallNeedToSetupMainView(accountId, convInfo.uid); emit showIncomingCallPage(accountId, convInfo.uid); emit showCallStack(accountId, convInfo.uid, true); - emit updateConversationSmartList(); + emit LRCInstance::instance().updateSmartList(); return; } @@ -150,10 +149,9 @@ CallAdapter::slotShowIncomingCallView(const QString& accountId, const conversati auto showIncomingCall = false; auto accountProperties = LRCInstance::accountModel().getAccountConfig(selectedAccountId); if (!accountProperties.autoAnswer && !accountProperties.isRendezVous) { - // App not focused or in different account if (QApplication::focusObject() == nullptr || accountId != selectedAccountId) { - showNotification(accountId, convInfo); + showNotification(accountId, convInfo.uid); return; } @@ -169,7 +167,7 @@ CallAdapter::slotShowIncomingCallView(const QString& accountId, const conversati if (currentConvHasCall) { auto currentCall = callModel->getCall(currentConvInfo.callId); if (currentCall.status == lrc::api::call::Status::OUTGOING_RINGING) { - showNotification(accountId, convInfo); + showNotification(accountId, convInfo.uid); } else { showIncomingCall = true; } @@ -179,9 +177,9 @@ CallAdapter::slotShowIncomingCallView(const QString& accountId, const conversati } else { // Not current conversation if (currentConvHasCall) { auto currentCall = callModel->getCall(currentConvInfo.callId); - if (currentCall.status == lrc::api::call::Status::CONNECTED || - currentCall.status == lrc::api::call::Status::IN_PROGRESS) { - showNotification(accountId, convInfo); + if (currentCall.status == lrc::api::call::Status::CONNECTED + || currentCall.status == lrc::api::call::Status::IN_PROGRESS) { + showNotification(accountId, convInfo.uid); return; } } @@ -196,7 +194,7 @@ CallAdapter::slotShowIncomingCallView(const QString& accountId, const conversati } } emit callStatusChanged(static_cast<int>(call.status), accountId, convInfo.uid); - emit updateConversationSmartList(); + emit LRCInstance::instance().updateSmartList(); } void @@ -300,45 +298,39 @@ CallAdapter::getConferencesInfos() } void -CallAdapter::showNotification(const QString& accountId, const lrc::api::conversation::Info& convInfo) +CallAdapter::showNotification(const QString& accountId, const QString& convUid) { - // Hack for handling multiple consecutive calls to slotShowIncomingCallView (bug) - // Do not set notification if it is already active for the account and conversation - if (accountId == GlobalSystemTray::instance().getTriggeredAccountId() && - convInfo.uid == GlobalSystemTray::instance().getPossibleOnGoingConversationUid()) { - return; - } - - QString sender = convInfo.uid; - if (accountId != "") { + QString from {}; + auto convInfo = LRCInstance::getConversationFromConvUid(convUid, accountId); + if (!accountId.isEmpty() && !convInfo.uid.isEmpty()) { auto& accInfo = LRCInstance::getAccountInfo(accountId); if (!convInfo.participants.isEmpty()) { - auto &contact = accInfo.contactModel->getContact(convInfo.participants[0]); - sender = Utils::bestNameForContact(contact); + auto& contact = accInfo.contactModel->getContact(convInfo.participants[0]); + from = Utils::bestNameForContact(contact); } } - GlobalSystemTray::instance().setPossibleOnGoingConversationUid(convInfo.uid); - - QObject::connect(&GlobalSystemTray::instance(), &GlobalSystemTray::messageClicked, - this, [this, accountId, convInfo]() { - if (accountId != "" && convInfo.uid != "") { - - emit incomingCallNeedToSetupMainView(accountId, convInfo.uid, true); - - auto call = LRCInstance::getCallInfoForConversation(convInfo); - if (call->status == lrc::api::call::Status::INCOMING_RINGING) { - emit showIncomingCallPage(accountId, convInfo.uid); - emit showCallStack(accountId, convInfo.uid, true); - } - emit updateConversationSmartList(); + auto onClicked = [this, accountId, convInfo]() { +#ifdef Q_OS_WINDOWS + emit LRCInstance::instance().notificationClicked(); +#else + emit LRCInstance::instance().notificationClicked(true); +#endif + // TODO: only a selectConversation(accountId, convUid) should occur + // here, the UI should resolve its state by observation as much as possible, + // or in response to a conversationSelected signal. + // - https://git.jami.net/savoirfairelinux/jami-client-qt/issues/87 + // - https://git.jami.net/savoirfairelinux/jami-client-qt/issues/86 + emit incomingCallNeedToSetupMainView(accountId, convInfo.uid); + auto call = LRCInstance::getCallInfoForConversation(convInfo); + if (call && call->status == lrc::api::call::Status::INCOMING_RINGING) { + emit showIncomingCallPage(accountId, convInfo.uid); + emit showCallStack(accountId, convInfo.uid, true); } - GlobalSystemTray::instance().setTriggeredAccountId(""); - GlobalSystemTray::instance().setPossibleOnGoingConversationUid(""); - - }, Qt::UniqueConnection); + emit LRCInstance::instance().updateSmartList(); + }; - Utils::showSystemNotification(QApplication::focusWidget(), sender, tr("is calling you"), 0, accountId); + Utils::showNotification(tr("is calling you"), from, accountId, convUid, onClicked); } void @@ -472,7 +464,7 @@ CallAdapter::connectCallModel(const QString& accountId) break; } - emit updateConversationSmartList(); + emit LRCInstance::instance().updateSmartList(); }); } diff --git a/src/calladapter.h b/src/calladapter.h index cfc18edc9..6fea6b3be 100644 --- a/src/calladapter.h +++ b/src/calladapter.h @@ -78,7 +78,8 @@ signals: const QString& accountId, const QString& callId); - void incomingCallNeedToSetupMainView(const QString& accountId, const QString& convUid, + void incomingCallNeedToSetupMainView(const QString& accountId, + const QString& convUid, bool fromNotification = false); void previewVisibilityNeedToChange(bool visible); @@ -107,7 +108,7 @@ private: const QString& accountId = {}, bool forceCallOnly = false); bool shouldShowPreview(bool force); - void showNotification(const QString& accountId, const lrc::api::conversation::Info& convInfo); + void showNotification(const QString& accountId, const QString& convUid); /* * Current conf/call info. diff --git a/src/conversationsadapter.cpp b/src/conversationsadapter.cpp index e52fb0070..2dcedda88 100644 --- a/src/conversationsadapter.cpp +++ b/src/conversationsadapter.cpp @@ -24,6 +24,9 @@ #include "conversationsadapter.h" #include "utils.h" +#include "qtutils.h" + +#include <QApplication> ConversationsAdapter::ConversationsAdapter(QObject* parent) : QmlAdapterBase(parent) @@ -42,6 +45,11 @@ ConversationsAdapter::safeInit() emit showChatView(accountId, convInfo.uid); }); + connect(&LRCInstance::behaviorController(), + &BehaviorController::newUnreadInteraction, + this, + &ConversationsAdapter::onNewUnreadInteraction); + connect(&LRCInstance::instance(), &LRCInstance::currentAccountChanged, this, @@ -88,20 +96,33 @@ ConversationsAdapter::selectConversation(const QString& convUid) } bool -ConversationsAdapter::selectConversation(const lrc::api::conversation::Info& item, +ConversationsAdapter::selectConversation(const lrc::api::conversation::Info& convInfo, bool preventSendingSignal) { // accInfo.conversationModel->selectConversation(item.uid) only emit ui // behavior control signals, but sometimes we do not want that, // preventSendingSignal boolean can help us to determine. - if (LRCInstance::getCurrentConvUid() == item.uid) { + if (LRCInstance::getCurrentConvUid() == convInfo.uid + && LRCInstance::getCurrAccId() == convInfo.accountId) { return false; - } else if (item.participants.size() > 0) { - auto& accInfo = LRCInstance::getAccountInfo(item.accountId); - LRCInstance::setSelectedConvId(item.uid); - if (!preventSendingSignal) - accInfo.conversationModel->selectConversation(item.uid); - accInfo.conversationModel->clearUnreadInteractions(item.uid); + } else if (convInfo.participants.size() > 0) { + // If the account is not currently selected, do that first, then + // proceed to select the conversation. + auto selectConversation = [convInfo, preventSendingSignal] { + auto& accInfo = LRCInstance::getAccountInfo(convInfo.accountId); + LRCInstance::setSelectedConvId(convInfo.uid); + if (!preventSendingSignal) + accInfo.conversationModel->selectConversation(convInfo.uid); + accInfo.conversationModel->clearUnreadInteractions(convInfo.uid); + }; + if (convInfo.accountId != LRCInstance::getCurrAccId()) { + Utils::oneShotConnect(&LRCInstance::instance(), + &LRCInstance::currentAccountChanged, + [selectConversation] { selectConversation(); }); + LRCInstance::setSelectedAccountId(convInfo.accountId); + } else { + selectConversation(); + } return true; } } @@ -138,6 +159,38 @@ ConversationsAdapter::onCurrentAccountIdChanged() connectConversationModel(); } +void +ConversationsAdapter::onNewUnreadInteraction(const QString& accountId, + const QString& convUid, + uint64_t interactionId, + const interaction::Info& interaction) +{ + Q_UNUSED(interactionId) + if (!interaction.authorUri.isEmpty() + && (!QApplication::focusWindow() || accountId != LRCInstance::getCurrAccId() + || convUid != LRCInstance::getCurrentConvUid())) { + auto& accInfo = LRCInstance::getAccountInfo(accountId); + auto& contact = accInfo.contactModel->getContact(interaction.authorUri); + auto from = Utils::bestNameForContact(contact); + auto onClicked = [this, accountId, convUid, uri = interaction.authorUri] { +#ifdef Q_OS_WINDOWS + emit LRCInstance::instance().notificationClicked(); +#else + emit LRCInstance::instance().notificationClicked(true); +#endif + auto convInfo = LRCInstance::getConversationFromConvUid(convUid, accountId); + if (!convInfo.uid.isEmpty()) { + selectConversation(convInfo, false); + emit LRCInstance::instance().updateSmartList(); + emit modelSorted(uri); + } + }; + + Utils::showNotification(interaction.body, from, accountId, convUid, onClicked); + return; + } +} + void ConversationsAdapter::updateConversationsFilterWidget() { @@ -282,8 +335,11 @@ ConversationsAdapter::disconnectConversationModel() QObject::disconnect(modelSortedConnection_); QObject::disconnect(modelUpdatedConnection_); QObject::disconnect(filterChangedConnection_); + QObject::disconnect(newConversationConnection_); QObject::disconnect(conversationRemovedConnection_); QObject::disconnect(conversationClearedConnection); + QObject::disconnect(selectedCallChanged_); + QObject::disconnect(smartlistSelectionConnection_); QObject::disconnect(interactionRemovedConnection_); QObject::disconnect(searchStatusChangedConnection_); QObject::disconnect(searchResultUpdatedConnection_); diff --git a/src/conversationsadapter.h b/src/conversationsadapter.h index a2b962459..8b6ebdc61 100644 --- a/src/conversationsadapter.h +++ b/src/conversationsadapter.h @@ -59,6 +59,10 @@ signals: private slots: void onCurrentAccountIdChanged(); + void onNewUnreadInteraction(const QString& accountId, + const QString& convUid, + uint64_t interactionId, + const interaction::Info& interaction); private: void setConversationFilter(lrc::api::profile::Type filter); diff --git a/src/globalsystemtray.cpp b/src/globalsystemtray.cpp deleted file mode 100644 index 73b9fddbb..000000000 --- a/src/globalsystemtray.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2015-2020 by Savoir-faire Linux - * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.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 3 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "globalsystemtray.h" - -GlobalSystemTray::GlobalSystemTray() {} - -void -GlobalSystemTray::setTriggeredAccountId(const QString& accountId) -{ - triggeredAccountId_ = accountId; -} - -const QString& -GlobalSystemTray::getTriggeredAccountId() -{ - return triggeredAccountId_; -} - -void -GlobalSystemTray::setPossibleOnGoingConversationUid(const QString& convUid) -{ - triggeredOnGoingConvUid_ = convUid; -} - -const QString& -GlobalSystemTray::getPossibleOnGoingConversationUid() -{ - return triggeredOnGoingConvUid_; -} diff --git a/src/globalsystemtray.h b/src/globalsystemtray.h index 1ce513c29..e3853306d 100644 --- a/src/globalsystemtray.h +++ b/src/globalsystemtray.h @@ -22,32 +22,32 @@ #include <QSystemTrayIcon> -class GlobalSystemTray : public QSystemTrayIcon +class GlobalSystemTray final : public QSystemTrayIcon { Q_OBJECT public: + ~GlobalSystemTray() = default; static GlobalSystemTray& instance() { static GlobalSystemTray* instance_ = new GlobalSystemTray(); return *instance_; } - /* - * Remember the last triggering account for the notification, - * safe since user cannot activate previous notifications. - */ - void setTriggeredAccountId(const QString& accountId); + inline static QString notificationAccountId {}; + inline static QString notificationConvUid {}; - const QString& getTriggeredAccountId(); - - void setPossibleOnGoingConversationUid(const QString& convUid); - - const QString& getPossibleOnGoingConversationUid(); + template<typename Func> + static void connectClicked(Func&& onClicked) + { + auto& instance_ = instance(); + instance_.disconnect(instance_.messageClicked_); + instance_.connect(&instance_, &QSystemTrayIcon::messageClicked, onClicked); + } private: - GlobalSystemTray(); + explicit GlobalSystemTray() + : QSystemTrayIcon() {}; - QString triggeredAccountId_; - QString triggeredOnGoingConvUid_; + QMetaObject::Connection messageClicked_; }; diff --git a/src/lrcinstance.h b/src/lrcinstance.h index 811ff5687..145cb6bf5 100644 --- a/src/lrcinstance.h +++ b/src/lrcinstance.h @@ -226,6 +226,16 @@ public: return invalid; } + static const conversation::Info& getConversationFromConvUid(const QString& convUid, + const QString& accountId = {}, + bool filtered = false) + { + return getConversation( + !accountId.isEmpty() ? accountId : getCurrAccId(), + [&](const conversation::Info& conv) -> bool { return convUid == conv.uid; }, + filtered); + } + static const conversation::Info& getConversationFromCallId(const QString& callId, const QString& accountId = {}, bool filtered = false) @@ -431,6 +441,8 @@ signals: void accountListChanged(); void currentAccountChanged(); void restoreAppRequested(); + void notificationClicked(bool forceToTop = false); + void updateSmartList(); private: LRCInstance(migrateCallback willMigrateCb = {}, migrateCallback didMigrateCb = {}) diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp index dc93b6b01..1c37de9e6 100644 --- a/src/mainapplication.cpp +++ b/src/mainapplication.cpp @@ -33,6 +33,7 @@ #include <QFontDatabase> #include <QMenu> #include <QQmlContext> +#include <QWindow> #include <locale.h> @@ -133,8 +134,27 @@ MainApplication::init() GlobalInstances::setPixmapManipulator(std::make_unique<PixbufManipulator>()); initLrc(); + initConnectivityMonitor(); +#ifdef Q_OS_WINDOWS + QObject::connect(&LRCInstance::instance(), &LRCInstance::notificationClicked, [] { + for (QWindow* appWindow : qApp->allWindows()) { + if (appWindow->objectName().compare("mainViewWindow")) + continue; + // clang-format off + ::SetWindowPos((HWND) appWindow->winId(), + HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + ::SetWindowPos((HWND) appWindow->winId(), + HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + // clang-format on + return; + } + }); +#endif + bool startMinimized {false}; parseArguments(startMinimized); @@ -214,7 +234,7 @@ MainApplication::initConnectivityMonitor() { #ifdef Q_OS_WIN connectivityMonitor_.reset(new ConnectivityMonitor(this)); - connect(connectivityMonitor_.get(), &ConnectivityMonitor::connectivityChanged, [this] { + connect(connectivityMonitor_.get(), &ConnectivityMonitor::connectivityChanged, [] { LRCInstance::connectivityChanged(); }); #endif // Q_OS_WIN diff --git a/src/mainview/MainView.qml b/src/mainview/MainView.qml index 47e6ecc5f..13a3acf79 100644 --- a/src/mainview/MainView.qml +++ b/src/mainview/MainView.qml @@ -32,6 +32,7 @@ import "../settingsview/components" Window { id: mainViewWindow + objectName: "mainViewWindow" property int minWidth: settingsViewPreferredWidth property int minHeight: 400 @@ -121,6 +122,7 @@ Window { function newAccountAdded(index) { mainViewWindowSidePanel.refreshAccountComboBox(index) + AccountAdapter.accountChanged(index) } function currentAccountIsCalling() { @@ -230,6 +232,7 @@ Window { callStackView.updateCorrespondingUI() mainViewWindowSidePanel.refreshAccountComboBox(index) + AccountAdapter.accountChanged(index) ConversationsAdapter.selectConversation(accountId, convUid, !fromNotification) MessagesAdapter.setupChatView(convUid) } @@ -304,6 +307,7 @@ Window { mainViewWindowSidePanel.deselectConversationSmartList() mainViewWindowSidePanel.refreshAccountComboBox(index) + AccountAdapter.accountChanged(index) settingsView.slotAccountListChanged() settingsView.setSelected(settingsView.selectedMenu, true) @@ -545,6 +549,7 @@ Window { onSettingsViewWindowNeedToShowMainViewWindow: { mainViewWindowSidePanel.refreshAccountComboBox(0) + AccountAdapter.accountChanged(index) toggleSettingsView() } diff --git a/src/mainview/components/AccountComboBox.qml b/src/mainview/components/AccountComboBox.qml index 127bfd5ce..54a7dc8ed 100644 --- a/src/mainview/components/AccountComboBox.qml +++ b/src/mainview/components/AccountComboBox.qml @@ -25,7 +25,7 @@ import net.jami.Adapters 1.0 import "../../commoncomponents" ComboBox { - id: accountComboBox + id: root signal accountChanged(int index) signal needToBackToWelcomePage @@ -61,9 +61,9 @@ ComboBox { Image { id: userImageRoot - anchors.left: accountComboBox.left + anchors.left: root.left anchors.leftMargin: 16 - anchors.verticalCenter: accountComboBox.verticalCenter + anchors.verticalCenter: root.verticalCenter width: 30 height: 30 @@ -130,7 +130,7 @@ ComboBox { font: textUserAliasRoot.font elide: Text.ElideRight - elideWidth: accountComboBox.width - userImageRoot.width - settingsButton.width + elideWidth: root.width - userImageRoot.width - settingsButton.width - arrowDropDown.width - qrCodeGenerateButton.width - 55 // Role::Alias @@ -142,7 +142,7 @@ ComboBox { font: textUsernameRoot.font elide: Text.ElideRight - elideWidth: accountComboBox.width - userImageRoot.width - settingsButton.width + elideWidth: root.width - userImageRoot.width - settingsButton.width - qrCodeGenerateButton.width - 55 @@ -156,7 +156,7 @@ ComboBox { anchors.right: settingsButton.left anchors.rightMargin: 10 - anchors.verticalCenter: accountComboBox.verticalCenter + anchors.verticalCenter: root.verticalCenter buttonImageHeight: height - 8 buttonImageWidth: width - 8 @@ -180,9 +180,9 @@ ComboBox { HoverableButton { id: settingsButton - anchors.right: accountComboBox.right + anchors.right: root.right anchors.rightMargin: 10 - anchors.verticalCenter: accountComboBox.verticalCenter + anchors.verticalCenter: root.verticalCenter buttonImageHeight: height - 8 buttonImageWidth: width - 8 @@ -207,8 +207,8 @@ ComboBox { background: Rectangle { id: rootItemBackground - implicitWidth: accountComboBox.width - implicitHeight: accountComboBox.height + implicitWidth: root.width + implicitHeight: root.height color: JamiTheme.backgroundColor } @@ -240,9 +240,9 @@ ComboBox { } else { rootItemBackground.color = JamiTheme.releaseColor if (comboBoxPopup.opened) { - accountComboBox.popup.close() + root.popup.close() } else { - accountComboBox.popup.open() + root.popup.open() } } } @@ -291,11 +291,11 @@ ComboBox { } onAccountNeedToChange: { - accountComboBox.accountChanged(index) + root.accountChanged(index) } onNewAccountButtonClicked: { - accountComboBox.newAccountButtonClicked() + root.newAccountButtonClicked() } } diff --git a/src/mainview/components/ConversationSmartListView.qml b/src/mainview/components/ConversationSmartListView.qml index db5c82c5b..c3df41f74 100644 --- a/src/mainview/components/ConversationSmartListView.qml +++ b/src/mainview/components/ConversationSmartListView.qml @@ -80,11 +80,8 @@ ListView { } Connections { - target: CallAdapter - - function onUpdateConversationSmartList() { - updateListView() - } + target: LRCInstance + function onUpdateSmartList() { updateListView() } } onCurrentIndexChanged: { diff --git a/src/mainview/components/SidePanel.qml b/src/mainview/components/SidePanel.qml index 5ef32df94..e3eb40e9e 100644 --- a/src/mainview/components/SidePanel.qml +++ b/src/mainview/components/SidePanel.qml @@ -26,12 +26,19 @@ import "../../commoncomponents" Rectangle { id: sidePanelRect + color: JamiTheme.backgroundColor property bool tabBarVisible: true property int pendingRequestCount: 0 property int totalUnreadMessagesCount: 0 + property string currentAccountId: AccountAdapter.currentAccountId + onCurrentAccountIdChanged: { + var index = UtilsAdapter.getCurrAccList().indexOf(currentAccountId) + refreshAccountComboBox(index) + } + signal conversationSmartListNeedToAccessMessageWebView(string currentUserDisplayName, string currentUserAlias, string currentUID, bool callStackViewShouldShow, bool isAudioOnly, int callState) signal accountComboBoxNeedToShowWelcomePage() signal conversationSmartListViewNeedToShowWelcomePage @@ -73,8 +80,6 @@ Rectangle { } function refreshAccountComboBox(index) { - AccountAdapter.accountChanged(index) - accountComboBox.update() accountChangedUIReset() accountComboBox.resetAccountListModel() diff --git a/src/messagesadapter.cpp b/src/messagesadapter.cpp index 539d9c19a..7f2d85a0e 100644 --- a/src/messagesadapter.cpp +++ b/src/messagesadapter.cpp @@ -23,7 +23,9 @@ #include "messagesadapter.h" +#include "globalsystemtray.h" #include "qtutils.h" +#include "utils.h" #include "webchathelpers.h" #include <QApplication> @@ -469,22 +471,11 @@ MessagesAdapter::newInteraction(const QString& accountId, { Q_UNUSED(interactionId); try { - auto& accountInfo = LRCInstance::getAccountInfo(accountId); - auto& convModel = accountInfo.conversationModel; - const auto conversation = convModel->getConversationForUID(convUid); - - if (conversation.uid.isEmpty()) { - return; - } - if (!interaction.authorUri.isEmpty() - && (!QApplication::focusWindow() || LRCInstance::getCurrAccId() != accountId)) { - /* - * TODO: Notification from other accounts. - */ - } - if (convUid != LRCInstance::getCurrentConvUid()) { + if (convUid.isEmpty() || convUid != LRCInstance::getCurrentConvUid()) { return; } + auto& accountInfo = LRCInstance::getAccountInfo(accountId); + auto& convModel = accountInfo.conversationModel; convModel->clearUnreadInteractions(convUid); printNewInteraction(*convModel, interactionId, interaction); } catch (...) { diff --git a/src/qtutils.h b/src/qtutils.h index 577127b42..ba970047c 100644 --- a/src/qtutils.h +++ b/src/qtutils.h @@ -1,10 +1,10 @@ -/* +/*! * Copyright (C) 2015-2020 by Savoir-faire Linux * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com> * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * Author: Isa Nanic <isa.nanic@savoirfairelinux.com> - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 diff --git a/src/utils.cpp b/src/utils.cpp index 7cfdc54c3..805d803f1 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,10 +1,10 @@ -/* +/*! * Copyright (C) 2015-2020 by Savoir-faire Linux * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com> * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * Author: Isa Nanic <isa.nanic@savoirfairelinux.com * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 @@ -281,34 +281,38 @@ Utils::getCirclePhoto(const QImage original, int sizePhoto) } void -Utils::showSystemNotification(QWidget* widget, - const QString& message, - long delay, - const QString& triggeredAccountId) +Utils::showNotification(const QString& message, + const QString& from, + const QString& accountId, + const QString& convUid, + std::function<void()> const& onClicked) { + if (accountId.isEmpty() || convUid.isEmpty()) { + // This should never happen. + qFatal("Invalid account or conversation."); + } + if (!AppSettingsManager::getValue(Settings::Key::EnableNotifications).toBool()) { qWarning() << "Notifications are disabled"; return; } - GlobalSystemTray::instance().setTriggeredAccountId(triggeredAccountId); - GlobalSystemTray::instance().showMessage(message, "", QIcon(":images/jami.png")); - QApplication::alert(widget, delay); -} -void -Utils::showSystemNotification(QWidget* widget, - const QString& sender, - const QString& message, - long delay, - const QString& triggeredAccountId) -{ - if (!AppSettingsManager::getValue(Settings::Key::EnableNotifications).toBool()) { - qWarning() << "Notifications are disabled"; + if (accountId == GlobalSystemTray::notificationAccountId + && convUid == GlobalSystemTray::notificationConvUid) { + GlobalSystemTray::notificationAccountId.clear(); + GlobalSystemTray::notificationConvUid.clear(); return; } - GlobalSystemTray::instance().setTriggeredAccountId(triggeredAccountId); - GlobalSystemTray::instance().showMessage(sender, message, QIcon(":images/jami.png")); - QApplication::alert(widget, delay); + + GlobalSystemTray::connectClicked(std::move(onClicked)); + + GlobalSystemTray::notificationAccountId = accountId; + GlobalSystemTray::notificationConvUid = convUid; + + if (from.isEmpty()) + GlobalSystemTray::instance().showMessage(message, "", QIcon(":images/jami.png")); + else + GlobalSystemTray::instance().showMessage(from, message, QIcon(":images/jami.png")); } QSize diff --git a/src/utils.h b/src/utils.h index 2cec141cf..3bb7a08a2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,10 +1,10 @@ -/* +/*! * Copyright (C) 2015-2020 by Savoir-faire Linux * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com> * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * Author: Isa Nanic <isa.nanic@savoirfairelinux.com> - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 @@ -45,7 +45,7 @@ #include <windows.h> #undef ERROR #else -#define LPCWSTR char * +#define LPCWSTR char* #endif #include "api/account.h" @@ -58,25 +58,21 @@ namespace Utils { /* * App/System */ -bool CreateStartupLink(const std::wstring &wstrAppName); -void DeleteStartupLink(const std::wstring &wstrAppName); +bool CreateStartupLink(const std::wstring& wstrAppName); +void DeleteStartupLink(const std::wstring& wstrAppName); bool CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink); bool CheckStartupLink(const std::wstring& wstrAppName); const char* WinGetEnv(const char* name); QString GetRingtonePath(); QString GenGUID(); QString GetISODate(); -void showSystemNotification(QWidget* widget, - const QString& message, - long delay = 5000, - const QString &triggeredAccountId = ""); -void showSystemNotification(QWidget *widget, - const QString &sender, - const QString &message, - long delay = 5000, - const QString &triggeredAccountId = ""); -QSize getRealSize(QScreen *screen); -void forceDeleteAsync(const QString &path); +void showNotification(const QString& message, + const QString& from, + const QString& accountId, + const QString& convUid, + std::function<void()> const& onClicked); +QSize getRealSize(QScreen* screen); +void forceDeleteAsync(const QString& path); QString getChangeLog(); QString getProjectCredits(); void removeOldVersions(); @@ -90,8 +86,8 @@ static constexpr bool isBeta = true; static constexpr bool isBeta = false; #endif void cleanUpdateFiles(); -void checkForUpdates(bool withUI, QWidget *parent = nullptr); -void applyUpdates(bool updateToBeta, QWidget *parent = nullptr); +void checkForUpdates(bool withUI, QWidget* parent = nullptr); +void applyUpdates(bool updateToBeta, QWidget* parent = nullptr); /* * LRC helpers @@ -118,8 +114,8 @@ bool getReplyMessageBox(QWidget* widget, const QString& title, const QString& te static const QSize defaultAvatarSize {128, 128}; QString getContactImageString(const QString& accountId, const QString& uid); QImage getCirclePhoto(const QImage original, int sizePhoto); -QImage conversationPhoto(const QString &convUid, - const lrc::api::account::Info &accountInfo, +QImage conversationPhoto(const QString& convUid, + const lrc::api::account::Info& accountInfo, bool filtered = false); QColor getAvatarColor(const QString& canonicalUri); QImage fallbackAvatar(const QString& canonicalUriStr, -- GitLab