Commit 90c35eb2 authored by Ronan Abhamon's avatar Ronan Abhamon

feat(app): calls in progress

parent af60c28e
<?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="#DC4100" 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>
......@@ -40,15 +40,15 @@
</message>
<message>
<source>resumeCall</source>
<translation type="unfinished"></translation>
<translation>RESUME CALL</translation>
</message>
<message>
<source>transferCall</source>
<translation type="unfinished"></translation>
<translation>TRANSFER CALL</translation>
</message>
<message>
<source>pauseCall</source>
<translation type="unfinished"></translation>
<translation>PAUSE CALL</translation>
</message>
</context>
<context>
......
......@@ -32,15 +32,15 @@
</message>
<message>
<source>resumeCall</source>
<translation type="unfinished"></translation>
<translation>REPRENDRE APPEL</translation>
</message>
<message>
<source>transferCall</source>
<translation type="unfinished"></translation>
<translation>TRANSFERER APPEL</translation>
</message>
<message>
<source>pauseCall</source>
<translation type="unfinished"></translation>
<translation>PAUSE</translation>
</message>
</context>
<context>
......
......@@ -26,6 +26,7 @@
<file>assets/images/call_quality_2.svg</file>
<file>assets/images/call_quality_3.svg</file>
<file>assets/images/call_sign_connected.svg</file>
<file>assets/images/call_sign_ended.svg</file>
<file>assets/images/call_sign_incoming.svg</file>
<file>assets/images/call_sign_outgoing.svg</file>
<file>assets/images/call_sign_paused.svg</file>
......
......@@ -62,6 +62,14 @@ App::App (int &argc, char **argv) : QApplication(argc, argv) {
// -----------------------------------------------------------------------------
QQuickWindow *App::getCallsWindow () const {
QQuickWindow *window = m_engine.rootContext()->contextProperty("CallsWindow").value<QQuickWindow *>();
if (!window)
qFatal("Unable to get calls window.");
return window;
}
bool App::hasFocus () const {
QQmlApplicationEngine &engine = const_cast<QQmlApplicationEngine &>(m_engine);
const QQuickWindow *root = qobject_cast<QQuickWindow *>(engine.rootObjects().at(0));
......@@ -182,7 +190,6 @@ void App::addContextProperties () {
qInfo() << "Adding context properties...";
QQmlContext *context = m_engine.rootContext();
// TODO: Avoid context properties. Use qmlRegister...
QQmlComponent component(&m_engine, QUrl(QML_VIEW_CALL_WINDOW));
if (component.isError()) {
qWarning() << component.errors();
......
......@@ -4,6 +4,7 @@
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlFileSelector>
#include <QQuickWindow>
#include <QSystemTrayIcon>
#include "../components/notifier/Notifier.hpp"
......@@ -25,6 +26,8 @@ public:
return m_notifier;
}
QQuickWindow *getCallsWindow () const;
bool hasFocus () const;
Q_INVOKABLE QString locale () const {
......
#include <QDebug>
#include <QTimer>
#include "../../app/App.hpp"
#include "../core/CoreManager.hpp"
#include "CallsListModel.hpp"
/* Delay before removing call in ms. */
#define DELAY_BEFORE_REMOVE_CALL 3000
using namespace std;
// =============================================================================
......@@ -16,17 +20,16 @@ CallsListModel::CallsListModel (QObject *parent) : QAbstractListModel(parent) {
this, [this](const shared_ptr<linphone::Call> &linphone_call, linphone::CallState state) {
switch (state) {
case linphone::CallStateIncomingReceived:
addCall(linphone_call);
break;
case linphone::CallStateOutgoingInit:
addCall(linphone_call);
break;
case linphone::CallStateEnd:
case linphone::CallStateError:
removeCall(linphone_call);
break;
default:
default:
break;
}
}
......@@ -80,11 +83,13 @@ bool CallsListModel::removeRows (int row, int count, const QModelIndex &parent)
// -----------------------------------------------------------------------------
void CallsListModel::addCall (const shared_ptr<linphone::Call> &linphone_call) {
App::getInstance()->getCallsWindow()->show();
CallModel *call = new CallModel(linphone_call);
App::getInstance()->getEngine()->setObjectOwnership(call, QQmlEngine::CppOwnership);
linphone_call->setData("call-model", *call);
int row = rowCount();
int row = m_list.count();
beginInsertRows(QModelIndex(), row, row);
m_list << call;
......@@ -92,12 +97,20 @@ void CallsListModel::addCall (const shared_ptr<linphone::Call> &linphone_call) {
}
void CallsListModel::removeCall (const shared_ptr<linphone::Call> &linphone_call) {
CallModel &call = linphone_call->getData<CallModel>("call-model");
CallModel *call = &linphone_call->getData<CallModel>("call-model");
linphone_call->unsetData("call-model");
qInfo() << "Removing call:" << &call;
// TODO: It will be (maybe) necessary to use a single scheduled function in the future.
QTimer::singleShot(
DELAY_BEFORE_REMOVE_CALL, this, [this, call]() {
qInfo() << "Removing call:" << call;
int index = m_list.indexOf(call);
if (index == -1 || !removeRow(index))
qWarning() << "Unable to remove call:" << call;
int index = m_list.indexOf(&call);
if (index == -1 || !removeRow(index))
qWarning() << "Unable to remove call:" << &call;
if (m_list.empty())
App::getInstance()->getCallsWindow()->close();
}
);
}
......@@ -520,7 +520,7 @@ void ChatModel::removeEntry (ChatEntryData &pair) {
}
void ChatModel::insertMessageAtEnd (const shared_ptr<linphone::ChatMessage> &message) {
int row = rowCount();
int row = m_entries.count();
beginInsertRows(QModelIndex(), row, row);
......
......@@ -92,7 +92,7 @@ ContactModel *ContactsListModel::addContact (VcardModel *vcard) {
return nullptr;
}
int row = rowCount();
int row = m_list.count();
beginInsertRows(QModelIndex(), row, row);
addContact(contact);
......
......@@ -6,75 +6,75 @@ import Common.Styles 1.0
// =============================================================================
BusyIndicator {
id: busyIndicator
id: busyIndicator
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
readonly property int _rotation: 360
readonly property int _size: width < height ? width : height
readonly property int _rotation: 360
readonly property int _size: width < height ? width : height
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
contentItem: Item {
x: parent.width / 2 - width / 2
y: parent.height / 2 - height / 2
contentItem: Item {
x: parent.width / 2 - width / 2
y: parent.height / 2 - height / 2
height: _size
width: _size
height: _size
width: _size
Item {
id: item
Item {
id: item
height: parent.height
width: parent.width
height: parent.height
width: parent.width
// -----------------------------------------------------------------------
// Animation.
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Animation.
// -----------------------------------------------------------------------
RotationAnimator {
duration: BusyIndicatorStyle.duration
loops: Animation.Infinite
running: busyIndicator.visible && busyIndicator.running
target: item
RotationAnimator {
duration: BusyIndicatorStyle.duration
loops: Animation.Infinite
running: busyIndicator.visible && busyIndicator.running
target: item
from: 0
to: busyIndicator._rotation
}
from: 0
to: busyIndicator._rotation
}
// -----------------------------------------------------------------------
// Items to draw.
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Items to draw.
// -----------------------------------------------------------------------
Repeater {
id: repeater
Repeater {
id: repeater
model: BusyIndicatorStyle.nSpheres
model: BusyIndicatorStyle.nSpheres
Rectangle {
x: item.width / 2 - width / 2
y: item.height / 2 - height / 2
Rectangle {
x: item.width / 2 - width / 2
y: item.height / 2 - height / 2
height: item.height / 3
width: item.width / 3
height: item.height / 3
width: item.width / 3
color: BusyIndicatorStyle.color
radius: (width > height ? width : height) / 2
color: BusyIndicatorStyle.color
radius: (width > height ? width : height) / 2
transform: [
Translate {
y: busyIndicator._size / 2
},
Rotation {
angle: index / repeater.count * busyIndicator._rotation
origin {
x: width / 2
y: height / 2
}
}
]
}
}
}
}
transform: [
Translate {
y: busyIndicator._size / 2
},
Rotation {
angle: index / repeater.count * busyIndicator._rotation
origin {
x: width / 2
y: height / 2
}
}
]
}
}
}
}
}
......@@ -6,7 +6,7 @@ import Common 1.0
// =============================================================================
QtObject {
property color color: Colors.i
property int duration: 1250
property int nSpheres: 6
property color color: Colors.i
property int duration: 1250
property int nSpheres: 6
}
......@@ -7,172 +7,207 @@ import Linphone.Styles 1.0
// =============================================================================
ListView {
id: calls
id: calls
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
property var _mapStatusToParams
property var _mapStatusToParams
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
signal entrySelected (var entry)
// ---------------------------------------------------------------------------
function _getSignIcon (call) {
if (call) {
var string = _mapStatusToParams[call.status].string
return string ? 'call_sign_' + string : ''
}
return ''
}
function _getParams (call) {
if (call) {
return _mapStatusToParams[call.status]
}
}
// ---------------------------------------------------------------------------
boundsBehavior: Flickable.StopAtBounds
clip: true
spacing: 0
// ---------------------------------------------------------------------------
Component.onCompleted: {
_mapStatusToParams = {}
_mapStatusToParams[CallModel.CallStatusConnected] = {
actions: [{
name: qsTr('resumeCall'),
handler: (function (call) { call.pausedByUser = false })
}, {
name: qsTr('transferCall'),
handler: (function (call) { call.transferCall() })
}, {
name: qsTr('terminateCall'),
handler: (function (call) { call.terminateCall() })
}],
component: callActions,
string: 'connected'
}
_mapStatusToParams[CallModel.CallStatusEnded] = {}
_mapStatusToParams[CallModel.CallStatusIncoming] = {
actions: [{
name: qsTr('acceptAudioCall'),
handler: (function (call) { call.acceptAudioCall() })
}, {
name: qsTr('acceptVideoCall'),
handler: (function (call) { call.acceptVideoCall() })
}, {
name: qsTr('terminateCall'),
handler: (function (call) { call.terminateCall() })
}],
component: callActions,
string: 'incoming'
}
_mapStatusToParams[CallModel.CallStatusOutgoing] = {
component: callAction,
handler: (function (call) { call.terminateCall() }),
icon: 'hangup',
string: 'outgoing'
}
_mapStatusToParams[CallModel.CallStatusPaused] = {
actions: [{
name: qsTr('pauseCall'),
handler: (function (call) { call.pausedByUser = true })
}, {
name: qsTr('transferCall'),
handler: (function (call) { call.transferCall() })
}, {
name: qsTr('terminateCall'),
handler: (function (call) { call.terminateCall() })
}],
component: callActions,
string: 'paused'
}
}
// ---------------------------------------------------------------------------
Component {
id: callAction
ActionButton {
icon: params.icon
iconSize: CallsStyle.entry.iconActionSize
onClicked: params.handler(call)
}
}
// ---------------------------------------------------------------------------
Component {
id: callActions
ActionButton {
id: button
icon: 'burger_menu'
iconSize: CallsStyle.entry.iconMenuSize
onClicked: menu.showMenu()
DropDownMenu {
id: menu
implicitWidth: actionMenu.width
launcher: button
relativeTo: callControls
relativeX: callControls.width
ActionMenu {
id: actionMenu
entryHeight: CallsStyle.entry.height
entryWidth: CallsStyle.entry.width
Repeater {
model: params.actions
ActionMenuEntry {
entryName: modelData.name
onClicked: {
menu.hideMenu()
params.actions[index].handler(call)
}
}
}
}
}
}
}
// ---------------------------------------------------------------------------
delegate: CallControls {
id: _callControls
signIcon: _getSignIcon($call)
sipAddress: $call.sipAddress
width: parent.width
Loader {
property var call: $call
property var callControls: _callControls
property var params: _getParams($call)
anchors.centerIn: parent
sourceComponent: params.component
}
}
// ---------------------------------------------------------------------------
function _getSignIcon (call) {
if (call) {
return 'call_sign_' + _mapStatusToParams[call.status].string
}
return ''
}
function _getParams (call) {
if (call) {
return _mapStatusToParams[call.status]
}
}
// ---------------------------------------------------------------------------
boundsBehavior: Flickable.StopAtBounds
clip: true
spacing: 0
// ---------------------------------------------------------------------------
Component.onCompleted: {
_mapStatusToParams = {}
_mapStatusToParams[CallModel.CallStatusConnected] = {
actions: [{
name: qsTr('resumeCall'),
handler: (function (call) { call.pausedByUser = false })
}, {
name: qsTr('transferCall'),
handler: (function (call) { call.transferCall() })
}, {
name: qsTr('terminateCall'),
handler: (function (call) { call.terminateCall() })
}],
component: callActions,
string: 'connected'
}
_mapStatusToParams[CallModel.CallStatusEnded] = {
string: 'ended'
}
_mapStatusToParams[CallModel.CallStatusIncoming] = {
actions: [{
name: qsTr('acceptAudioCall'),
handler: (function (call) { call.acceptAudioCall() })
}, {
name: qsTr('acceptVideoCall'),
handler: (function (call) { call.acceptVideoCall() })
}, {
name: qsTr('terminateCall'),
handler: (function (call) { call.terminateCall() })
}],
component: callActions,
string: 'incoming'
}
_mapStatusToParams[CallModel.CallStatusOutgoing] = {
component: callAction,
handler: (function (call) { call.terminateCall() }),
icon: 'hangup',
string: 'outgoing'
}
_mapStatusToParams[CallModel.CallStatusPaused] = {
actions: [{
name: qsTr('pauseCall'),
handler: (function (call) { call.pausedByUser = true })
}, {
name: qsTr('transferCall'),
handler: (function (call) { call.transferCall() })
}, {
name: qsTr('terminateCall'),
handler: (function (call) { call.terminateCall() })
}],
component: callActions,
string: 'paused'
}
}
// ---------------------------------------------------------------------------
Component {
id: callAction
ActionButton {
icon: params.icon
iconSize: CallsStyle.entry.iconActionSize
onClicked: params.handler(call)
}
}
// ---------------------------------------------------------------------------
Component {
id: callActions
ActionButton {
id: button
icon: calls.currentIndex === callId && call.status !== CallModel.CallStatusEnded
? 'burger_menu_light'
: 'burger_menu'
iconSize: CallsStyle.entry.iconMenuSize
onClicked: menu.showMenu()
DropDownMenu {
id: menu
implicitWidth: actionMenu.width
launcher: button
relativeTo: callControls
relativeX: callControls.width
ActionMenu {
id: actionMenu
entryHeight: CallsStyle.entry.height
entryWidth: CallsStyle.entry.width
Repeater {
model: params.actions
ActionMenuEntry {
entryName: modelData.name
onClicked: {
menu.hideMenu()
params.actions[index].handler(call)
}
}
}
}
}
}
}
// ---------------------------------------------------------------------------
delegate: CallControls {
id: _callControls
function useColorStatus () {
return calls.currentIndex === index && $call.status !== CallModel.CallStatusEnded
}
color: useColorStatus()
? CallsStyle.entry.color.selected
: CallsStyle.entry.color.normal
sipAddressColor: useColorStatus()
? CallsStyle.entry.sipAddressColor.selected
: CallsStyle.entry.sipAddressColor.normal
usernameColor: useColorStatus()
? CallsStyle.entry.usernameColor.selected
: CallsStyle.entry.usernameColor.normal
signIcon: _getSignIcon($call)
sipAddress: $call.sipAddress
width: parent.width
Loader {
property int callId: index
property var call: $call
property var callControls: _callControls
property var params: _getParams($call)
anchors.centerIn: parent
sourceComponent: params.component
}
SequentialAnimation on color {
loops: CallsStyle.entry.endCallAnimation.loops
running: $call.status === CallModel.CallStatusEnded
ColorAnimation {
duration: CallsStyle.entry.endCallAnimation.duration
from: CallsStyle.entry.color.normal
to: CallsStyle.entry.endCallAnimation.blinkColor
}
ColorAnimation {
duration: CallsStyle.entry.endCallAnimation.duration
from: CallsStyle.entry.endCallAnimation.blinkColor
to: CallsStyle.entry.color.normal
}
}
}
}
......@@ -6,10 +6,10 @@ import Common 1.0
// =============================================================================
QtObject {
property color color: Colors.e
property int height: 60
property int leftMargin: 12
property int rightMargin: 12
property int signSize: 40
property int width: 240
property color color: Colors.e
property int height: 60
property int leftMargin: 12
property int rightMargin: 12
property int signSize: 40
property int width: 240
}
......@@ -6,10 +6,31 @@ import Common 1.0
// =============================================================================
QtObject {
property QtObject entry: QtObject {
property int iconActionSize: 30
property int iconMenuSize: 17
property int height: 30
property int width: 200
}
property QtObject entry: QtObject {
property int iconActionSize: 30
property int iconMenuSize: 17
property int height: 30
property int width: 200
property QtObject color: QtObject {
property color normal: Colors.e
property color selected: Colors.j
}
property QtObject endCallAnimation: QtObject {
property color blinkColor: Colors.i
property int duration: 300
property int loops: 3
}
property QtObject sipAddressColor: QtObject {
property color normal: Colors.w
property color selected: Colors.k
}
property QtObject usernameColor: QtObject {
property color normal: Colors.j
property color selected: Colors.k
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment