Skip to content

Commit

Permalink
Close call notifications when the call has been joined by the user, o…
Browse files Browse the repository at this point in the history
…r the call has ended

Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
  • Loading branch information
claucambra committed Jul 1, 2022
1 parent 974939e commit a504dbf
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ set(client_SRCS
application.cpp
invalidfilenamedialog.h
invalidfilenamedialog.cpp
callstatechecker.h
callstatechecker.cpp
conflictdialog.h
conflictdialog.cpp
conflictsolver.h
Expand Down
184 changes: 184 additions & 0 deletions src/gui/callstatechecker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 2 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.
*/

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

#include "callstatechecker.h"
#include "account.h"

namespace OCC {

Q_LOGGING_CATEGORY(lcCallStateChecker, "nextcloud.gui.callstatechecker", QtInfoMsg)

constexpr int successStatusCode = 200;

CallStateChecker::CallStateChecker(QObject *parent)
: QObject(parent)
{
setup();
}

void CallStateChecker::setup()
{
_notificationTimer.setSingleShot(true);
_notificationTimer.setInterval(60 * 1000);
connect(&_notificationTimer, &QTimer::timeout, this, &CallStateChecker::slotNotificationTimerElapsed);

_statusCheckTimer.setInterval(2 * 1000);
connect(&_statusCheckTimer, &QTimer::timeout, this, &CallStateChecker::slotStatusCheckTimerElapsed);
}

QString CallStateChecker::token() const
{
return _token;
}

void CallStateChecker::setToken(const QString &token)
{
_token = token;
Q_EMIT tokenChanged();
reset();
}

AccountState* CallStateChecker::accountState() const
{
return _accountState;
}

void CallStateChecker::setAccountState(AccountState *state)
{
_accountState = state;
Q_EMIT accountStateChanged();
reset();
}

bool CallStateChecker::checking() const
{
return _checking;
}

void CallStateChecker::setChecking(const bool checking)
{
if(checking) {
qCInfo(lcCallStateChecker) << "Starting to check state of call with token:" << _token;
_notificationTimer.start();
_statusCheckTimer.start();
} else {
qCInfo(lcCallStateChecker) << "Stopping checking of call state for call with token:" << _token;
_notificationTimer.stop();
_statusCheckTimer.stop();
_stateCheckJob.clear();
}

_checking = checking;
Q_EMIT checkingChanged();
}

void CallStateChecker::reset()
{
qCInfo(lcCallStateChecker, "Resetting call check");
setChecking(false);
setChecking(true);
}

void CallStateChecker::slotNotificationTimerElapsed()
{
qCInfo(lcCallStateChecker) << "Notification timer elapsed, stopping call checking of call with token:" << _token;
setChecking(false);
Q_EMIT stopNotifying();
}

void CallStateChecker::slotStatusCheckTimerElapsed()
{
// Don't run check if another check is still ongoing
if (_stateCheckJob) {
return;
}

startCallStateCheck();
}

bool CallStateChecker::isAccountServerVersion22OrLater() const
{
if(!_accountState || !_accountState->account()) {
return false;
}

const auto accountNcVersion = _accountState->account()->serverVersionInt();
constexpr auto ncVersion22 = OCC::Account::makeServerVersion(22, 0, 0);

return accountNcVersion >= ncVersion22;
}

void CallStateChecker::startCallStateCheck()
{
// check connectivity and credentials
if (!(_accountState && _accountState->isConnected() &&
_accountState->account() && _accountState->account()->credentials() &&
_accountState->account()->credentials()->ready())) {
qCInfo(lcCallStateChecker, "Could not connect, can't check call state.");
return;
}

// Check for token
if(_token.isEmpty()) {
qCInfo(lcCallStateChecker, "No call token set, can't check without it.");
return;
}

qCInfo(lcCallStateChecker) << "Checking state of call with token: " << _token;

const auto spreedPath = QStringLiteral("ocs/v2.php/apps/spreed/");
const auto callApiPath = isAccountServerVersion22OrLater() ? QStringLiteral("api/v4/call/") : QStringLiteral("api/v1/call/");
const QString callPath = spreedPath + callApiPath + _token; // Make sure it's a QString and not a QStringBuilder

_stateCheckJob = new JsonApiJob(_accountState->account(), callPath, this);
connect(_stateCheckJob.data(), &JsonApiJob::jsonReceived, this, &CallStateChecker::slotCallStateReceived);

_stateCheckJob->setVerb(JsonApiJob::Verb::Get);
_stateCheckJob->start();
}

void CallStateChecker::slotCallStateReceived(const QJsonDocument &json, const int statusCode)
{
if (statusCode != successStatusCode) {
qCInfo(lcCallStateChecker) << "Failed to retrieve call state data. Server returned status code: " << statusCode;
return;
}

const auto participantsJsonArray = json.object().value("ocs").toObject().value("data").toArray();

if (participantsJsonArray.empty()) {
qCInfo(lcCallStateChecker, "Call has no participants and has therefore been abandoned.");
Q_EMIT stopNotifying();
setChecking(false);
return;
}

for (const auto &participant : participantsJsonArray) {
const auto participantDataObject = participant.toObject();
const auto participantId = isAccountServerVersion22OrLater() ? participantDataObject.value("actorId").toString() : participantDataObject.value("userId").toString();

if (participantId == _accountState->account()->davUser()) {
qCInfo(lcCallStateChecker, "Found own account ID in participants list, meaning call has been joined.");
Q_EMIT stopNotifying();
setChecking(false);
return;
}
}
}

}
70 changes: 70 additions & 0 deletions src/gui/callstatechecker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 2 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.
*/

#pragma once

#include <QObject>
#include <QTimer>

#include "networkjobs.h"
#include "accountstate.h"

namespace OCC {

class CallStateChecker : public QObject
{
Q_OBJECT
Q_PROPERTY(QString token READ token WRITE setToken NOTIFY tokenChanged)
Q_PROPERTY(AccountState* accountState READ accountState WRITE setAccountState NOTIFY accountStateChanged)
Q_PROPERTY(bool checking READ checking WRITE setChecking NOTIFY checkingChanged)

public:
explicit CallStateChecker(QObject *parent = nullptr);

QString token() const;
AccountState* accountState() const;
bool checking() const;

signals:
void tokenChanged();
void accountStateChanged();
void checkingChanged();

void stopNotifying();

public slots:
void setToken(const QString &token);
void setAccountState(OCC::AccountState *accountState);
void setChecking(const bool checking);

private slots:
void slotStatusCheckTimerElapsed();
void slotNotificationTimerElapsed();
void slotCallStateReceived(const QJsonDocument &json, const int statusCode);
void reset();

private:
void setup();
void startCallStateCheck();
bool isAccountServerVersion22OrLater() const;

AccountState *_accountState = nullptr;
QString _token;
QTimer _statusCheckTimer; // How often we check the status of the call
QTimer _notificationTimer; // How long we present the call notification for
QPointer<JsonApiJob> _stateCheckJob;
bool _checking = false;
};

}
5 changes: 4 additions & 1 deletion src/gui/systray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "tray/trayimageprovider.h"
#include "configfile.h"
#include "accessmanager.h"
#include "callstatechecker.h"

#include <QCursor>
#include <QGuiApplication>
Expand Down Expand Up @@ -98,6 +99,7 @@ Systray::Systray()
);

qmlRegisterType<WheelHandler>("com.nextcloud.desktopclient", 1, 0, "WheelHandler");
qmlRegisterType<CallStateChecker>("com.nextcloud.desktopclient", 1, 0, "CallStateChecker");

#if defined(Q_OS_MACOS) && defined(BUILD_OWNCLOUD_OSX_BUNDLE)
setUserNotificationCenterDelegate();
Expand Down Expand Up @@ -183,7 +185,7 @@ void Systray::setupContextMenu()
});
}

void Systray::createCallDialog(const Activity &callNotification)
void Systray::createCallDialog(const Activity &callNotification, const AccountStatePtr accountState)
{
qCDebug(lcSystray) << "Starting a new call dialog for notification with id: " << callNotification._id << "with text: " << callNotification._subject;

Expand All @@ -208,6 +210,7 @@ void Systray::createCallDialog(const Activity &callNotification)
}

const QVariantMap initialProperties{
{"accountState", QVariant::fromValue(accountState.data())},
{"talkNotificationData", talkNotificationData},
{"links", links},
{"subject", callNotification._subject},
Expand Down
2 changes: 1 addition & 1 deletion src/gui/systray.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class Systray
bool isOpen();
QString windowTitle() const;
bool useNormalWindow() const;
void createCallDialog(const Activity &callNotification);
void createCallDialog(const Activity &callNotification, const AccountStatePtr accountState);

Q_INVOKABLE void pauseResumeSync();
Q_INVOKABLE bool syncIsPaused();
Expand Down
29 changes: 27 additions & 2 deletions src/gui/tray/CallNotificationDialog.qml
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 by Camila Ayres <camila@nextcloud.com>
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 2 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.
*/

import QtQuick 2.15
import QtQuick.Window 2.15
import Style 1.0
Expand All @@ -20,6 +35,7 @@ Window {
readonly property string deleteIcon: svgImage.arg("delete")

// We set talkNotificationData, subject, and links properties in C++
property var accountState: ({})
property var talkNotificationData: ({})
property string subject: ""
property var links: []
Expand All @@ -29,6 +45,7 @@ Window {
readonly property bool usingUserAvatar: root.talkNotificationData.userAvatar !== ""

function closeNotification() {
callStateChecker.checking = false;
ringSound.stop();
root.close();
}
Expand All @@ -45,14 +62,22 @@ Window {
root.requestActivate();

ringSound.play();
}
callStateChecker.checking = true;
}

CallStateChecker {
id: callStateChecker
token: root.talkNotificationData.conversationToken
accountState: root.accountState

onStopNotifying: root.closeNotification()
}

Audio {
id: ringSound
source: root.ringtonePath
loops: 9 // about 45 seconds of audio playing
audioRole: Audio.RingtoneRole
onStopped: root.closeNotification()
}

Rectangle {
Expand Down
2 changes: 1 addition & 1 deletion src/gui/tray/usermodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ void User::slotBuildIncomingCallDialogs(const ActivityList &list)

if(systray) {
for(const auto &activity : list) {
systray->createCallDialog(activity);
systray->createCallDialog(activity, _account);
}
}
}
Expand Down
5 changes: 0 additions & 5 deletions src/libsync/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,11 +657,6 @@ int Account::serverVersionInt() const
components.value(2).toInt());
}

int Account::makeServerVersion(int majorVersion, int minorVersion, int patchVersion)
{
return (majorVersion << 16) + (minorVersion << 8) + patchVersion;
}

bool Account::serverVersionUnsupported() const
{
if (serverVersionInt() == 0) {
Expand Down
5 changes: 4 additions & 1 deletion src/libsync/account.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,10 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
*/
int serverVersionInt() const;

static int makeServerVersion(int majorVersion, int minorVersion, int patchVersion);
static constexpr int makeServerVersion(const int majorVersion, const int minorVersion, const int patchVersion) {
return (majorVersion << 16) + (minorVersion << 8) + patchVersion;
};

void setServerVersion(const QString &version);

/** Whether the server is too old.
Expand Down

0 comments on commit a504dbf

Please sign in to comment.