mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-21 12:35:52 +03:00
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:
parent
4459b92f80
commit
3f5243aaee
21 changed files with 555 additions and 34 deletions
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -48,6 +48,7 @@ private slots:
|
|||
void saveMiscSettings();
|
||||
void slotToggleLaunchOnStartup(bool);
|
||||
void slotToggleOptionalServerNotifications(bool);
|
||||
void slotToggleCallNotifications(bool);
|
||||
void slotShowInExplorerNavigationPane(bool);
|
||||
void slotIgnoreFilesEditor();
|
||||
void slotCreateDebugArchive();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -65,6 +65,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();
|
||||
void showMessage(const QString &title, const QString &message, MessageIcon icon = Information);
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
244
src/gui/tray/CallNotificationDialog.qml
Normal file
244
src/gui/tray/CallNotificationDialog.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -115,6 +115,7 @@ public:
|
|||
QString conversationToken;
|
||||
QString messageId;
|
||||
QString messageSent;
|
||||
QString userAvatar;
|
||||
};
|
||||
|
||||
Type _type;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ public:
|
|||
TalkNotificationConversationTokenRole,
|
||||
TalkNotificationMessageIdRole,
|
||||
TalkNotificationMessageSentRole,
|
||||
TalkNotificationUserAvatarRole,
|
||||
};
|
||||
Q_ENUM(DataRole)
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ public:
|
|||
|
||||
signals:
|
||||
void newNotificationList(ActivityList);
|
||||
void newIncomingCallsList(ActivityList);
|
||||
|
||||
public slots:
|
||||
void slotFetchNotifications();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
BIN
theme/call-notification.wav
Normal file
Binary file not shown.
Loading…
Reference in a new issue