Add an incoming talk call notification to the desktop client

Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>

Co-authored-by: Camila <hello@camila.codes>
This commit is contained in:
Claudio Cambra 2022-04-22 00:32:01 +02:00 committed by Matthieu Gallien
parent 4459b92f80
commit 3f5243aaee
21 changed files with 555 additions and 34 deletions

View file

@ -29,5 +29,6 @@
<file>src/gui/tray/ActivityItemActions.qml</file>
<file>src/gui/tray/ActivityItemContent.qml</file>
<file>src/gui/tray/TalkReplyTextField.qml</file>
<file>src/gui/tray/CallNotificationDialog.qml</file>
</qresource>
</RCC>

View file

@ -146,6 +146,10 @@ GeneralSettings::GeneralSettings(QWidget *parent)
this, &GeneralSettings::slotToggleOptionalServerNotifications);
_ui->serverNotificationsCheckBox->setToolTip(tr("Server notifications that require attention."));
connect(_ui->callNotificationsCheckBox, &QAbstractButton::toggled,
this, &GeneralSettings::slotToggleCallNotifications);
_ui->callNotificationsCheckBox->setToolTip(tr("Show call notification dialogs."));
connect(_ui->showInExplorerNavigationPaneCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotShowInExplorerNavigationPane);
// Rename 'Explorer' appropriately on non-Windows
@ -247,6 +251,8 @@ void GeneralSettings::loadMiscSettings()
ConfigFile cfgFile;
_ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons());
_ui->serverNotificationsCheckBox->setChecked(cfgFile.optionalServerNotifications());
_ui->callNotificationsCheckBox->setEnabled(_ui->serverNotificationsCheckBox->isEnabled());
_ui->callNotificationsCheckBox->setChecked(cfgFile.showCallNotifications());
_ui->showInExplorerNavigationPaneCheckBox->setChecked(cfgFile.showInExplorerNavigationPane());
_ui->crashreporterCheckBox->setChecked(cfgFile.crashReporter());
auto newFolderLimit = cfgFile.newBigFolderSizeLimit();
@ -428,6 +434,13 @@ void GeneralSettings::slotToggleOptionalServerNotifications(bool enable)
{
ConfigFile cfgFile;
cfgFile.setOptionalServerNotifications(enable);
_ui->callNotificationsCheckBox->setEnabled(enable);
}
void GeneralSettings::slotToggleCallNotifications(bool enable)
{
ConfigFile cfgFile;
cfgFile.setShowCallNotifications(enable);
}
void GeneralSettings::slotShowInExplorerNavigationPane(bool checked)

View file

@ -48,6 +48,7 @@ private slots:
void saveMiscSettings();
void slotToggleLaunchOnStartup(bool);
void slotToggleOptionalServerNotifications(bool);
void slotToggleCallNotifications(bool);
void slotShowInExplorerNavigationPane(bool);
void slotIgnoreFilesEditor();
void slotCreateDebugArchive();

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>554</width>
<height>558</height>
<width>556</width>
<height>563</height>
</rect>
</property>
<property name="windowTitle">
@ -90,6 +90,13 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="callNotificationsCheckBox">
<property name="text">
<string>Show Call Notifications</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View file

@ -29,6 +29,7 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QVariantMap>
#include <QScreen>
#include <QMenu>
#include <QGuiApplication>
@ -159,6 +160,44 @@ void Systray::create()
}
}
void Systray::createCallDialog(const Activity &callNotification)
{
qCDebug(lcSystray) << "Starting a new call dialog for notification with id: " << callNotification._id << "with text: " << callNotification._subject;
if (_trayEngine && !_callsAlreadyNotified.contains(callNotification._id)) {
const QVariantMap talkNotificationData{
{"conversationToken", callNotification._talkNotificationData.conversationToken},
{"messageId", callNotification._talkNotificationData.messageId},
{"messageSent", callNotification._talkNotificationData.messageSent},
{"userAvatar", callNotification._talkNotificationData.userAvatar},
};
QVariantList links;
for(const auto &link : callNotification._links) {
links.append(QVariantMap{
{"imageSource", link._imageSource},
{"imageSourceHovered", link._imageSourceHovered},
{"label", link._label},
{"link", link._link},
{"primary", link._primary},
{"verb", link._verb},
});
}
const QVariantMap initialProperties{
{"talkNotificationData", talkNotificationData},
{"links", links},
{"subject", callNotification._subject},
{"link", callNotification._link},
};
const auto callDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/CallNotificationDialog.qml"));
callDialog->createWithInitialProperties(initialProperties);
_callsAlreadyNotified.insert(callNotification._id);
}
}
void Systray::slotNewUserSelected()
{
if (_trayEngine) {
@ -308,6 +347,30 @@ void Systray::forceWindowInit(QQuickWindow *window) const
#endif
}
void Systray::positionNotificationWindow(QQuickWindow *window) const
{
if (!useNormalWindow()) {
window->setScreen(currentScreen());
if(geometry().isValid()) {
// On OSes where the QSystemTrayIcon geometry method isn't borked, we can actually figure out where the system tray is located
// We can therefore use our normal routines
const auto position = computeNotificationPosition(window->width(), window->height());
window->setPosition(position);
} else if (QProcessEnvironment::systemEnvironment().contains(QStringLiteral("XDG_CURRENT_DESKTOP")) &&
(QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP")).contains(QStringLiteral("GNOME")))) {
// We can safely hardcode the top-right position for the notification when running GNOME
const auto position = computeNotificationPosition(window->width(), window->height(), 0, NotificationPosition::TopRight);
window->setPosition(position);
} else {
// For other DEs we play it safe and place the notification in the centre of the screen
const QPoint windowAdjustment(window->geometry().width() / 2, window->geometry().height() / 2);
const auto position = currentScreen()->geometry().center();// - windowAdjustment;
window->setPosition(position);
}
// TODO: Get actual notification positions for the DEs
}
}
QScreen *Systray::currentScreen() const
{
const auto screen = QGuiApplication::screenAt(QCursor::pos());
@ -446,8 +509,85 @@ QPoint Systray::computeWindowReferencePoint() const
Q_UNREACHABLE();
}
QPoint Systray::computeNotificationReferencePoint(int spacing, NotificationPosition position) const
{
auto trayIconCenter = calcTrayIconCenter();
auto taskbarScreenEdge = taskbarOrientation();
auto taskbarRect = taskbarGeometry();
const auto screenRect = currentScreenRect();
if(position == NotificationPosition::TopLeft) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(0, 0);
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::TopRight) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(screenRect.width(), 0);
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::BottomLeft) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(0, screenRect.height());
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::BottomRight) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(screenRect.width(), screenRect.height());
taskbarRect = QRect(0, 0, screenRect.width(), 32);
}
qCDebug(lcSystray) << "screenRect:" << screenRect;
qCDebug(lcSystray) << "taskbarRect:" << taskbarRect;
qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "trayIconCenter:" << trayIconCenter;
switch(taskbarScreenEdge) {
case TaskBarPosition::Bottom:
return {
trayIconCenter.x() < screenRect.center().x() ? screenRect.left() + spacing : screenRect.right() - spacing,
screenRect.bottom() - taskbarRect.height() - spacing
};
case TaskBarPosition::Left:
return {
screenRect.left() + taskbarRect.width() + spacing,
trayIconCenter.y() < screenRect.center().y() ? screenRect.top() + spacing : screenRect.bottom() - spacing
};
case TaskBarPosition::Top:
return {
trayIconCenter.x() < screenRect.center().x() ? screenRect.left() + spacing : screenRect.right() - spacing,
screenRect.top() + taskbarRect.height() + spacing
};
case TaskBarPosition::Right:
return {
screenRect.right() - taskbarRect.width() - spacing,
trayIconCenter.y() < screenRect.center().y() ? screenRect.top() + spacing : screenRect.bottom() - spacing
};
}
Q_UNREACHABLE();
}
QRect Systray::computeWindowRect(int spacing, const QPoint &topLeft, const QPoint &bottomRight) const
{
const auto screenRect = currentScreenRect();
const auto rect = QRect(topLeft, bottomRight);
auto offset = QPoint();
if (rect.left() < screenRect.left()) {
offset.setX(screenRect.left() - rect.left() + spacing);
} else if (rect.right() > screenRect.right()) {
offset.setX(screenRect.right() - rect.right() - spacing);
}
if (rect.top() < screenRect.top()) {
offset.setY(screenRect.top() - rect.top() + spacing);
} else if (rect.bottom() > screenRect.bottom()) {
offset.setY(screenRect.bottom() - rect.bottom() - spacing);
}
return rect.translated(offset);
}
QPoint Systray::computeWindowPosition(int width, int height) const
{
constexpr auto spacing = 4;
const auto referencePoint = computeWindowReferencePoint();
const auto taskbarScreenEdge = taskbarOrientation();
@ -467,24 +607,7 @@ QPoint Systray::computeWindowPosition(int width, int height) const
Q_UNREACHABLE();
}();
const auto bottomRight = topLeft + QPoint(width, height);
const auto windowRect = [=]() {
const auto rect = QRect(topLeft, bottomRight);
auto offset = QPoint();
if (rect.left() < screenRect.left()) {
offset.setX(screenRect.left() - rect.left() + 4);
} else if (rect.right() > screenRect.right()) {
offset.setX(screenRect.right() - rect.right() - 4);
}
if (rect.top() < screenRect.top()) {
offset.setY(screenRect.top() - rect.top() + 4);
} else if (rect.bottom() > screenRect.bottom()) {
offset.setY(screenRect.bottom() - rect.bottom() - 4);
}
return rect.translated(offset);
}();
const auto windowRect = computeWindowRect(spacing, topLeft, bottomRight);
qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "screenRect:" << screenRect;
@ -494,17 +617,64 @@ QPoint Systray::computeWindowPosition(int width, int height) const
return windowRect.topLeft();
}
QPoint Systray::computeNotificationPosition(int width, int height, int spacing, NotificationPosition position) const
{
const auto referencePoint = computeNotificationReferencePoint(spacing, position);
auto trayIconCenter = calcTrayIconCenter();
auto taskbarScreenEdge = taskbarOrientation();
const auto screenRect = currentScreenRect();
if(position == NotificationPosition::TopLeft) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(0, 0);
} else if(position == NotificationPosition::TopRight) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(screenRect.width(), 0);
} else if(position == NotificationPosition::BottomLeft) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(0, screenRect.height());
} else if(position == NotificationPosition::BottomRight) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(screenRect.width(), screenRect.height());
}
const auto topLeft = [=]() {
switch(taskbarScreenEdge) {
case TaskBarPosition::Bottom:
return trayIconCenter.x() < screenRect.center().x() ? referencePoint - QPoint(0, height) : referencePoint - QPoint(width, height);
case TaskBarPosition::Left:
return trayIconCenter.y() < screenRect.center().y() ? referencePoint : referencePoint - QPoint(0, height);
case TaskBarPosition::Top:
return trayIconCenter.x() < screenRect.center().x() ? referencePoint : referencePoint - QPoint(width, 0);
case TaskBarPosition::Right:
return trayIconCenter.y() < screenRect.center().y() ? referencePoint - QPoint(width, 0) : QPoint(width, height);
}
Q_UNREACHABLE();
}();
const auto bottomRight = topLeft + QPoint(width, height);
const auto windowRect = computeWindowRect(spacing, topLeft, bottomRight);
qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "screenRect:" << screenRect;
qCDebug(lcSystray) << "windowRect (reference)" << QRect(topLeft, bottomRight);
qCDebug(lcSystray) << "windowRect (adjusted)" << windowRect;
qCDebug(lcSystray) << "referencePoint" << referencePoint;
return windowRect.topLeft();
}
QPoint Systray::calcTrayIconCenter() const
{
// QSystemTrayIcon::geometry() is broken for ages on most Linux DEs (invalid geometry returned)
// thus we can use this only for Windows and macOS
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
auto trayIconCenter = geometry().center();
return trayIconCenter;
#else
if(geometry().isValid()) {
// QSystemTrayIcon::geometry() is broken for ages on most Linux DEs (invalid geometry returned)
// thus we can use this only for Windows and macOS
auto trayIconCenter = geometry().center();
return trayIconCenter;
}
// On Linux, fall back to mouse position (assuming tray icon is activated by mouse click)
return QCursor::pos(currentScreen());
#endif
}
AccessManagerFactory::AccessManagerFactory()

View file

@ -64,6 +64,9 @@ public:
enum class TaskBarPosition { Bottom, Left, Top, Right };
Q_ENUM(TaskBarPosition);
enum class NotificationPosition { Default, TopLeft, TopRight, BottomLeft, BottomRight };
Q_ENUM(NotificationPosition);
void setTrayEngine(QQmlApplicationEngine *trayEngine);
void create();
@ -72,6 +75,7 @@ public:
bool isOpen();
QString windowTitle() const;
bool useNormalWindow() const;
void createCallDialog(const Activity &callNotification);
Q_INVOKABLE void pauseResumeSync();
Q_INVOKABLE bool syncIsPaused();
@ -79,6 +83,7 @@ public:
Q_INVOKABLE void setClosed();
Q_INVOKABLE void positionWindow(QQuickWindow *window) const;
Q_INVOKABLE void forceWindowInit(QQuickWindow *window) const;
Q_INVOKABLE void positionNotificationWindow(QQuickWindow *window) const;
signals:
void currentUserChanged();
@ -110,16 +115,21 @@ private:
QScreen *currentScreen() const;
QRect currentScreenRect() const;
QPoint computeWindowReferencePoint() const;
QPoint computeNotificationReferencePoint(int spacing = 20, NotificationPosition position = NotificationPosition::Default) const;
QPoint calcTrayIconCenter() const;
TaskBarPosition taskbarOrientation() const;
QRect taskbarGeometry() const;
QRect computeWindowRect(int spacing, const QPoint &topLeft, const QPoint &bottomRight) const;
QPoint computeWindowPosition(int width, int height) const;
QPoint computeNotificationPosition(int width, int height, int spacing = 20, NotificationPosition position = NotificationPosition::Default) const;
bool _isOpen = false;
bool _syncIsPaused = true;
QPointer<QQmlApplicationEngine> _trayEngine;
AccessManagerFactory _accessManagerFactory;
QSet<qlonglong> _callsAlreadyNotified;
};
} // namespace OCC

View file

@ -15,6 +15,7 @@ MouseArea {
readonly property bool isChatActivity: model.objectType === "chat" || model.objectType === "room" || model.objectType === "call"
readonly property bool isTalkReplyPossible: model.conversationToken !== ""
property bool isTalkReplyOptionVisible: model.messageSent !== ""
readonly property bool isCallActivity: model.objectType === "call"
signal fileActivityButtonClicked(string absolutePath)

View file

@ -0,0 +1,244 @@
import QtQuick 2.15
import QtQuick.Window 2.15
import Style 1.0
import com.nextcloud.desktopclient 1.0
import QtQuick.Layouts 1.2
import QtMultimedia 5.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15
Window {
id: root
color: "transparent"
flags: Qt.Dialog | Qt.FramelessWindowHint
readonly property int windowSpacing: 10
readonly property int windowWidth: 240
readonly property string svgImage: "image://svgimage-custom-color/%1.svg" + "/"
readonly property string talkIcon: svgImage.arg("wizard-talk")
readonly property string deleteIcon: svgImage.arg("delete")
// We set talkNotificationData, subject, and links properties in C++
property var talkNotificationData: ({})
property string subject: ""
property var links: []
property string link: ""
property string ringtonePath: "qrc:///client/theme/call-notification.wav"
readonly property bool usingUserAvatar: root.talkNotificationData.userAvatar !== ""
function closeNotification() {
ringSound.stop();
root.close();
}
width: root.windowWidth
height: rootBackground.height
Component.onCompleted: {
Systray.forceWindowInit(root);
Systray.positionNotificationWindow(root);
root.show();
root.raise();
root.requestActivate();
ringSound.play();
}
Audio {
id: ringSound
source: root.ringtonePath
loops: 9 // about 45 seconds of audio playing
audioRole: Audio.RingtoneRole
onStopped: root.closeNotification()
}
Rectangle {
id: rootBackground
width: parent.width
height: contentLayout.height + (root.windowSpacing * 2)
radius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius
color: Style.backgroundColor
border.width: Style.trayWindowBorderWidth
border.color: Style.menuBorder
clip: true
Loader {
id: backgroundLoader
anchors.fill: parent
active: root.usingUserAvatar
sourceComponent: Item {
anchors.fill: parent
Image {
id: backgroundImage
anchors.fill: parent
cache: true
source: root.talkNotificationData.userAvatar
fillMode: Image.PreserveAspectCrop
smooth: true
visible: false
}
FastBlur {
id: backgroundBlur
anchors.fill: backgroundImage
source: backgroundImage
radius: 50
visible: false
}
Rectangle {
id: backgroundMask
color: "white"
radius: rootBackground.radius
anchors.fill: backgroundImage
visible: false
width: backgroundImage.paintedWidth
height: backgroundImage.paintedHeight
}
OpacityMask {
id: backgroundOpacityMask
anchors.fill: backgroundBlur
source: backgroundBlur
maskSource: backgroundMask
}
Rectangle {
id: darkenerRect
anchors.fill: parent
color: "black"
opacity: 0.4
visible: backgroundOpacityMask.visible
radius: rootBackground.radius
}
}
}
ColumnLayout {
id: contentLayout
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: root.windowSpacing
spacing: root.windowSpacing
Item {
width: Style.accountAvatarSize
height: Style.accountAvatarSize
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Image {
id: callerAvatar
anchors.fill: parent
cache: true
source: root.usingUserAvatar ? root.talkNotificationData.userAvatar :
Theme.darkMode ? root.talkIcon + Style.ncTextColor : root.talkIcon + Style.ncBlue
sourceSize.width: Style.accountAvatarSize
sourceSize.height: Style.accountAvatarSize
visible: !root.usingUserAvatar
Accessible.role: Accessible.Indicator
Accessible.name: qsTr("Talk notification caller avatar")
}
Rectangle {
id: mask
color: "white"
radius: width * 0.5
anchors.fill: callerAvatar
visible: false
width: callerAvatar.paintedWidth
height: callerAvatar.paintedHeight
}
OpacityMask {
anchors.fill: callerAvatar
source: callerAvatar
maskSource: mask
visible: root.usingUserAvatar
}
}
Label {
id: message
text: root.subject
color: root.usingUserAvatar ? "white" : Style.ncTextColor
font.pixelSize: Style.topLinePixelSize
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.fillWidth: true
}
RowLayout {
spacing: root.windowSpacing / 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Repeater {
id: linksRepeater
model: root.links
CustomButton {
id: answerCall
readonly property string verb: modelData.verb
readonly property bool isAnswerCallButton: verb === "WEB"
visible: isAnswerCallButton
text: modelData.label
bold: true
bgColor: Style.ncBlue
bgOpacity: 0.8
textColor: Style.ncHeaderTextColor
imageSource: root.talkIcon + Style.ncHeaderTextColor
imageSourceHover: root.talkIcon + Style.ncHeaderTextColor
Layout.fillWidth: true
Layout.preferredHeight: Style.callNotificationPrimaryButtonMinHeight
onClicked: {
Qt.openUrlExternally(root.link);
root.closeNotification();
}
Accessible.role: Accessible.Button
Accessible.name: qsTr("Answer Talk call notification")
Accessible.onPressAction: answerCall.clicked()
}
}
CustomButton {
id: declineCall
text: qsTr("Decline")
bold: true
bgColor: Style.errorBoxBackgroundColor
bgOpacity: 0.8
textColor: Style.ncHeaderTextColor
imageSource: root.deleteIcon + "white"
imageSourceHover: root.deleteIcon + "white"
Layout.fillWidth: true
Layout.preferredHeight: Style.callNotificationPrimaryButtonMinHeight
onClicked: root.closeNotification()
Accessible.role: Accessible.Button
Accessible.name: qsTr("Decline Talk call notification")
Accessible.onPressAction: declineCall.clicked()
}
}
}
}
}

View file

@ -8,19 +8,22 @@ Button {
property string imageSource: ""
property string imageSourceHover: ""
property Image iconItem: icon
property string toolTipText: ""
property color textColor
property color textColorHovered
property color textColor: Style.ncTextColor
property color textColorHovered: textColor
property color bgColor: "transparent"
property bool bold: false
property real bgOpacity: 0.3
background: Rectangle {
color: root.bgColor
opacity: parent.hovered ? 1.0 : 0.3
opacity: parent.hovered ? 1.0 : bgOpacity
radius: width / 2
}
@ -49,6 +52,7 @@ Button {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
source: root.hovered ? root.imageSourceHover : root.imageSource
fillMode: Image.PreserveAspectFit
}
Label {

View file

@ -33,7 +33,7 @@ namespace OCC {
class ActivityLink
{
Q_GADGET
Q_PROPERTY(QString imageSource MEMBER _imageSource)
Q_PROPERTY(QString imageSourceHovered MEMBER _imageSourceHovered)
Q_PROPERTY(QString label MEMBER _label)
@ -115,6 +115,7 @@ public:
QString conversationToken;
QString messageId;
QString messageSent;
QString userAvatar;
};
Type _type;

View file

@ -80,6 +80,7 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
roles[TalkNotificationConversationTokenRole] = "conversationToken";
roles[TalkNotificationMessageIdRole] = "messageId";
roles[TalkNotificationMessageSentRole] = "messageSent";
roles[TalkNotificationUserAvatarRole] = "userAvatar";
return roles;
}
@ -332,6 +333,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return a._talkNotificationData.messageId;
case TalkNotificationMessageSentRole:
return replyMessageSent(a);
case TalkNotificationUserAvatarRole:
return a._talkNotificationData.userAvatar;
default:
return QVariant();
}

View file

@ -71,6 +71,7 @@ public:
TalkNotificationConversationTokenRole,
TalkNotificationMessageIdRole,
TalkNotificationMessageSentRole,
TalkNotificationUserAvatarRole,
};
Q_ENUM(DataRole)

View file

@ -90,13 +90,30 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
auto *ai = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
ActivityList list;
ActivityList callList;
foreach (auto element, notifies) {
auto json = element.toObject();
auto a = Activity::fromActivityJson(json, ai->account());
a._type = Activity::NotificationType;
a._id = json.value("notification_id").toInt();
if(json.contains("subjectRichParameters")) {
const auto richParams = json.value("subjectRichParameters").toObject();
for(const auto &key : richParams.keys()) {
const auto parameterJsonObject = richParams.value(key).toObject();
a._subjectRichParameters.insert(key, Activity::RichSubjectParameter{
parameterJsonObject.value(QStringLiteral("type")).toString(),
parameterJsonObject.value(QStringLiteral("id")).toString(),
parameterJsonObject.value(QStringLiteral("name")).toString(),
QString(),
QUrl()
});
}
}
// 2 cases to consider:
// 1. server == 24 & has Talk: object_type is chat/call/room & object_id contains conversationToken/messageId
// 2. server < 24 & has Talk: object_type is chat/call/room & object_id contains _only_ conversationToken
@ -116,7 +133,16 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
al._primary = true;
a._links.insert(0, al);
if(a._subjectRichParameters.contains("user")) {
a._talkNotificationData.userAvatar = ai->account()->url().toString() + QStringLiteral("/index.php/avatar/") + a._subjectRichParameters["user"].id + QStringLiteral("/128");
}
list.append(a);
// We want to serve incoming call dialogs to the user for calls that
if(a._objectType == "call" && a._dateTime.secsTo(QDateTime::currentDateTime()) < 120) {
callList.append(a);
}
}
a._status = 0;
@ -145,6 +171,7 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
list.append(a);
}
emit newNotificationList(list);
emit newIncomingCallsList(callList);
deleteLater();
}

View file

@ -17,6 +17,7 @@ public:
signals:
void newNotificationList(ActivityList);
void newIncomingCallsList(ActivityList);
public slots:
void slotFetchNotifications();

View file

@ -12,6 +12,7 @@
#include "logger.h"
#include "guiutility.h"
#include "syncfileitem.h"
#include "systray.h"
#include "tray/activitylistmodel.h"
#include "tray/notificationcache.h"
#include "tray/unifiedsearchresultslistmodel.h"
@ -123,6 +124,18 @@ void User::slotBuildNotificationDisplay(const ActivityList &list)
}
}
void User::slotBuildIncomingCallDialogs(const ActivityList &list)
{
const auto systray = Systray::instance();
const ConfigFile cfg;
if(systray && cfg.showCallNotifications()) {
for(const auto &activity : list) {
systray->createCallDialog(activity);
}
}
}
void User::setNotificationRefreshInterval(std::chrono::milliseconds interval)
{
if (!checkPushNotificationsAreReady()) {
@ -264,6 +277,8 @@ void User::slotRefreshNotifications()
auto *snh = new ServerNotificationHandler(_account.data());
connect(snh, &ServerNotificationHandler::newNotificationList,
this, &User::slotBuildNotificationDisplay);
connect(snh, &ServerNotificationHandler::newIncomingCallsList,
this, &User::slotBuildIncomingCallDialogs);
snh->slotFetchNotifications();
} else {
@ -906,7 +921,7 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent)
endInsertRows();
ConfigFile cfg;
_users.last()->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
u->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
emit newUserSelected();
}
}

View file

@ -100,6 +100,7 @@ public slots:
void slotNotifyServerFinished(const QString &reply, int replyCode);
void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
void slotBuildNotificationDisplay(const ActivityList &list);
void slotBuildIncomingCallDialogs(const ActivityList &list);
void slotRefreshNotifications();
void slotRefreshActivities();
void slotRefresh();

View file

@ -68,6 +68,7 @@ static const char monoIconsC[] = "monoIcons";
static const char promptDeleteC[] = "promptDeleteAllFiles";
static const char crashReporterC[] = "crashReporter";
static const char optionalServerNotificationsC[] = "optionalServerNotifications";
static const char showCallNotificationsC[] = "showCallNotifications";
static const char showInExplorerNavigationPaneC[] = "showInExplorerNavigationPane";
static const char skipUpdateCheckC[] = "skipUpdateCheck";
static const char autoUpdateCheckC[] = "autoUpdateCheck";
@ -189,6 +190,19 @@ bool ConfigFile::optionalServerNotifications() const
return settings.value(QLatin1String(optionalServerNotificationsC), true).toBool();
}
bool ConfigFile::showCallNotifications() const
{
const QSettings settings(configFile(), QSettings::IniFormat);
return settings.value(QLatin1String(showCallNotificationsC), true).toBool() && optionalServerNotifications();
}
void ConfigFile::setShowCallNotifications(bool show)
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setValue(QLatin1String(showCallNotificationsC), show);
settings.sync();
}
bool ConfigFile::showInExplorerNavigationPane() const
{
const bool defaultValue =
@ -557,7 +571,7 @@ chrono::milliseconds ConfigFile::notificationRefreshInterval(const QString &conn
QSettings settings(configFile(), QSettings::IniFormat);
settings.beginGroup(con);
auto defaultInterval = chrono::minutes(5);
const auto defaultInterval = chrono::minutes(1);
auto interval = millisecondsValue(settings, notificationRefreshIntervalC, defaultInterval);
if (interval < chrono::minutes(1)) {
qCWarning(lcConfigFile) << "Notification refresh interval smaller than one minute, setting to one minute";

View file

@ -152,6 +152,9 @@ public:
bool optionalServerNotifications() const;
void setOptionalServerNotifications(bool show);
bool showCallNotifications() const;
void setShowCallNotifications(bool show);
bool showInExplorerNavigationPane() const;
void setShowInExplorerNavigationPane(bool show);

View file

@ -232,5 +232,6 @@
<file>theme/black/edit.svg</file>
<file>theme/delete.svg</file>
<file>theme/send.svg</file>
<file>theme/call-notification.wav</file>
</qresource>
</RCC>

View file

@ -30,7 +30,7 @@ QtObject {
property int trayWindowWidth: variableSize(400)
property int trayWindowHeight: variableSize(510)
property int trayWindowRadius: 10
property int trayWindowBorderWidth: 1
property int trayWindowBorderWidth: variableSize(1)
property int trayWindowHeaderHeight: variableSize(60)
property int trayHorizontalMargin: 10
property int trayListItemIconSize: accountAvatarSize
@ -68,6 +68,9 @@ QtObject {
property int activityItemActionPrimaryButtonMinWidth: 100
property int activityItemActionSecondaryButtonMinWidth: 80
property int callNotificationPrimaryButtonMinWidth: 100
property int callNotificationPrimaryButtonMinHeight: 40
property int roundButtonBackgroundVerticalMargins: 10
property int roundedButtonBackgroundVerticalMargins: 5

BIN
theme/call-notification.wav Normal file

Binary file not shown.