diff --git a/tests/assets/images/call_sign_ended.svg b/tests/assets/images/call_sign_ended.svg new file mode 100644 index 000000000..ddee9dc58 --- /dev/null +++ b/tests/assets/images/call_sign_ended.svg @@ -0,0 +1,13 @@ + + + + call_in_sign + Created with Sketch. + + + + + + + + diff --git a/tests/assets/languages/en.ts b/tests/assets/languages/en.ts index edc62bce0..ae4ec4710 100644 --- a/tests/assets/languages/en.ts +++ b/tests/assets/languages/en.ts @@ -40,15 +40,15 @@ resumeCall - + RESUME CALL transferCall - + TRANSFER CALL pauseCall - + PAUSE CALL diff --git a/tests/assets/languages/fr.ts b/tests/assets/languages/fr.ts index 612bdf29b..6b954c919 100644 --- a/tests/assets/languages/fr.ts +++ b/tests/assets/languages/fr.ts @@ -32,15 +32,15 @@ resumeCall - + REPRENDRE APPEL transferCall - + TRANSFERER APPEL pauseCall - + PAUSE diff --git a/tests/resources.qrc b/tests/resources.qrc index 398691972..e44b070ef 100644 --- a/tests/resources.qrc +++ b/tests/resources.qrc @@ -26,6 +26,7 @@ assets/images/call_quality_2.svg assets/images/call_quality_3.svg assets/images/call_sign_connected.svg + assets/images/call_sign_ended.svg assets/images/call_sign_incoming.svg assets/images/call_sign_outgoing.svg assets/images/call_sign_paused.svg diff --git a/tests/src/app/App.cpp b/tests/src/app/App.cpp index 3479084c3..62a00d9bd 100644 --- a/tests/src/app/App.cpp +++ b/tests/src/app/App.cpp @@ -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(); + if (!window) + qFatal("Unable to get calls window."); + + return window; +} + bool App::hasFocus () const { QQmlApplicationEngine &engine = const_cast(m_engine); const QQuickWindow *root = qobject_cast(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(); diff --git a/tests/src/app/App.hpp b/tests/src/app/App.hpp index 28a6fb6a2..e28dfddec 100644 --- a/tests/src/app/App.hpp +++ b/tests/src/app/App.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "../components/notifier/Notifier.hpp" @@ -25,6 +26,8 @@ public: return m_notifier; } + QQuickWindow *getCallsWindow () const; + bool hasFocus () const; Q_INVOKABLE QString locale () const { diff --git a/tests/src/components/calls/CallsListModel.cpp b/tests/src/components/calls/CallsListModel.cpp index 782b68138..62fe0b5f0 100644 --- a/tests/src/components/calls/CallsListModel.cpp +++ b/tests/src/components/calls/CallsListModel.cpp @@ -1,10 +1,14 @@ #include +#include #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::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) { + 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) { } void CallsListModel::removeCall (const shared_ptr &linphone_call) { - CallModel &call = linphone_call->getData("call-model"); + CallModel *call = &linphone_call->getData("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(); + } + ); } diff --git a/tests/src/components/chat/ChatModel.cpp b/tests/src/components/chat/ChatModel.cpp index 589977d35..66d578783 100644 --- a/tests/src/components/chat/ChatModel.cpp +++ b/tests/src/components/chat/ChatModel.cpp @@ -520,7 +520,7 @@ void ChatModel::removeEntry (ChatEntryData &pair) { } void ChatModel::insertMessageAtEnd (const shared_ptr &message) { - int row = rowCount(); + int row = m_entries.count(); beginInsertRows(QModelIndex(), row, row); diff --git a/tests/src/components/contacts/ContactsListModel.cpp b/tests/src/components/contacts/ContactsListModel.cpp index 1771ed0bd..a17efc9c7 100644 --- a/tests/src/components/contacts/ContactsListModel.cpp +++ b/tests/src/components/contacts/ContactsListModel.cpp @@ -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); diff --git a/tests/ui/modules/Common/BusyIndicator.qml b/tests/ui/modules/Common/BusyIndicator.qml index 8eabd3052..86cb30e80 100644 --- a/tests/ui/modules/Common/BusyIndicator.qml +++ b/tests/ui/modules/Common/BusyIndicator.qml @@ -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 + } + } + ] + } + } + } + } } diff --git a/tests/ui/modules/Common/Styles/BusyIndicatorStyle.qml b/tests/ui/modules/Common/Styles/BusyIndicatorStyle.qml index a76835e90..835c3c93a 100644 --- a/tests/ui/modules/Common/Styles/BusyIndicatorStyle.qml +++ b/tests/ui/modules/Common/Styles/BusyIndicatorStyle.qml @@ -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 } diff --git a/tests/ui/modules/Linphone/Calls/Calls.qml b/tests/ui/modules/Linphone/Calls/Calls.qml index 9e056e528..faf53ad86 100644 --- a/tests/ui/modules/Linphone/Calls/Calls.qml +++ b/tests/ui/modules/Linphone/Calls/Calls.qml @@ -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 : '' - } + function _getSignIcon (call) { + if (call) { + return 'call_sign_' + _mapStatusToParams[call.status].string + } - return '' - } + return '' + } - function _getParams (call) { - if (call) { - return _mapStatusToParams[call.status] - } - } + function _getParams (call) { + if (call) { + return _mapStatusToParams[call.status] + } + } - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- - boundsBehavior: Flickable.StopAtBounds - clip: true - spacing: 0 + boundsBehavior: Flickable.StopAtBounds + clip: true + spacing: 0 - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- - Component.onCompleted: { - _mapStatusToParams = {} + 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.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.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.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.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' - } - } + _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 + Component { + id: callAction - ActionButton { - icon: params.icon - iconSize: CallsStyle.entry.iconActionSize + ActionButton { + icon: params.icon + iconSize: CallsStyle.entry.iconActionSize - onClicked: params.handler(call) - } - } + onClicked: params.handler(call) + } + } - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- - Component { - id: callActions + Component { + id: callActions - ActionButton { - id: button + ActionButton { + id: button - icon: 'burger_menu' - iconSize: CallsStyle.entry.iconMenuSize + icon: calls.currentIndex === callId && call.status !== CallModel.CallStatusEnded + ? 'burger_menu_light' + : 'burger_menu' + iconSize: CallsStyle.entry.iconMenuSize - onClicked: menu.showMenu() + onClicked: menu.showMenu() - DropDownMenu { - id: menu + DropDownMenu { + id: menu - implicitWidth: actionMenu.width - launcher: button - relativeTo: callControls - relativeX: callControls.width + implicitWidth: actionMenu.width + launcher: button + relativeTo: callControls + relativeX: callControls.width - ActionMenu { - id: actionMenu + ActionMenu { + id: actionMenu - entryHeight: CallsStyle.entry.height - entryWidth: CallsStyle.entry.width + entryHeight: CallsStyle.entry.height + entryWidth: CallsStyle.entry.width - Repeater { - model: params.actions + Repeater { + model: params.actions - ActionMenuEntry { - entryName: modelData.name + ActionMenuEntry { + entryName: modelData.name - onClicked: { - menu.hideMenu() - params.actions[index].handler(call) - } - } - } - } - } - } - } + onClicked: { + menu.hideMenu() + params.actions[index].handler(call) + } + } + } + } + } + } + } - // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- - delegate: CallControls { - id: _callControls + delegate: CallControls { + id: _callControls - signIcon: _getSignIcon($call) - sipAddress: $call.sipAddress - width: parent.width + function useColorStatus () { + return calls.currentIndex === index && $call.status !== CallModel.CallStatusEnded + } - Loader { - property var call: $call - property var callControls: _callControls - property var params: _getParams($call) + 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 - anchors.centerIn: parent - sourceComponent: params.component - } - } + 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 + } + } + } } diff --git a/tests/ui/modules/Linphone/Styles/Calls/CallControlsStyle.qml b/tests/ui/modules/Linphone/Styles/Calls/CallControlsStyle.qml index 60516875a..0151f6c15 100644 --- a/tests/ui/modules/Linphone/Styles/Calls/CallControlsStyle.qml +++ b/tests/ui/modules/Linphone/Styles/Calls/CallControlsStyle.qml @@ -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 } diff --git a/tests/ui/modules/Linphone/Styles/Calls/CallsStyle.qml b/tests/ui/modules/Linphone/Styles/Calls/CallsStyle.qml index 24b9115f4..9124edff4 100644 --- a/tests/ui/modules/Linphone/Styles/Calls/CallsStyle.qml +++ b/tests/ui/modules/Linphone/Styles/Calls/CallsStyle.qml @@ -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 + } + } }