Commit 2b5cb6bc authored by Ronan's avatar Ronan
Browse files

feat(src/components/notifier/Notifier): supports messages & calls notifications

parent 40ee4028
<?xml version="1.0" encoding="UTF-8"?>
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 41 (35326) - http://www.bohemiancoding.com/sketch -->
<title>call_in_sign</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="call_in_sign">
<polygon fill="#FF5E00" points="0 0 40 0 0 40"></polygon>
<path d="M11.243716,8.71501414 L11.1871835,8.71501414 C8.47215269,8.71501414 5.21235367,10.3895588 4.11804501,12.8152126 L4.51732144,15.956733 L8.01512006,15.956733 L8.2626641,12.830113 L14.1683578,12.830113 L14.41529,15.956733 L17.9130886,15.956733 L18.3123651,12.8149535 C17.2174446,10.3886518 13.9583798,8.71501414 11.243716,8.71501414 Z" fill="#FFFFFF" transform="translate(11.215205, 12.335874) rotate(-135.000000) translate(-11.215205, -12.335874) "></path>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 41 (35326) - http://www.bohemiancoding.com/sketch -->
<title>call_in_sign</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="call_in_sign">
<polygon fill="#FF5E00" points="0 0 40 0 0 40"></polygon>
<path d="M11.243716,8.71501414 L11.1871835,8.71501414 C8.47215269,8.71501414 5.21235367,10.3895588 4.11804501,12.8152126 L4.51732144,15.956733 L8.01512006,15.956733 L8.2626641,12.830113 L14.1683578,12.830113 L14.41529,15.956733 L17.9130886,15.956733 L18.3123651,12.8149535 C17.2174446,10.3886518 13.9583798,8.71501414 11.243716,8.71501414 Z" fill="#FFFFFF" transform="translate(11.215205, 12.335874) rotate(-135.000000) translate(-11.215205, -12.335874) "></path>
</g>
</g>
</svg>
\ No newline at end of file
......@@ -78,6 +78,7 @@
<file>assets/images/edit_normal.svg</file>
<file>assets/images/edit_pressed.svg</file>
<file>assets/images/ended_call.svg</file>
<file>assets/images/file_sign.svg</file>
<file>assets/images/filter.svg</file>
<file>assets/images/fullscreen_hovered.svg</file>
<file>assets/images/fullscreen_normal.svg</file>
......@@ -101,6 +102,7 @@
<file>assets/images/led_red.svg</file>
<file>assets/images/led_white.svg</file>
<file>assets/images/linphone.png</file>
<file>assets/images/message_sign.svg</file>
<file>assets/images/micro_off_hovered.svg</file>
<file>assets/images/micro_off_normal.svg</file>
<file>assets/images/micro_off_pressed.svg</file>
......@@ -228,9 +230,10 @@
<file>ui/modules/Linphone/Contact/ContactDescription.qml</file>
<file>ui/modules/Linphone/Contact/Contact.qml</file>
<file>ui/modules/Linphone/Contact/MessagesCounter.qml</file>
<file>ui/modules/Linphone/Notifications/CallNotification.qml</file>
<file>ui/modules/Linphone/Notifications/Notification.qml</file>
<file>ui/modules/Linphone/Notifications/ReceivedMessageNotification.qml</file>
<file>ui/modules/Linphone/Notifications/NotificationReceivedCall.qml</file>
<file>ui/modules/Linphone/Notifications/NotificationReceivedFileMessage.qml</file>
<file>ui/modules/Linphone/Notifications/NotificationReceivedMessage.qml</file>
<file>ui/modules/Linphone/Presence/PresenceLevel.qml</file>
<file>ui/modules/Linphone/Presence/PresenceString.qml</file>
<file>ui/modules/Linphone/qmldir</file>
......@@ -243,7 +246,10 @@
<file>ui/modules/Linphone/Styles/Contact/ContactDescriptionStyle.qml</file>
<file>ui/modules/Linphone/Styles/Contact/ContactStyle.qml</file>
<file>ui/modules/Linphone/Styles/Contact/MessagesCounterStyle.qml</file>
<file>ui/modules/Linphone/Styles/NotificationStyle.qml</file>
<file>ui/modules/Linphone/Styles/Notifications/NotificationReceivedCallStyle.qml</file>
<file>ui/modules/Linphone/Styles/Notifications/NotificationReceivedFileMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/Notifications/NotificationReceivedMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/Notifications/NotificationStyle.qml</file>
<file>ui/modules/Linphone/Styles/Presence/PresenceStringStyle.qml</file>
<file>ui/modules/Linphone/Styles/qmldir</file>
<file>ui/modules/Linphone/Styles/SmartSearchBarStyle.qml</file>
......
......@@ -92,10 +92,13 @@ QQuickWindow *App::getCallsWindow () const {
return window;
}
bool App::hasFocus () const {
QQuickWindow *App::getMainWindow () const {
QQmlApplicationEngine &engine = const_cast<QQmlApplicationEngine &>(m_engine);
const QQuickWindow *root = qobject_cast<QQuickWindow *>(engine.rootObjects().at(0));
return !!root->activeFocusItem();
return qobject_cast<QQuickWindow *>(engine.rootObjects().at(0));
}
bool App::hasFocus () const {
return getMainWindow()->isActive() || getCallsWindow()->isActive();
}
// -----------------------------------------------------------------------------
......@@ -109,6 +112,9 @@ void App::initContentApp () {
registerTypes();
addContextProperties();
// Enable notifications.
m_notifier = new Notifier();
CoreManager::getInstance()->enableHandlers();
// Load main view.
......@@ -167,7 +173,7 @@ void App::registerTypes () {
qmlRegisterSingletonType<CallsListModel>(
"Linphone", 1, 0, "CallsListModel",
[](QQmlEngine *, QJSEngine *) -> QObject *{
return new CallsListModel();
return CoreManager::getInstance()->getCallsListModel();
}
);
......@@ -219,13 +225,10 @@ void App::addContextProperties () {
}
context->setContextProperty("CallsWindow", component.create());
m_notifier = new Notifier();
context->setContextProperty("Notifier", m_notifier);
}
void App::setTrayIcon () {
QQuickWindow *root = qobject_cast<QQuickWindow *>(m_engine.rootObjects().at(0));
QQuickWindow *root = getMainWindow();
QMenu *menu = new QMenu();
m_system_tray_icon = new QSystemTrayIcon(root);
......
......@@ -49,6 +49,7 @@ public:
}
QQuickWindow *getCallsWindow () const;
QQuickWindow *getMainWindow () const;
bool hasFocus () const;
......
......@@ -226,18 +226,15 @@ float CallModel::getQuality () const {
}
bool CallModel::getMicroMuted () const {
return m_micro_muted;
return !CoreManager::getInstance()->getCore()->micEnabled();
}
void CallModel::setMicroMuted (bool status) {
if (m_micro_muted != status) {
m_micro_muted = status;
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
if (m_micro_muted == core->micEnabled())
core->enableMic(!m_micro_muted);
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
emit microMutedChanged(m_micro_muted);
if (status != core->micEnabled()) {
core->enableMic(!status);
emit microMutedChanged(status);
}
}
......
......@@ -109,7 +109,6 @@ private:
bool getRecording () const;
bool m_micro_muted = false;
bool m_paused_by_remote = false;
bool m_paused_by_user = false;
bool m_recording = false;
......
......@@ -81,6 +81,16 @@ QVariant CallsListModel::data (const QModelIndex &index, int role) const {
return QVariant();
}
CallModel *CallsListModel::getCall (const shared_ptr<linphone::Call> &linphone_call) const {
auto it = find_if(
m_list.begin(), m_list.end(), [linphone_call](CallModel *call) {
return linphone_call == call->getLinphoneCall();
}
);
return it != m_list.end() ? *it : nullptr;
}
// -----------------------------------------------------------------------------
void CallsListModel::launchAudioCall (const QString &sip_uri) const {
......
......@@ -43,6 +43,8 @@ public:
QHash<int, QByteArray> roleNames () const override;
QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override;
CallModel *getCall (const std::shared_ptr<linphone::Call> &linphone_call) const;
Q_INVOKABLE void launchAudioCall (const QString &sip_uri) const;
Q_INVOKABLE void launchVideoCall (const QString &sip_uri) const;
......
#include <QOpenGLFunctions>
/*
* MSFunctions.cpp
* Copyright (C) 2017 Belledonne Communications, Grenoble, France
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Created on: February 9, 2017
* Author: Ronan Abhamon
*/
#include "MSFunctions.hpp"
// Do not include this header before `QOpenGLFunctions`!!!
#include <mediastreamer2/msogl.h>
#include "MSFunctions.hpp"
// =============================================================================
MSFunctions *MSFunctions::m_instance = nullptr;
......
/*
* MSFunctions.hpp
* Copyright (C) 2017 Belledonne Communications, Grenoble, France
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Created on: February 9, 2017
* Author: Ronan Abhamon
*/
#ifndef MS_FUNCTIONS_H_
#define MS_FUNCTIONS_H_
......
......@@ -30,6 +30,7 @@
#include <QTimer>
#include <QUuid>
#include "../../app/App.hpp"
#include "../../app/Paths.hpp"
#include "../../app/ThumbnailProvider.hpp"
#include "../../utils.hpp"
......@@ -150,9 +151,11 @@ private:
if (it == m_chat_model->m_entries.end())
return;
// File message downloaded.
if (state == linphone::ChatMessageStateFileTransferDone && !message->isOutgoing()) {
createThumbnail(message);
fillThumbnailProperty((*it).first, message);
App::getInstance()->getNotifier()->notifyReceivedFileMessage(message);
}
(*it).first["status"] = state;
......
......@@ -46,16 +46,19 @@ void CoreHandlers::onCallStateChanged (
const string &
) {
emit callStateChanged(call, state);
if (call->getState() == linphone::CallStateIncomingReceived)
App::getInstance()->getNotifier()->notifyReceivedCall(call);
}
void CoreHandlers::onMessageReceived (
const shared_ptr<linphone::Core> &,
const shared_ptr<linphone::ChatRoom> &room,
const shared_ptr<linphone::ChatRoom> &,
const shared_ptr<linphone::ChatMessage> &message
) {
emit messageReceived(message);
const App *app = App::getInstance();
if (!app->hasFocus())
app->getNotifier()->notifyReceivedMessage(10000, room, message);
app->getNotifier()->notifyReceivedMessage(message);
}
......@@ -49,6 +49,7 @@ void CoreManager::init () {
if (!m_instance) {
m_instance = new CoreManager();
m_instance->m_calls_list_model = new CallsListModel(m_instance);
m_instance->m_contacts_list_model = new ContactsListModel(m_instance);
m_instance->m_sip_addresses_model = new SipAddressesModel(m_instance);
......
......@@ -23,6 +23,7 @@
#ifndef CORE_MANAGER_H_
#define CORE_MANAGER_H_
#include "../calls/CallsListModel.hpp"
#include "../contacts/ContactsListModel.hpp"
#include "../sip-addresses/SipAddressesModel.hpp"
#include "CoreHandlers.hpp"
......@@ -51,6 +52,10 @@ public:
// Singleton models.
// ---------------------------------------------------------------------------
CallsListModel *getCallsListModel () const {
return m_calls_list_model;
}
ContactsListModel *getContactsListModel () const {
return m_contacts_list_model;
}
......@@ -83,6 +88,7 @@ private:
std::shared_ptr<linphone::Core> m_core;
std::shared_ptr<CoreHandlers> m_handlers;
CallsListModel *m_calls_list_model;
ContactsListModel *m_contacts_list_model;
SipAddressesModel *m_sip_addresses_model;
......
......@@ -20,23 +20,31 @@
* Author: Ronan Abhamon
*/
#include "../../app/App.hpp"
#include "Notifier.hpp"
#include <QQmlComponent>
#include <QQuickWindow>
#include <QtDebug>
#include <QTimer>
#include "../../app/App.hpp"
#include "../../utils.hpp"
#include "../core/CoreManager.hpp"
#include "Notifier.hpp"
// Notifications QML properties/methods.
#define NOTIFICATION_SHOW_METHOD_NAME "show"
#define NOTIFICATION_HEIGHT_PROPERTY "notificationHeight"
#define NOTIFICATION_OFFSET_PROPERTY_NAME "notificationOffset"
#define NOTIFICATION_PROPERTY_DATA "notificationData"
#define NOTIFICATION_PROPERTY_HEIGHT "notificationHeight"
#define NOTIFICATION_PROPERTY_OFFSET "notificationOffset"
#define QML_CALL_NOTIFICATION_PATH "qrc:/ui/modules/Linphone/Notifications/CallNotification.qml"
#define QML_MESSAGE_RECEIVED_NOTIFICATION_PATH "qrc:/ui/modules/Linphone/Notifications/ReceivedMessageNotification.qml"
#define QML_NOTIFICATION_PATH_RECEIVED_MESSAGE "qrc:/ui/modules/Linphone/Notifications/NotificationReceivedMessage.qml"
#define QML_NOTIFICATION_PATH_RECEIVED_FILE_MESSAGE "qrc:/ui/modules/Linphone/Notifications/NotificationReceivedFileMessage.qml"
#define QML_NOTIFICATION_PATH_RECEIVED_CALL "qrc:/ui/modules/Linphone/Notifications/NotificationReceivedCall.qml"
#define NOTIFICATION_TIMEOUT_RECEIVED_MESSAGE 10000
#define NOTIFICATION_TIMEOUT_RECEIVED_FILE_MESSAGE 10000
#define NOTIFICATION_TIMEOUT_RECEIVED_CALL 10000
// Arbitrary hardcoded values.
#define NOTIFICATION_SPACING 10
......@@ -77,22 +85,22 @@ Notifier::Notifier (QObject *parent) :
QQmlEngine *engine = App::getInstance()->getEngine();
// Build components.
m_components[Notifier::Call] = new QQmlComponent(engine, QUrl(QML_CALL_NOTIFICATION_PATH));
m_components[Notifier::MessageReceived] = new QQmlComponent(engine, QUrl(QML_MESSAGE_RECEIVED_NOTIFICATION_PATH));
m_components[Notifier::MessageReceived] = new QQmlComponent(engine, QUrl(QML_NOTIFICATION_PATH_RECEIVED_MESSAGE));
m_components[Notifier::FileMessageReceived] = new QQmlComponent(engine, QUrl(QML_NOTIFICATION_PATH_RECEIVED_FILE_MESSAGE));
m_components[Notifier::CallReceived] = new QQmlComponent(engine, QUrl(QML_NOTIFICATION_PATH_RECEIVED_CALL));
// Check errors.
for (int i = 0; i < Notifier::MaxNbTypes; ++i) {
QQmlComponent *component = m_components[i];
if (component->isError()) {
qWarning() << QStringLiteral("Errors found in `Notification` component %1:").arg(i) <<
component->errors();
qWarning() << QStringLiteral("Errors found in `Notification` component %1:").arg(i) << component->errors();
abort();
}
}
}
Notifier::~Notifier () {
for (int i = 0; i < Notifier::MaxNbTypes; i++)
for (int i = 0; i < Notifier::MaxNbTypes; ++i)
delete m_components[i];
}
......@@ -110,9 +118,9 @@ QObject *Notifier::createNotification (Notifier::NotificationType type) {
// Create instance and set attributes.
QObject *object = m_components[type]->create();
int offset = getNotificationSize(*object, NOTIFICATION_HEIGHT_PROPERTY);
int offset = getNotificationSize(*object, NOTIFICATION_PROPERTY_HEIGHT);
if (offset == -1 || !::setProperty(*object, NOTIFICATION_OFFSET_PROPERTY_NAME, m_offset)) {
if (offset == -1 || !::setProperty(*object, NOTIFICATION_PROPERTY_OFFSET, m_offset)) {
delete object;
m_mutex.unlock();
return nullptr;
......@@ -149,7 +157,7 @@ void Notifier::showNotification (QObject *notification, int timeout) {
qInfo() << "Update notifications counter, hidden notification detected.";
if (visible)
qFatal("A notification cannot be visible twice!");
qWarning("A notification cannot be visible twice!");
m_mutex.lock();
......@@ -172,20 +180,41 @@ void Notifier::showNotification (QObject *notification, int timeout) {
// -----------------------------------------------------------------------------
void Notifier::notifyReceivedMessage (
int timeout,
const shared_ptr<linphone::ChatRoom> &room,
const shared_ptr<linphone::ChatMessage> &message
) {
QObject *object = createNotification(Notifier::MessageReceived);
void Notifier::notifyReceivedMessage (const shared_ptr<linphone::ChatMessage> &message) {
QObject *notification = createNotification(Notifier::MessageReceived);
if (!notification)
return;
QVariantMap map;
map["message"] = ::Utils::linphoneStringToQString(message->getText());
map["sipAddress"] = ::Utils::linphoneStringToQString(message->getFromAddress()->asStringUriOnly());
map["window"].setValue(App::getInstance()->getMainWindow());
if (object)
showNotification(object, timeout);
::setProperty(*notification, NOTIFICATION_PROPERTY_DATA, map);
showNotification(notification, NOTIFICATION_TIMEOUT_RECEIVED_MESSAGE);
}
void Notifier::showCallMessage (int timeout, const QString &) {
QObject *object = createNotification(Notifier::Call);
void Notifier::notifyReceivedFileMessage (const shared_ptr<linphone::ChatMessage> &message) {
QObject *notification = createNotification(Notifier::FileMessageReceived);
if (!notification)
return;
QVariantMap map;
map["fileUri"] = ::Utils::linphoneStringToQString(message->getFileTransferFilepath());
map["fileSize"] = static_cast<quint64>(message->getFileTransferInformation()->getSize());
::setProperty(*notification, NOTIFICATION_PROPERTY_DATA, map);
showNotification(notification, NOTIFICATION_TIMEOUT_RECEIVED_FILE_MESSAGE);
}
void Notifier::notifyReceivedCall (const shared_ptr<linphone::Call> &call) {
QObject *notification = createNotification(Notifier::CallReceived);
if (!notification)
return;
QVariantMap map;
map["call"].setValue(CoreManager::getInstance()->getCallsListModel()->getCall(call));
if (object)
showNotification(object, timeout);
::setProperty(*notification, NOTIFICATION_PROPERTY_DATA, map);
showNotification(notification, NOTIFICATION_TIMEOUT_RECEIVED_CALL);
}
......@@ -40,19 +40,15 @@ public:
~Notifier ();
enum NotificationType {
Call,
MessageReceived,
FileMessageReceived,
CallReceived,
MaxNbTypes
};
void notifyReceivedMessage (
int timeout,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::shared_ptr<linphone::ChatMessage> &message
);
// TODO
void showCallMessage (int timeout, const QString &);
void notifyReceivedMessage (const std::shared_ptr<linphone::ChatMessage> &message);
void notifyReceivedFileMessage (const std::shared_ptr<linphone::ChatMessage> &message);
void notifyReceivedCall (const std::shared_ptr<linphone::Call> &call);
private:
QObject *createNotification (NotificationType type);
......
......@@ -28,6 +28,8 @@
int main (int argc, char *argv[]) {
Logger::init();
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
// Force shader version 2.0.
QSurfaceFormat fmt;
fmt.setVersion(2, 0);
......
......@@ -77,7 +77,10 @@ Item {
to: 'opened'
ScriptAction {
script: popup.showNormal()
script: {
popup.showNormal()
popup.requestActivate()
}
}
NumberAnimation {
......
......@@ -23,8 +23,10 @@ Item {
iconSize: MessagesCounterStyle.iconSize.message
Icon {
anchors.horizontalCenter: parent.right
anchors.verticalCenter: parent.bottom
anchors {
horizontalCenter: parent.right
verticalCenter: parent.bottom
}
icon: 'chat_amount'
iconSize: MessagesCounterStyle.iconSize.amount
......
import QtQuick 2.7
// =============================================================================
Notification {
Rectangle {
color: 'red'
width: 200
height: 100
}
}
......@@ -15,15 +15,19 @@ DesktopPopup {
// ---------------------------------------------------------------------------
property int notificationOffset: 0
property alias notificationHeight: notification.popupHeight
property int notificationOffset: 0
property var notificationData: null
readonly property var window: _window
property var _window
// ---------------------------------------------------------------------------
flags: Qt.Popup
Component.onCompleted: {
var window = data[0]
var window = _window = data[0]
Utils.assert(
Utils.qmlTypeof(window, 'QQuickWindowQmlImpl'), true,
......@@ -45,7 +49,7 @@ DesktopPopup {
}
var height = screen.desktopAvailableHeight - window.height
return height - notificationOffset % height
return height - (notificationOffset % height)
})
}
}
......@@ -11,6 +11,10 @@ TestCase {
id: notification
}
function test_notificationDataProperty () {
compare(Utils.isObject(notification.notificationData), true)
}