mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-21 13:48:08 +00:00
feat(app): calls in progress (fix crash, remove CaterpillarAnimation)
This commit is contained in:
parent
3632197aec
commit
4effc5dc6e
18 changed files with 122 additions and 171 deletions
|
|
@ -143,9 +143,8 @@
|
|||
<file>assets/images/video_call_hovered.svg</file>
|
||||
<file>assets/images/video_call_normal.svg</file>
|
||||
<file>assets/images/video_call_pressed.svg</file>
|
||||
<file>ui/modules/Common/Animations/CaterpillarAnimation.qml</file>
|
||||
<file>ui/modules/Common/Animations/BusyIndicator.qml</file>
|
||||
<file>ui/modules/Common/Borders.qml</file>
|
||||
<file>ui/modules/Common/BusyIndicator.qml</file>
|
||||
<file>ui/modules/Common/Collapse.qml</file>
|
||||
<file>ui/modules/Common/Colors.qml</file>
|
||||
<file>ui/modules/Common/Constants.qml</file>
|
||||
|
|
|
|||
|
|
@ -123,3 +123,11 @@ void CallModel::setPausedByUser (bool status) {
|
|||
emit pausedByUserChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool CallModel::getVideoOutputEnabled () const {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void CallModel::setVideoOutputEnabled (bool status) {
|
||||
// TODO
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class CallModel : public QObject {
|
|||
Q_PROPERTY(bool isOutgoing READ isOutgoing CONSTANT);
|
||||
Q_PROPERTY(bool microMuted READ getMicroMuted WRITE setMicroMuted NOTIFY microMutedChanged);
|
||||
Q_PROPERTY(bool pausedByUser READ getPausedByUser WRITE setPausedByUser NOTIFY pausedByUserChanged);
|
||||
Q_PROPERTY(bool videoOutputEnabled READ getVideoOutputEnabled WRITE setVideoOutputEnabled NOTIFY videoOutputEnabled);
|
||||
|
||||
public:
|
||||
enum CallStatus {
|
||||
|
|
@ -39,6 +40,7 @@ signals:
|
|||
void statusChanged (CallStatus status);
|
||||
void pausedByUserChanged (bool status);
|
||||
void microMutedChanged (bool status);
|
||||
void videoOutputEnabled (bool status);
|
||||
|
||||
private:
|
||||
QString getSipAddress () const;
|
||||
|
|
@ -54,6 +56,9 @@ private:
|
|||
bool getPausedByUser () const;
|
||||
void setPausedByUser (bool status);
|
||||
|
||||
bool getVideoOutputEnabled () const;
|
||||
void setVideoOutputEnabled (bool status);
|
||||
|
||||
bool m_micro_muted = false;
|
||||
|
||||
linphone::CallState m_linphone_call_status = linphone::CallStateIdle;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <QTimer>
|
||||
|
||||
#include "../../app/App.hpp"
|
||||
#include "../../utils.hpp"
|
||||
#include "../core/CoreManager.hpp"
|
||||
|
||||
#include "CallsListModel.hpp"
|
||||
|
|
@ -60,6 +61,19 @@ QVariant CallsListModel::data (const QModelIndex &index, int role) const {
|
|||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void CallsListModel::launchAudioCall (const QString &sip_uri) const {
|
||||
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
|
||||
core->inviteAddress(
|
||||
core->interpretUrl(::Utils::qStringToLinphoneString(sip_uri))
|
||||
);
|
||||
}
|
||||
|
||||
void CallsListModel::launchVideoCall (const QString &sip_uri) const {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool CallsListModel::removeRow (int row, const QModelIndex &parent) {
|
||||
return removeRows(row, 1, parent);
|
||||
}
|
||||
|
|
@ -86,6 +100,9 @@ void CallsListModel::addCall (const shared_ptr<linphone::Call> &linphone_call) {
|
|||
App::getInstance()->getCallsWindow()->show();
|
||||
|
||||
CallModel *call = new CallModel(linphone_call);
|
||||
|
||||
qInfo() << "Add call:" << call;
|
||||
|
||||
App::getInstance()->getEngine()->setObjectOwnership(call, QQmlEngine::CppOwnership);
|
||||
linphone_call->setData("call-model", *call);
|
||||
|
||||
|
|
@ -97,12 +114,12 @@ 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");
|
||||
linphone_call->unsetData("call-model");
|
||||
|
||||
// TODO: It will be (maybe) necessary to use a single scheduled function in the future.
|
||||
QTimer::singleShot(
|
||||
DELAY_BEFORE_REMOVE_CALL, this, [this, call]() {
|
||||
DELAY_BEFORE_REMOVE_CALL, this, [this, linphone_call]() {
|
||||
CallModel *call = &linphone_call->getData<CallModel>("call-model");
|
||||
linphone_call->unsetData("call-model");
|
||||
|
||||
qInfo() << "Removing call:" << call;
|
||||
|
||||
int index = m_list.indexOf(call);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ public:
|
|||
QHash<int, QByteArray> roleNames () const override;
|
||||
QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
Q_INVOKABLE void launchAudioCall (const QString &sip_uri) const;
|
||||
Q_INVOKABLE void launchVideoCall (const QString &sip_uri) const;
|
||||
|
||||
private:
|
||||
bool removeRow (int row, const QModelIndex &parent = QModelIndex());
|
||||
bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ BusyIndicator {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
property color color: BusyIndicatorStyle.color
|
||||
|
||||
readonly property int _rotation: 360
|
||||
readonly property int _size: width < height ? width : height
|
||||
|
||||
|
|
@ -58,7 +60,7 @@ BusyIndicator {
|
|||
height: item.height / 3
|
||||
width: item.width / 3
|
||||
|
||||
color: BusyIndicatorStyle.color
|
||||
color: busyIndicator.color
|
||||
radius: (width > height ? width : height) / 2
|
||||
|
||||
transform: [
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import QtQuick 2.7
|
||||
|
||||
import Common.Styles 1.0
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Row {
|
||||
id: container
|
||||
|
||||
property color sphereColor: CaterpillarAnimationStyle.sphere.color
|
||||
property int animationDuration: CaterpillarAnimationStyle.animation.duration
|
||||
property int nSpheres: CaterpillarAnimationStyle.nSpheres
|
||||
property int sphereSize: CaterpillarAnimationStyle.sphere.size
|
||||
property int animationSpace: CaterpillarAnimationStyle.animation.space
|
||||
|
||||
spacing: CaterpillarAnimationStyle.spacing
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: nSpheres
|
||||
|
||||
Rectangle {
|
||||
id: sphere
|
||||
|
||||
property bool forceRunning: false
|
||||
property int previousY: 0
|
||||
|
||||
function startAnimation () {
|
||||
if (!animator.running) {
|
||||
animator.running = true
|
||||
} else {
|
||||
forceRunning = true
|
||||
}
|
||||
}
|
||||
|
||||
color: sphereColor
|
||||
height: width
|
||||
radius: width / 2
|
||||
width: container.sphereSize
|
||||
|
||||
// y can be: `0`, `animationSpace` or `animationSpace / 2`
|
||||
onYChanged: {
|
||||
// No call executed by last sphere.
|
||||
if (index === nSpheres - 1) {
|
||||
return
|
||||
}
|
||||
|
||||
if (y === (animationSpace / 2) && previousY === 0) {
|
||||
repeater.itemAt(index + 1).startAnimation()
|
||||
}
|
||||
|
||||
previousY = y
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Only start first sphere.
|
||||
if (index === 0) {
|
||||
animator.running = true
|
||||
}
|
||||
}
|
||||
|
||||
YAnimator on y {
|
||||
id: animator
|
||||
|
||||
duration: container.animationDuration
|
||||
from: 0
|
||||
running: false
|
||||
to: animationSpace / 2
|
||||
|
||||
onRunningChanged: {
|
||||
if (running) {
|
||||
return
|
||||
}
|
||||
|
||||
var mid = animationSpace / 2
|
||||
if (from === animationSpace && to === mid) {
|
||||
from = mid
|
||||
to = 0
|
||||
} else if (from === mid && to === 0) {
|
||||
from = 0
|
||||
to = mid
|
||||
|
||||
if (index !== 0 && !forceRunning) {
|
||||
return
|
||||
}
|
||||
} else if (from === 0 && to === mid) {
|
||||
from = mid
|
||||
to = animationSpace
|
||||
} else {
|
||||
from = animationSpace
|
||||
to = mid
|
||||
}
|
||||
|
||||
forceRunning = false
|
||||
animator.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,14 +12,11 @@ singleton Constants 1.0 Constants.qml
|
|||
# Components -------------------------------------------------------------------
|
||||
|
||||
# Animations
|
||||
CaterpillarAnimation 1.0 Animations/CaterpillarAnimation.qml
|
||||
BusyIndicator 1.0 Animations/BusyIndicator.qml
|
||||
|
||||
# Chat
|
||||
Borders 1.0 Borders.qml
|
||||
|
||||
# BusyIndicator
|
||||
BusyIndicator 1.0 BusyIndicator.qml
|
||||
|
||||
# Collapse
|
||||
Collapse 1.0 Collapse.qml
|
||||
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ ListView {
|
|||
|
||||
_mapStatusToParams[CallModel.CallStatusConnected] = {
|
||||
actions: [{
|
||||
name: qsTr('resumeCall'),
|
||||
handler: (function (call) { call.pausedByUser = false })
|
||||
handler: (function (call) { call.pausedByUser = false }),
|
||||
name: qsTr('resumeCall')
|
||||
}, {
|
||||
name: qsTr('transferCall'),
|
||||
handler: (function (call) { call.transfer() })
|
||||
handler: (function (call) { call.transfer() }),
|
||||
name: qsTr('transferCall')
|
||||
}, {
|
||||
name: qsTr('terminateCall'),
|
||||
handler: (function (call) { call.terminate() })
|
||||
handler: (function (call) { call.terminate() }),
|
||||
name: qsTr('terminateCall')
|
||||
}],
|
||||
component: callActions,
|
||||
string: 'connected'
|
||||
|
|
@ -85,14 +85,14 @@ ListView {
|
|||
|
||||
_mapStatusToParams[CallModel.CallStatusPaused] = {
|
||||
actions: [{
|
||||
name: qsTr('pauseCall'),
|
||||
handler: (function (call) { call.pausedByUser = true })
|
||||
handler: (function (call) { call.pausedByUser = true }),
|
||||
name: qsTr('pauseCall')
|
||||
}, {
|
||||
name: qsTr('transferCall'),
|
||||
handler: (function (call) { call.transfer() })
|
||||
handler: (function (call) { call.transfer() }),
|
||||
name: qsTr('transferCall')
|
||||
}, {
|
||||
name: qsTr('terminateCall'),
|
||||
handler: (function (call) { call.terminate() })
|
||||
handler: (function (call) { call.terminate() }),
|
||||
name: qsTr('terminateCall')
|
||||
}],
|
||||
component: callActions,
|
||||
string: 'paused'
|
||||
|
|
@ -160,6 +160,29 @@ ListView {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
SmartConnect {
|
||||
Component.onCompleted: {
|
||||
this.connect(model, 'rowsAboutToBeRemoved', function (_, first, last) {
|
||||
var index = calls.currentIndex
|
||||
if (index >= first && index <= last) { // Remove current call.
|
||||
if (model.rowCount() - (last - first + 1) <= 0) {
|
||||
calls.currentIndex = -1
|
||||
} else {
|
||||
calls.currentIndex = 0
|
||||
}
|
||||
} else if (last < index) { // Remove before current call.
|
||||
calls.currentIndex = index - (last - first + 1)
|
||||
}
|
||||
})
|
||||
|
||||
this.connect(model, 'rowsInserted', function (_, first, last) {
|
||||
calls.currentIndex = first
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
delegate: CallControls {
|
||||
id: _callControls
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ Rectangle {
|
|||
property var call
|
||||
|
||||
default property alias _actionArea: actionArea.data
|
||||
property var _contact: SipAddressesModel.mapSipAddressToContact(call.sipAddress)
|
||||
|
||||
property var _contactObserver: SipAddressesModel.getContactObserver(sipAddress)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -45,12 +46,16 @@ Rectangle {
|
|||
height: StartingCallStyle.contactDescriptionHeight
|
||||
horizontalTextAlignment: Text.AlignHCenter
|
||||
sipAddress: call.sipAddress
|
||||
username: LinphoneUtils.getContactUsername(_contact || call.sipAddress)
|
||||
username: LinphoneUtils.getContactUsername(_contactObserver.contact || call.sipAddress)
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
CaterpillarAnimation {
|
||||
BusyIndicator {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: StartingCallStyle.busyIndicator.color
|
||||
height: StartingCallStyle.busyIndicator.height
|
||||
width: StartingCallStyle.busyIndicator.width
|
||||
|
||||
visible: call.isOutgoing
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +86,7 @@ Rectangle {
|
|||
|
||||
anchors.centerIn: parent
|
||||
backgroundColor: StartingCallStyle.avatar.backgroundColor
|
||||
image: _contact && _contact.avatar
|
||||
image: _contactObserver.contact && _contactObserver.contact.avatar
|
||||
username: contactDescription.username
|
||||
|
||||
height: _computeAvatarSize()
|
||||
|
|
|
|||
|
|
@ -15,10 +15,7 @@ Window {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
readonly property var call: {
|
||||
console.log('hihi')
|
||||
return calls.selectedCall
|
||||
}
|
||||
readonly property var call: calls.selectedCall
|
||||
readonly property var sipAddress: {
|
||||
if (call) {
|
||||
return call.sipAddress
|
||||
|
|
@ -27,18 +24,6 @@ Window {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function launchAudioCall (sipAddress) {
|
||||
window.show()
|
||||
|
||||
}
|
||||
|
||||
function launchVideoCall (sipAddress) {
|
||||
window.show()
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
minimumHeight: CallsWindowStyle.minimumHeight
|
||||
minimumWidth: CallsWindowStyle.minimumWidth
|
||||
title: CallsWindowStyle.title
|
||||
|
|
@ -125,7 +110,6 @@ Window {
|
|||
id: incomingCall
|
||||
|
||||
IncomingCall {
|
||||
anchors.fill: parent
|
||||
call: window.call
|
||||
}
|
||||
}
|
||||
|
|
@ -134,7 +118,6 @@ Window {
|
|||
id: outgoingCall
|
||||
|
||||
OutgoingCall {
|
||||
anchors.fill: parent
|
||||
call: window.call
|
||||
}
|
||||
}
|
||||
|
|
@ -143,11 +126,20 @@ Window {
|
|||
id: incall
|
||||
|
||||
Incall {
|
||||
anchors.fill: parent
|
||||
call: window.call
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: chat
|
||||
|
||||
Chat {
|
||||
proxyModel: ChatProxyModel {
|
||||
sipAddress: window.sipAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
childA: Loader {
|
||||
|
|
@ -158,11 +150,12 @@ Window {
|
|||
if (!call) {
|
||||
return null
|
||||
}
|
||||
return incomingCall
|
||||
|
||||
var status = call.status
|
||||
if (status === CallModel.CallStatusIncoming) {
|
||||
return incomingCall
|
||||
}
|
||||
|
||||
if (status === CallModel.CallStatusOutgoing) {
|
||||
return outgoingCall
|
||||
}
|
||||
|
|
@ -174,13 +167,7 @@ Window {
|
|||
childB: Loader {
|
||||
active: Boolean(window.call)
|
||||
anchors.fill: parent
|
||||
|
||||
sourceComponent: Chat {
|
||||
anchors.fill: parent
|
||||
proxyModel: ChatProxyModel {
|
||||
sipAddress: window.sipAddress || ''
|
||||
}
|
||||
}
|
||||
sourceComponent: window.call ? chat : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,6 @@ import App.Styles 1.0
|
|||
// =============================================================================
|
||||
|
||||
Rectangle {
|
||||
id: call
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
property var call
|
||||
|
||||
property var _contactObserver: SipAddressesModel.getContactObserver(sipAddress)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Common 1.0
|
|||
|
||||
import App.Styles 1.0
|
||||
|
||||
// ===================================================================
|
||||
// =============================================================================
|
||||
|
||||
AbstractStartingCall {
|
||||
ActionBar {
|
||||
|
|
@ -11,18 +11,22 @@ AbstractStartingCall {
|
|||
|
||||
ActionButton {
|
||||
icon: 'video_call_accept'
|
||||
|
||||
onClicked: call.acceptWithVideo()
|
||||
}
|
||||
|
||||
ActionButton {
|
||||
icon: 'call_accept'
|
||||
|
||||
onClicked: call.accept()
|
||||
}
|
||||
}
|
||||
|
||||
ActionBar {
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
rightMargin: StartingCallStyle.rightButtonsGroupMargin
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
iconSize: StartingCallStyle.iconSize
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import App.Styles 1.0
|
|||
|
||||
AbstractStartingCall {
|
||||
GridLayout {
|
||||
columns: parent.width < 415 && call.videoOutputEnabled ? 1 : 2
|
||||
rowSpacing: ActionBarStyle.spacing
|
||||
columns: parent.width < 415 && call.isVideoCall ? 1 : 2
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
|
|
@ -24,7 +24,7 @@ AbstractStartingCall {
|
|||
icon: 'micro'
|
||||
iconSize: StartingCallStyle.iconSize
|
||||
|
||||
onClicked: call.microMuted = !enabled
|
||||
onClicked: call.microMuted = enabled
|
||||
}
|
||||
|
||||
ActionSwitch {
|
||||
|
|
@ -40,7 +40,7 @@ AbstractStartingCall {
|
|||
height: StartingCallStyle.userVideo.height
|
||||
width: StartingCallStyle.userVideo.width
|
||||
|
||||
visible: isVideoCall
|
||||
visible: call.videoOutputEnabled
|
||||
}
|
||||
|
||||
ActionBar {
|
||||
|
|
|
|||
|
|
@ -131,12 +131,12 @@ ColumnLayout {
|
|||
|
||||
ActionButton {
|
||||
icon: 'video_call'
|
||||
onClicked: CallsWindow.launchVideoCall($contact.vcard.sipAddresses[0]) // FIXME: Display menu if many addresses.
|
||||
onClicked: CallsListModel.launchVideoCall($contact.vcard.sipAddresses[0]) // FIXME: Display menu if many addresses.
|
||||
}
|
||||
|
||||
ActionButton {
|
||||
icon: 'call'
|
||||
onClicked: CallsWindow.launchAudioCall($contact.vcard.sipAddresses[0]) // FIXME: Display menu if many addresses.
|
||||
onClicked: CallsListModel.launchAudioCall($contact.vcard.sipAddresses[0]) // FIXME: Display menu if many addresses.
|
||||
}
|
||||
|
||||
ActionButton {
|
||||
|
|
|
|||
|
|
@ -79,12 +79,12 @@ ColumnLayout {
|
|||
|
||||
ActionButton {
|
||||
icon: 'video_call'
|
||||
onClicked: CallsWindow.launchVideoCall(conversation.sipAddress)
|
||||
onClicked: CallsListModel.launchVideoCall(conversation.sipAddress)
|
||||
}
|
||||
|
||||
ActionButton {
|
||||
icon: 'call'
|
||||
onClicked: CallsWindow.launchAudioCall(conversation.sipAddress)
|
||||
onClicked: CallsListModel.launchAudioCall(conversation.sipAddress)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ ColumnLayout {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
ActionButton {
|
||||
icon: _contact ? 'contact_add' : 'contact_edit'
|
||||
icon: !_contact ? 'contact_add' : 'contact_edit'
|
||||
iconSize: ConversationStyle.bar.actions.edit.iconSize
|
||||
|
||||
onClicked: window.setView('ContactEdit', {
|
||||
|
|
|
|||
|
|
@ -160,14 +160,14 @@ ApplicationWindow {
|
|||
})
|
||||
}
|
||||
|
||||
onLaunchCall: CallsWindow.launchAudioCall(sipAddress)
|
||||
onLaunchCall: CallsListModel.launchAudioCall(sipAddress)
|
||||
onLaunchChat: {
|
||||
window.ensureCollapsed()
|
||||
window.setView('Conversation', {
|
||||
sipAddress: sipAddress
|
||||
})
|
||||
}
|
||||
onLaunchVideoCall: CallsWindow.launchVideoCall(sipAddress)
|
||||
onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress)
|
||||
|
||||
onEntryClicked: {
|
||||
window.ensureCollapsed()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ QtObject {
|
|||
property int maxSize: 300
|
||||
}
|
||||
|
||||
property QtObject busyIndicator: QtObject {
|
||||
property color color: Colors.g
|
||||
property int height: 30
|
||||
property int width: 30
|
||||
}
|
||||
|
||||
property QtObject header: QtObject {
|
||||
property int spacing: 10
|
||||
property int topMargin: 26
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue