start audio call, features : pause call + blind transfer

(call window in c++)
This commit is contained in:
Gaelle Braud 2023-11-22 13:25:28 +01:00
parent a1d72e6382
commit 4ea1b96246
50 changed files with 1597 additions and 229 deletions

View file

@ -26,8 +26,10 @@
#include <QFileSelector>
#include <QGuiApplication>
#include <QLibraryInfo>
#include <QQmlComponent>
#include <QQmlContext>
#include <QQmlFileSelector>
#include <QQuickWindow>
#include <QTimer>
#include "core/account/AccountCore.hpp"
@ -45,11 +47,14 @@
#include "core/singleapplication/singleapplication.h"
#include "model/object/VariantObject.hpp"
#include "tool/Constants.hpp"
#include "tool/EnumsToString.hpp"
#include "tool/Utils.hpp"
#include "tool/providers/AvatarProvider.hpp"
#include "tool/providers/ImageProvider.hpp"
#include "tool/thread/Thread.hpp"
DEFINE_ABSTRACT_OBJECT(App)
App::App(int &argc, char *argv[])
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
mLinphoneThread = new Thread(this);
@ -83,7 +88,7 @@ void App::init() {
if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true);
if (!mLinphoneThread->isRunning()) {
qDebug() << "[App] Starting Thread";
qDebug() << log().arg("Starting Thread");
mLinphoneThread->start();
}
setQuitOnLastWindowClosed(true); // TODO: use settings to set it
@ -96,7 +101,7 @@ void App::init() {
if (version.majorVersion() == 5 && version.minorVersion() == 9) selectors.push_back("5.9");
auto selector = new QQmlFileSelector(mEngine, mEngine);
selector->setExtraSelectors(selectors);
qInfo() << QStringLiteral("[App] Activated selectors:") << selector->selector()->allSelectors();
qInfo() << log().arg("Activated selectors:") << selector->selector()->allSelectors();
mEngine->addImportPath(":/");
mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath());
@ -110,9 +115,9 @@ void App::init() {
const QUrl url(u"qrc:/Linphone/view/App/Main.qml"_qs);
QObject::connect(
mEngine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
qCritical() << "[App] Main.qml couldn't be load. The app will exit";
qCritical() << log().arg("Main.qml couldn't be load. The app will exit");
exit(-1);
}
},
@ -129,6 +134,9 @@ void App::initCppInterfaces() {
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Constants(engine); });
qmlRegisterSingletonType<Utils>("UtilsCpp", 1, 0, "UtilsCpp",
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Utils(engine); });
qmlRegisterSingletonType<EnumsToString>(
"EnumsToStringCpp", 1, 0, "EnumsToStringCpp",
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new EnumsToString(engine); });
qmlRegisterType<PhoneNumberProxy>(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy");
qmlRegisterType<VariantObject>(Constants::MainQmlUri, 1, 0, "VariantObject");
@ -189,9 +197,70 @@ bool App::notify(QObject *receiver, QEvent *event) {
try {
done = QApplication::notify(receiver, event);
} catch (const std::exception &ex) {
qCritical() << "[App] Exception has been catch in notify";
qCritical() << log().arg("Exception has been catch in notify");
} catch (...) {
qCritical() << "[App] Generic exeption has been catch in notify";
qCritical() << log().arg("Generic exeption has been catch in notify");
}
return done;
}
QQuickWindow *App::getCallsWindow(QVariant callGui) {
mustBeInMainThread(getClassName());
if (!mCallsWindow) {
const QUrl callUrl("qrc:/Linphone/view/App/CallsWindow.qml");
qInfo() << log().arg("Creating subwindow: `%1`.").arg(callUrl.toString());
QQmlComponent component(mEngine, callUrl);
if (component.isError()) {
qWarning() << component.errors();
abort();
}
qInfo() << log().arg("Subwindow status: `%1`.").arg(component.status());
QObject *object = component.createWithInitialProperties({{"call", callGui}});
Q_ASSERT(object);
if (!object) {
qCritical() << log().arg("Calls window could not be created.");
return nullptr;
}
// QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
object->setParent(mEngine);
auto window = qobject_cast<QQuickWindow *>(object);
Q_ASSERT(window);
if (!window) {
qCritical() << log().arg("Calls window could not be created.");
return nullptr;
}
mCallsWindow = window;
}
postModelAsync([this]() {
auto core = CoreModel::getInstance()->getCore();
auto callsNb = core->getCallsNb();
postCoreAsync([this, callsNb] { mCallsWindow->setProperty("callsCount", callsNb); });
});
mCallsWindow->setProperty("call", callGui);
return mCallsWindow;
}
void App::closeCallsWindow() {
if (mCallsWindow) {
mCallsWindow->close();
mCallsWindow->deleteLater();
mCallsWindow = nullptr;
}
}
void App::smartShowWindow(QQuickWindow *window) {
if (!window) return;
window->setVisible(true);
// Force show, maybe redundant with setVisible
if (window->visibility() == QWindow::Maximized) // Avoid to change visibility mode
window->showMaximized();
else window->show();
window->raise(); // Raise ensure to get focus on Mac
window->requestActivate();
}

View file

@ -24,11 +24,14 @@
#include "core/singleapplication/singleapplication.h"
#include "model/core/CoreModel.hpp"
#include "tool/AbstractObject.hpp"
class CallGui;
class Thread;
class Notifier;
class QQuickWindow;
class App : public SingleApplication {
class App : public SingleApplication, public AbstractObject {
public:
App(int &argc, char *argv[]);
static App *getInstance();
@ -53,6 +56,20 @@ public:
QMetaObject::invokeMethod(App::getInstance(), callable);
}
template <typename Func, typename... Args>
static auto postCoreSync(Func &&callable, Args &&...args) {
if (QThread::currentThread() == CoreModel::getInstance()->thread()) {
bool end = false;
postCoreAsync([&end, callable, args...]() mutable {
QMetaObject::invokeMethod(App::getInstance(), callable, args..., Qt::DirectConnection);
end = true;
});
while (!end)
qApp->processEvents();
} else {
QMetaObject::invokeMethod(App::getInstance(), callable, Qt::DirectConnection);
}
}
template <typename Func, typename... Args>
static auto postModelSync(Func &&callable, Args &&...args) {
if (QThread::currentThread() != CoreModel::getInstance()->thread()) {
bool end = false;
@ -73,8 +90,13 @@ public:
void onLoggerInitialized();
QQuickWindow *getCallsWindow(QVariant callGui);
void closeCallsWindow();
Q_INVOKABLE static void smartShowWindow(QQuickWindow *window);
QQmlApplicationEngine *mEngine = nullptr;
bool notify(QObject *receiver, QEvent *event);
bool notify(QObject *receiver, QEvent *event) override;
enum class StatusCode { gRestartCode = 1000, gDeleteDataCode = 1001 };
@ -84,4 +106,9 @@ private:
QCommandLineParser *mParser = nullptr;
Thread *mLinphoneThread = nullptr;
Notifier *mNotifier = nullptr;
QQuickWindow *mCallsWindow = nullptr;
// TODO : changer ce count lorsqu'on aura liste d'appels
int callsCount = 0;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -21,6 +21,7 @@
#include "CallCore.hpp"
#include "core/App.hpp"
#include "model/object/VariantObject.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.hpp"
@ -38,15 +39,25 @@ CallCore::CallCore(const std::shared_ptr<linphone::Call> &call) : QObject(nullpt
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
// Should be call from model Thread
mustBeInLinphoneThread(getClassName());
mDir = LinphoneEnums::fromLinphone(call->getDir());
mCallModel = Utils::makeQObject_ptr<CallModel>(call);
mCallModel->setSelf(mCallModel);
mDuration = call->getDuration();
mMicrophoneMuted = call->getMicrophoneMuted();
mCallModel = Utils::makeQObject_ptr<CallModel>(call);
connect(mCallModel.get(), &CallModel::stateChanged, this, &CallCore::onStateChanged);
connect(this, &CallCore::lAccept, mCallModel.get(), &CallModel::accept);
connect(this, &CallCore::lDecline, mCallModel.get(), &CallModel::decline);
connect(this, &CallCore::lTerminate, mCallModel.get(), &CallModel::terminate);
mCallModel->setSelf(mCallModel);
// mSpeakerMuted = call->getSpeakerMuted();
mCameraEnabled = call->cameraEnabled();
mDuration = call->getDuration();
mState = LinphoneEnums::fromLinphone(call->getState());
mPeerAddress = Utils::coreStringToAppString(mCallModel->getRemoteAddress()->asString());
mStatus = LinphoneEnums::fromLinphone(call->getCallLog()->getStatus());
mTransferState = LinphoneEnums::fromLinphone(call->getTransferState());
auto encryption = LinphoneEnums::fromLinphone(call->getCurrentParams()->getMediaEncryption());
auto tokenVerified = mCallModel->getAuthenticationTokenVerified();
mPeerSecured = (encryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) ||
encryption == LinphoneEnums::MediaEncryption::Srtp ||
encryption == LinphoneEnums::MediaEncryption::Dtls;
mPaused = mState == LinphoneEnums::CallState::Pausing || mState == LinphoneEnums::CallState::Paused ||
mState == LinphoneEnums::CallState::PausedByRemote;
}
CallCore::~CallCore() {
@ -65,9 +76,72 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::microphoneMutedChanged, [this](bool isMuted) {
mAccountModelConnection->invokeToCore([this, isMuted]() { setMicrophoneMuted(isMuted); });
});
// mAccountModelConnection->makeConnect(this, &CallCore::lSetSpeakerMuted, [this](bool isMuted) {
// mAccountModelConnection->invokeToModel([this, isMuted]() { mCallModel->setSpeakerMuted(isMuted); });
// });
// mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::speakerMutedChanged, [this](bool isMuted) {
// mAccountModelConnection->invokeToCore([this, isMuted]() { setSpeakerMuted(isMuted); });
// });
mAccountModelConnection->makeConnect(this, &CallCore::lSetCameraEnabled, [this](bool enabled) {
mAccountModelConnection->invokeToModel([this, enabled]() { mCallModel->setCameraEnabled(enabled); });
});
mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::cameraEnabledChanged, [this](bool enabled) {
mAccountModelConnection->invokeToCore([this, enabled]() { setCameraEnabled(enabled); });
});
mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::durationChanged, [this](int duration) {
mAccountModelConnection->invokeToCore([this, duration]() { setDuration(duration); });
});
connect(mCallModel.get(), &CallModel::stateChanged, this,
[this](linphone::Call::State state, const std::string &message) {
mAccountModelConnection->invokeToCore([this, state, message]() {
setState(LinphoneEnums::fromLinphone(state), Utils::coreStringToAppString(message));
});
});
connect(mCallModel.get(), &CallModel::statusChanged, this, [this](linphone::Call::Status status) {
mAccountModelConnection->invokeToCore([this, status]() { setStatus(LinphoneEnums::fromLinphone(status)); });
});
mAccountModelConnection->makeConnect(this, &CallCore::lSetPaused, [this](bool paused) {
mAccountModelConnection->invokeToModel([this, paused]() { mCallModel->setPaused(paused); });
});
mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::pausedChanged, [this](bool paused) {
mAccountModelConnection->invokeToCore([this, paused]() { setPaused(paused); });
});
mAccountModelConnection->makeConnect(this, &CallCore::lTransferCall, [this](const QString &address) {
mAccountModelConnection->invokeToModel(
[this, address]() { mCallModel->transferTo(ToolModel::interpretUrl(address)); });
});
mAccountModelConnection->makeConnect(
mCallModel.get(), &CallModel::transferStateChanged,
[this](const std::shared_ptr<linphone::Call> &call, linphone::Call::State state) {
mAccountModelConnection->invokeToCore([this, state]() {
QString message;
if (state == linphone::Call::State::Error) {
message = "L'appel n'a pas pu être transféré.";
}
setTransferState(LinphoneEnums::fromLinphone(state), message);
});
});
mAccountModelConnection->makeConnect(
mCallModel.get(), &CallModel::encryptionChanged,
[this](const std::shared_ptr<linphone::Call> &call, bool on, const std::string &authenticationToken) {
auto encryption = LinphoneEnums::fromLinphone(call->getCurrentParams()->getMediaEncryption());
auto tokenVerified = mCallModel->getAuthenticationTokenVerified();
mAccountModelConnection->invokeToCore([this, call, encryption, tokenVerified]() {
setPeerSecured((encryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) ||
encryption == LinphoneEnums::MediaEncryption::Srtp ||
encryption == LinphoneEnums::MediaEncryption::Dtls);
});
});
mAccountModelConnection->makeConnect(this, &CallCore::lAccept, [this](bool withVideo) {
mAccountModelConnection->invokeToModel([this, withVideo]() { mCallModel->accept(withVideo); });
});
mAccountModelConnection->makeConnect(this, &CallCore::lDecline, [this]() {
mAccountModelConnection->invokeToModel([this]() { mCallModel->decline(); });
});
mAccountModelConnection->makeConnect(this, &CallCore::lTerminate, [this]() {
mAccountModelConnection->invokeToModel([this]() { mCallModel->terminate(); });
});
}
LinphoneEnums::CallStatus CallCore::getStatus() const {
@ -82,6 +156,18 @@ void CallCore::setStatus(LinphoneEnums::CallStatus status) {
}
}
LinphoneEnums::CallDir CallCore::getDir() const {
return mDir;
}
void CallCore::setDir(LinphoneEnums::CallDir dir) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mDir != dir) {
mDir = dir;
emit dirChanged(mDir);
}
}
LinphoneEnums::CallState CallCore::getState() const {
return mState;
}
@ -95,10 +181,6 @@ void CallCore::setState(LinphoneEnums::CallState state, const QString &message)
}
}
void CallCore::onStateChanged(linphone::Call::State state, const std::string &message) {
setState(LinphoneEnums::fromLinphone(state), Utils::coreStringToAppString(message));
}
QString CallCore::getLastErrorMessage() const {
return mLastErrorMessage;
}
@ -130,3 +212,47 @@ void CallCore::setMicrophoneMuted(bool isMuted) {
emit microphoneMutedChanged();
}
}
bool CallCore::getCameraEnabled() const {
return mCameraEnabled;
}
void CallCore::setCameraEnabled(bool enabled) {
if (mCameraEnabled != enabled) {
mCameraEnabled = enabled;
emit cameraEnabledChanged();
}
}
bool CallCore::getPaused() const {
return mPaused;
}
void CallCore::setPaused(bool paused) {
if (mPaused != paused) {
mPaused = paused;
emit pausedChanged();
}
}
bool CallCore::getPeerSecured() const {
return mPeerSecured;
}
void CallCore::setPeerSecured(bool secured) {
if (mPeerSecured != secured) {
mPeerSecured = secured;
emit peerSecuredChanged();
}
}
LinphoneEnums::CallState CallCore::getTransferState() const {
return mTransferState;
}
void CallCore::setTransferState(LinphoneEnums::CallState state, const QString &message) {
if (mTransferState != state) {
mTransferState = state;
if (state == LinphoneEnums::CallState::Error) setLastErrorMessage(message);
emit transferStateChanged();
}
}

View file

@ -32,11 +32,18 @@ class SafeConnection;
class CallCore : public QObject, public AbstractObject {
Q_OBJECT
// Q_PROPERTY(QString peerDisplayName MEMBER mPeerDisplayName)
Q_PROPERTY(LinphoneEnums::CallStatus status READ getStatus NOTIFY statusChanged)
Q_PROPERTY(LinphoneEnums::CallDir dir READ getDir NOTIFY dirChanged)
Q_PROPERTY(LinphoneEnums::CallState state READ getState NOTIFY stateChanged)
Q_PROPERTY(QString lastErrorMessage READ getLastErrorMessage NOTIFY lastErrorMessageChanged)
Q_PROPERTY(int duration READ getDuration NOTIFY durationChanged);
Q_PROPERTY(int duration READ getDuration NOTIFY durationChanged)
Q_PROPERTY(bool microphoneMuted READ getMicrophoneMuted WRITE lSetMicrophoneMuted NOTIFY microphoneMutedChanged)
Q_PROPERTY(bool cameraEnabled READ getCameraEnabled WRITE lSetCameraEnabled NOTIFY cameraEnabledChanged)
Q_PROPERTY(bool paused READ getPaused WRITE lSetPaused NOTIFY pausedChanged)
Q_PROPERTY(QString peerAddress MEMBER mPeerAddress CONSTANT)
Q_PROPERTY(bool peerSecured READ getPeerSecured WRITE setPeerSecured NOTIFY peerSecuredChanged)
Q_PROPERTY(LinphoneEnums::CallState transferState READ getTransferState NOTIFY transferStateChanged)
public:
// Should be call from model Thread. Will be automatically in App thread after initialization
@ -48,9 +55,11 @@ public:
LinphoneEnums::CallStatus getStatus() const;
void setStatus(LinphoneEnums::CallStatus status);
LinphoneEnums::CallDir getDir() const;
void setDir(LinphoneEnums::CallDir dir);
LinphoneEnums::CallState getState() const;
void setState(LinphoneEnums::CallState state, const QString &message);
void onStateChanged(linphone::Call::State state, const std::string &message);
QString getLastErrorMessage() const;
void setLastErrorMessage(const QString &message);
@ -61,18 +70,39 @@ public:
bool getMicrophoneMuted() const;
void setMicrophoneMuted(bool isMuted);
bool getCameraEnabled() const;
void setCameraEnabled(bool enabled);
bool getPaused() const;
void setPaused(bool paused);
bool getPeerSecured() const;
void setPeerSecured(bool secured);
LinphoneEnums::CallState getTransferState() const;
void setTransferState(LinphoneEnums::CallState state, const QString &message);
signals:
void statusChanged(LinphoneEnums::CallStatus status);
void stateChanged(LinphoneEnums::CallState state);
void dirChanged(LinphoneEnums::CallDir dir);
void lastErrorMessageChanged();
void peerAddressChanged();
void durationChanged(int duration);
void microphoneMutedChanged();
void cameraEnabledChanged();
void pausedChanged();
void transferStateChanged();
void peerSecuredChanged();
// Linphone commands
void lAccept(bool withVideo); // Accept an incoming call
void lDecline(); // Decline an incoming call
void lTerminate(); // Hangup a call
void lSetMicrophoneMuted(bool isMuted);
void lSetCameraEnabled(bool enabled);
void lSetPaused(bool paused);
void lTransferCall(const QString &dest);
/* TODO
Q_INVOKABLE void acceptWithVideo();
@ -99,9 +129,15 @@ private:
std::shared_ptr<CallModel> mCallModel;
LinphoneEnums::CallStatus mStatus;
LinphoneEnums::CallState mState;
LinphoneEnums::CallState mTransferState;
LinphoneEnums::CallDir mDir;
QString mLastErrorMessage;
QString mPeerAddress;
bool mPeerSecured;
int mDuration = 0;
bool mMicrophoneMuted;
bool mCameraEnabled;
bool mPaused = false;
QSharedPointer<SafeConnection> mAccountModelConnection;
DECLARE_ABSTRACT_OBJECT

View file

@ -25,6 +25,7 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc
"data/image/phone-selected.svg"
"data/image/phone-plus.svg"
"data/image/phone-disconnect.svg"
"data/image/phone-transfer.svg"
"data/image/address-book.svg"
"data/image/address-book-selected.svg"
"data/image/chat-teardrop-text.svg"
@ -39,12 +40,23 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc
"data/image/magnifying-glass.svg"
"data/image/backspace-fill.svg"
"data/image/x.svg"
"data/image/play.svg"
"data/image/incoming_call.svg"
"data/image/incoming_call_missed.svg"
"data/image/incoming_call_rejected.svg"
"data/image/outgoing_call.svg"
"data/image/outgoing_call_missed.svg"
"data/image/outgoing_call_rejected.svg"
"data/image/microphone.svg"
"data/image/microphone-slash.svg"
"data/image/video-camera.svg"
"data/image/video-camera-slash.svg"
"data/image/speaker-high.svg"
"data/image/speaker-slash.svg"
"data/image/trusted.svg"
"data/image/randomAvatar.png"
"data/image/pause.svg"
"data/image/smiley.svg"
data/shaders/roundEffect.vert.qsb
data/shaders/roundEffect.frag.qsb

View file

@ -0,0 +1,4 @@
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.985 23.3848C27.7621 25.0786 26.9303 26.6333 25.6449 27.7586C24.3595 28.884 22.7084 29.5029 21 29.4998C11.075 29.4998 3.00001 21.4248 3.00001 11.4998C2.99695 9.79142 3.61587 8.14034 4.74118 6.85493C5.86649 5.56952 7.42122 4.73769 9.11501 4.51481C9.54318 4.46242 9.97682 4.54986 10.3512 4.76409C10.7256 4.97833 11.0207 5.30787 11.1925 5.70356L13.8325 11.5986V11.6136C13.9639 11.9166 14.0181 12.2475 13.9904 12.5767C13.9627 12.9058 13.8539 13.223 13.6738 13.4998C13.6513 13.5336 13.6275 13.5648 13.6025 13.5961L11 16.6811C11.9363 18.5836 13.9263 20.5561 15.8538 21.4948L18.8963 18.9061C18.9261 18.8809 18.9574 18.8576 18.99 18.8361C19.2663 18.6508 19.5846 18.5378 19.9159 18.5072C20.2471 18.4766 20.5807 18.5295 20.8863 18.6611L20.9025 18.6686L26.7913 21.3073C27.1879 21.4783 27.5185 21.773 27.7337 22.1475C27.9489 22.522 28.037 22.9561 27.985 23.3848ZM26 23.1348C26 23.1348 25.9913 23.1348 25.9863 23.1348L20.1113 20.5023L17.0675 23.0923C17.038 23.1173 17.0071 23.1407 16.975 23.1623C16.6872 23.3543 16.3545 23.4684 16.0094 23.4934C15.6644 23.5183 15.3187 23.4534 15.0063 23.3048C12.665 22.1736 10.3313 19.8573 9.19876 17.5411C9.0488 17.2309 8.9815 16.8872 9.0034 16.5434C9.0253 16.1996 9.13565 15.8672 9.32376 15.5786C9.34496 15.5447 9.36879 15.5125 9.39501 15.4823L12 12.3936L9.37501 6.51856C9.37452 6.51357 9.37452 6.50855 9.37501 6.50356C8.16283 6.66168 7.04986 7.25626 6.24453 8.17595C5.43919 9.09564 4.99674 10.2774 5.00001 11.4998C5.00464 15.7419 6.69184 19.8088 9.69142 22.8084C12.691 25.808 16.758 27.4952 21 27.4998C22.2217 27.504 23.4031 27.0631 24.3233 26.2595C25.2436 25.4559 25.8396 24.3447 26 23.1336V23.1348Z" fill="white"/>
<path d="M15.3434 8.1709C15.3434 8.0624 15.3647 7.95495 15.4062 7.8547C15.4477 7.75445 15.5085 7.66336 15.5852 7.58664C15.662 7.50991 15.7531 7.44907 15.8533 7.40757C15.9536 7.36608 16.061 7.34475 16.1695 7.34481L21.1822 7.34554L19.6723 5.83568C19.5175 5.68084 19.4305 5.47084 19.4305 5.25187C19.4305 5.0329 19.5175 4.8229 19.6723 4.66807C19.8272 4.51323 20.0372 4.42625 20.2561 4.42625C20.4751 4.42625 20.6851 4.51323 20.8399 4.66807L23.759 7.58709C23.9138 7.74193 24.0008 7.95193 24.0008 8.1709C24.0008 8.38987 23.9138 8.59987 23.759 8.7547L20.8399 11.6737C20.6851 11.8286 20.4751 11.9155 20.2561 11.9155C20.0372 11.9155 19.8272 11.8286 19.6723 11.6737C19.5175 11.5189 19.4305 11.3089 19.4305 11.0899C19.4305 10.871 19.5175 10.661 19.6723 10.5061L21.1822 8.99625L16.1695 8.99698C16.061 8.99704 15.9536 8.97572 15.8533 8.93422C15.7531 8.89273 15.662 8.83188 15.5852 8.75516C15.5085 8.67844 15.4477 8.58735 15.4062 8.4871C15.3647 8.38685 15.3434 8.2794 15.3434 8.1709Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -23,6 +23,7 @@
#include <QDebug>
#include "model/core/CoreModel.hpp"
#include "tool/Utils.hpp"
DEFINE_ABSTRACT_OBJECT(CallModel)
@ -41,7 +42,6 @@ CallModel::~CallModel() {
void CallModel::accept(bool withVideo) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto core = CoreModel::getInstance()->getCore();
auto params = core->createCallParams(mMonitor);
params->enableVideo(withVideo);
@ -68,12 +68,53 @@ void CallModel::terminate() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
mMonitor->terminate();
}
void CallModel::setPaused(bool paused) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (paused) {
auto status = mMonitor->pause();
if (status != -1) emit pausedChanged(paused);
} else {
auto status = mMonitor->resume();
if (status != -1) emit pausedChanged(paused);
}
}
void CallModel::transferTo(const std::shared_ptr<linphone::Address> &address) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (mMonitor->transferTo(address) == -1)
qWarning() << log()
.arg(QStringLiteral("Unable to transfer: `%1`."))
.arg(Utils::coreStringToAppString(address->asString()));
}
void CallModel::setMicrophoneMuted(bool isMuted) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
mMonitor->setMicrophoneMuted(isMuted);
emit microphoneMutedChanged(isMuted);
}
void CallModel::setSpeakerMuted(bool isMuted) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
mMonitor->setSpeakerMuted(isMuted);
emit speakerMutedChanged(isMuted);
}
void CallModel::setCameraEnabled(bool enabled) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
mMonitor->enableCamera(enabled);
emit cameraEnabledChanged(enabled);
}
std::shared_ptr<const linphone::Address> CallModel::getRemoteAddress() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
return mMonitor->getRemoteAddress();
}
bool CallModel::getAuthenticationTokenVerified() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
return mMonitor->getAuthenticationTokenVerified();
}
void CallModel::onDtmfReceived(const std::shared_ptr<linphone::Call> &call, int dtmf) {
emit dtmfReceived(call, dtmf);
}
@ -108,6 +149,14 @@ void CallModel::onStateChanged(const std::shared_ptr<linphone::Call> &call,
emit stateChanged(state, message);
}
void CallModel::onStatusChanged(const std::shared_ptr<linphone::Call> &call, linphone::Call::Status status) {
emit statusChanged(status);
}
void CallModel::onDirChanged(const std::shared_ptr<linphone::Call> &call, linphone::Call::Dir dir) {
emit dirChanged(dir);
}
void CallModel::onStatsUpdated(const std::shared_ptr<linphone::Call> &call,
const std::shared_ptr<const linphone::CallStats> &stats) {
emit statsUpdated(call, stats);

View file

@ -41,10 +41,20 @@ public:
void terminate();
void setMicrophoneMuted(bool isMuted);
void setSpeakerMuted(bool isMuted);
void setCameraEnabled(bool enabled);
void setPaused(bool paused);
void transferTo(const std::shared_ptr<linphone::Address> &address);
std::shared_ptr<const linphone::Address> getRemoteAddress();
bool getAuthenticationTokenVerified();
signals:
void microphoneMutedChanged(bool isMuted);
void speakerMutedChanged(bool isMuted);
void cameraEnabledChanged(bool enabled);
void durationChanged(int);
void pausedChanged(bool paused);
private:
QTimer mDurationTimer;
@ -67,6 +77,8 @@ private:
virtual void onStateChanged(const std::shared_ptr<linphone::Call> &call,
linphone::Call::State state,
const std::string &message) override;
virtual void onStatusChanged(const std::shared_ptr<linphone::Call> &call, linphone::Call::Status status);
virtual void onDirChanged(const std::shared_ptr<linphone::Call> &call, linphone::Call::Dir dir);
virtual void onStatsUpdated(const std::shared_ptr<linphone::Call> &call,
const std::shared_ptr<const linphone::CallStats> &stats) override;
virtual void onTransferStateChanged(const std::shared_ptr<linphone::Call> &call,
@ -94,6 +106,8 @@ signals:
void infoMessageReceived(const std::shared_ptr<linphone::Call> &call,
const std::shared_ptr<const linphone::InfoMessage> &message);
void stateChanged(linphone::Call::State state, const std::string &message);
void statusChanged(linphone::Call::Status status);
void dirChanged(linphone::Call::Dir dir);
void statsUpdated(const std::shared_ptr<linphone::Call> &call,
const std::shared_ptr<const linphone::CallStats> &stats);
void transferStateChanged(const std::shared_ptr<linphone::Call> &call, linphone::Call::State state);

View file

@ -74,6 +74,7 @@ void CoreModel::start() {
setPathAfterStart();
mCore->enableFriendListSubscription(true);
mCore->enableRecordAware(true);
mCore->getCallsNb();
mIterateTimer->start();
}
// -----------------------------------------------------------------------------

View file

@ -1,5 +1,6 @@
list(APPEND _LINPHONEAPP_SOURCES
tool/Constants.cpp
tool/EnumsToString.cpp
tool/Utils.cpp
tool/LinphoneEnums.cpp

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "EnumsToString.hpp"
#include "core/App.hpp"
#include "model/call/CallModel.hpp"
#include "model/object/VariantObject.hpp"
#include "model/tool/ToolModel.hpp"
// =============================================================================

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef ENUMSTOSTRING_H_
#define ENUMSTOSTRING_H_
#include <QObject>
#include <QString>
#include "LinphoneEnums.hpp"
// =============================================================================
/***
* Class to make the link between qml and LinphoneEnums functions
* TODO : transform LinphoneEnums into a class so we can delete this one
*/
class EnumsToString : public QObject {
Q_OBJECT
public:
EnumsToString(QObject *parent = nullptr) : QObject(parent) {
}
Q_INVOKABLE QString dirToString(const LinphoneEnums::CallDir &data) {
return LinphoneEnums::toString(data);
}
Q_INVOKABLE QString statusToString(const LinphoneEnums::CallStatus &data) {
return LinphoneEnums::toString(data);
}
};
#endif // ENUMSTOSTRING_H_

View file

@ -115,6 +115,22 @@ QString LinphoneEnums::toString(const LinphoneEnums::CallStatus &data) {
}
}
LinphoneEnums::CallDir LinphoneEnums::fromLinphone(const linphone::Call::Dir &data) {
return static_cast<LinphoneEnums::CallDir>(data);
}
linphone::Call::Dir LinphoneEnums::toLinphone(const LinphoneEnums::CallDir &data) {
return static_cast<linphone::Call::Dir>(data);
}
QString LinphoneEnums::toString(const LinphoneEnums::CallDir &data) {
switch (data) {
case LinphoneEnums::CallDir::Incoming:
return "Incoming";
case LinphoneEnums::CallDir::Outgoing:
return "Outgoing";
}
}
linphone::Conference::Layout LinphoneEnums::toLinphone(const LinphoneEnums::ConferenceLayout &layout) {
if (layout != LinphoneEnums::ConferenceLayout::AudioOnly) return static_cast<linphone::Conference::Layout>(layout);
else return linphone::Conference::Layout::Grid; // Audio Only mode

View file

@ -159,6 +159,13 @@ linphone::Call::Status toLinphone(const LinphoneEnums::CallStatus &data);
LinphoneEnums::CallStatus fromLinphone(const linphone::Call::Status &data);
QString toString(const LinphoneEnums::CallStatus &data);
enum class CallDir { Outgoing = int(linphone::Call::Dir::Outgoing), Incoming = int(linphone::Call::Dir::Incoming) };
Q_ENUM_NS(CallDir)
linphone::Call::Dir toLinphone(const LinphoneEnums::CallDir &data);
LinphoneEnums::CallDir fromLinphone(const linphone::Call::Dir &data);
QString toString(const LinphoneEnums::CallDir &data);
enum class ConferenceLayout {
Grid = int(linphone::Conference::Layout::Grid),
ActiveSpeaker = int(linphone::Conference::Layout::ActiveSpeaker),

View file

@ -27,6 +27,7 @@
#include "model/tool/ToolModel.hpp"
#include "tool/providers/AvatarProvider.hpp"
#include <QImageReader>
#include <QQuickWindow>
// =============================================================================
@ -81,7 +82,13 @@ VariantObject *Utils::createCall(const QString &sipAddress,
data->makeRequest([sipAddress, prepareTransfertAddress, headers]() {
auto call = ToolModel::createCall(sipAddress, prepareTransfertAddress, headers);
if (call) {
return QVariant::fromValue(new CallGui(call));
auto callGui = QVariant::fromValue(new CallGui(call));
App::postCoreSync([callGui]() {
auto app = App::getInstance();
auto window = app->getCallsWindow(callGui);
window->show();
});
return callGui;
} else return QVariant();
});
data->requestValue();
@ -89,6 +96,10 @@ VariantObject *Utils::createCall(const QString &sipAddress,
return data;
}
void Utils::closeCallsWindow() {
App::getInstance()->closeCallsWindow();
}
VariantObject *Utils::haveAccount() {
VariantObject *result = new VariantObject();
@ -134,3 +145,36 @@ QString Utils::createAvatar(const QUrl &fileUrl) {
}
return fileUri;
}
QString Utils::formatElapsedTime(int seconds) {
// s, m, h, d, W, M, Y
// 1, 60, 3600, 86400, 604800, 2592000, 31104000
auto y = floor(seconds / 31104000);
if (y > 0) return QString::number(y) + " years";
auto M = floor(seconds / 2592000);
if (M > 0) return QString::number(M) + " months";
auto w = floor(seconds / 604800);
if (w > 0) return QString::number(w) + " week";
auto d = floor(seconds / 86400);
if (d > 0) return QString::number(d) + " days";
auto h = floor(seconds / 3600);
auto m = floor((seconds - h * 3600) / 60);
auto s = seconds - h * 3600 - m * 60;
QString hours, min, sec;
if (h < 10 && h > 0) {
hours = "0" + QString::number(h);
}
if (m < 10) {
min = "0" + QString::number(m);
}
if (s < 10) {
sec = "0" + QString::number(s);
}
return (h == 0 ? "" : hours + ":") + min + ":" + sec;
}

View file

@ -40,6 +40,7 @@
#endif // if defined(__GNUC__) && __GNUC__ >= 7
#endif // ifndef UTILS_NO_BREAK
class QQuickWindow;
class VariantObject;
class Utils : public QObject {
@ -53,8 +54,10 @@ public:
Q_INVOKABLE static VariantObject *createCall(const QString &sipAddress,
const QString &prepareTransfertAddress = "",
const QHash<QString, QString> &headers = {});
Q_INVOKABLE static void closeCallsWindow();
Q_INVOKABLE static VariantObject *haveAccount();
Q_INVOKABLE static QString createAvatar(const QUrl &fileUrl); // Return the avatar path
Q_INVOKABLE static QString formatElapsedTime(int seconds); // Return the elapsed time formated
static inline QString coreStringToAppString(const std::string &str) {
if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return QString::fromStdString(str);

View file

@ -45,6 +45,7 @@ ImageAsyncImageResponse::ImageAsyncImageResponse(const QString &id, const QSize
QString path = ":/data/image/";
QStringList filters;
filters << "*.svg";
filters << "*.png";
QDir imageDir(path);
if (!imageDir.exists()) {
qDebug() << QStringLiteral("[ImageProvider] Dir doesn't exist: `%1`.").arg(path);

View file

@ -0,0 +1,531 @@
import QtQuick 2.15
import QtQuick.Layouts
import QtQuick.Effects
import QtQuick.Controls as Control
import Linphone
import EnumsToStringCpp 1.0
import UtilsCpp 1.0
Window {
id: mainWindow
width: 1512 * DefaultStyle.dp
height: 982 * DefaultStyle.dp
property CallGui call
property bool isInContactList: false
property int callsCount: 0
onCallsCountChanged: console.log("calls count", callsCount)
property var peerName: UtilsCpp.getDisplayName(call.core.peerAddress)
property string peerNameText: peerName ? peerName.value : ""
// TODO : remove this, for debug only
property var callState: call && call.core.state
onCallStateChanged: {
console.log("State:", callState)
if (callState === LinphoneEnums.CallState.Error || callState === LinphoneEnums.CallState.End) {
endCall()
}
}
onClosing: {
endCall()
}
Timer {
id: autoCloseWindow
interval: 2000
onTriggered: {
UtilsCpp.closeCallsWindow()
}
}
function endCall() {
console.log("remaining calls before ending", mainWindow.callsCount)
callStatusText.text = qsTr("End of the call")
if (call) call.core.lTerminate()
if (callsCount === 1) {
bottomButtonsLayout.setButtonsEnabled(false)
autoCloseWindow.restart()
}
}
component BottomButton : Button {
required property string enabledIcon
property string disabledIcon
id: bottomButton
enabled: call != undefined
padding: 18 * DefaultStyle.dp
checkable: true
background: Rectangle {
anchors.fill: parent
color: bottomButton.enabled
? bottomButton.checked
? disabledIcon
? DefaultStyle.grey_0
: DefaultStyle.main2_400
: bottomButton.pressed
? DefaultStyle.main2_400
: DefaultStyle.grey_500
: DefaultStyle.grey_600
radius: 71 * DefaultStyle.dp
}
contentItem: EffectImage {
image.source: disabledIcon && bottomButton.checked ? disabledIcon : enabledIcon
anchors.fill: parent
image.width: 32 * DefaultStyle.dp
image.height: 32 * DefaultStyle.dp
colorizationColor: disabledIcon && bottomButton.checked ? DefaultStyle.main2_0 : DefaultStyle.grey_0
}
}
Control.Popup {
id: waitingPopup
visible: mainWindow.call.core.transferState === LinphoneEnums.CallState.OutgoingInit
|| mainWindow.call.core.transferState === LinphoneEnums.CallState.OutgoingProgress
|| mainWindow.call.core.transferState === LinphoneEnums.CallState.OutgoingRinging || false
modal: true
closePolicy: Control.Popup.NoAutoClose
anchors.centerIn: parent
padding: 20
background: Item {
anchors.fill: parent
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: parent.height + 2
color: DefaultStyle.main1_500_main
radius: 15
}
Rectangle {
id: mainBackground
anchors.fill: parent
radius: 15
}
}
contentItem: ColumnLayout {
BusyIndicator{
Layout.alignment: Qt.AlignHCenter
}
Text {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Transfert en cours, veuillez patienter")
}
}
}
Control.Popup {
id: transferErrorPopup
visible: mainWindow.call.core.transferState === LinphoneEnums.CallState.Error
modal: true
closePolicy: Control.Popup.NoAutoClose
x : parent.x + parent.width - width
y : parent.y + parent.height - height
padding: 20
background: Item {
anchors.fill: parent
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: parent.height + 2
color: DefaultStyle.danger_500
}
Rectangle {
id: transferErrorBackground
anchors.fill: parent
radius: 15
}
MultiEffect {
anchors.fill: transferErrorBackground
shadowEnabled: true
shadowColor: DefaultStyle.grey_900
shadowBlur: 10
// shadowOpacity: 0.1
}
}
contentItem: ColumnLayout {
Text {
text: qsTr("Erreur de transfert")
}
Text {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Le transfert d'appel a échoué.")
}
}
}
Rectangle {
anchors.fill: parent
color: DefaultStyle.ongoingCallWindowColor
ColumnLayout {
anchors.fill: parent
spacing: 5
anchors.bottomMargin: 5
Item {
Layout.margins: 10
Layout.fillWidth: true
Layout.minimumHeight: 25
RowLayout {
anchors.verticalCenter: parent.verticalCenter
spacing: 10
EffectImage {
id: callStatusIcon
image.fillMode: Image.PreserveAspectFit
image.width: 15
image.height: 15
image.sourceSize.width: 15
image.sourceSize.height: 15
image.source: (mainWindow.call.core.state === LinphoneEnums.CallState.Paused
|| mainWindow.callState === LinphoneEnums.CallState.PausedByRemote)
? AppIcons.pause
: (mainWindow.callState === LinphoneEnums.CallState.End
|| mainWindow.callState === LinphoneEnums.CallState.Released)
? AppIcons.endCall
: mainWindow.call.core.dir === LinphoneEnums.CallDir.Outgoing
? AppIcons.outgoingCall
: AppIcons.incomingCall
colorizationColor: mainWindow.callState === LinphoneEnums.CallState.Paused
|| mainWindow.callState === LinphoneEnums.CallState.PausedByRemote || mainWindow.callState === LinphoneEnums.CallState.End
|| mainWindow.callState === LinphoneEnums.CallState.Released ? DefaultStyle.danger_500 : undefined
}
Text {
id: callStatusText
text: (mainWindow.callState === LinphoneEnums.CallState.End || mainWindow.callState === LinphoneEnums.CallState.Released)
? qsTr("End of the call")
: (mainWindow.callState === LinphoneEnums.CallState.Paused || mainWindow.callState === LinphoneEnums.CallState.PausedByRemote)
? qsTr("Appel mis en pause")
: EnumsToStringCpp.dirToString(mainWindow.call.core.dir) + qsTr(" call")
color: DefaultStyle.grey_0
font.bold: true
}
Rectangle {
visible: mainWindow.callState === LinphoneEnums.CallState.Connected
|| mainWindow.callState === LinphoneEnums.CallState.StreamsRunning
Layout.preferredHeight: parent.height
Layout.preferredWidth: 2
}
Text {
text: UtilsCpp.formatElapsedTime(mainWindow.call.core.duration)
color: DefaultStyle.grey_0
visible: mainWindow.callState === LinphoneEnums.CallState.Connected
|| mainWindow.callState === LinphoneEnums.CallState.StreamsRunning
}
}
Control.Control {
anchors.centerIn: parent
topPadding: 8
bottomPadding: 8
leftPadding: 10
rightPadding: 10
visible: mainWindow.call.core.peerSecured
onVisibleChanged: console.log("peer secured", mainWindow.call.core.peerSecured)
background: Rectangle {
anchors.fill: parent
border.color: DefaultStyle.info_500_main
radius: 15
}
contentItem: RowLayout {
Image {
source: AppIcons.trusted
Layout.preferredWidth: 15
Layout.preferredHeight: 15
sourceSize.width: 15
sourceSize.height: 15
fillMode: Image.PreserveAspectFit
}
Text {
text: "This call is completely secured"
color: DefaultStyle.info_500_main
}
}
}
}
RowLayout {
Control.Control {
id: centerItem
Layout.fillWidth: true
Layout.preferredWidth: 1059 * DefaultStyle.dp
Layout.fillHeight: true
Layout.leftMargin: 10
Layout.rightMargin: 10
Layout.alignment: Qt.AlignCenter
background: Rectangle {
anchors.fill: parent
color: DefaultStyle.ongoingCallBackgroundColor
radius: 15
}
contentItem: Item {
anchors.fill: parent
StackLayout {
id: centerLayout
anchors.fill: parent
Connections {
target: mainWindow
onCallStateChanged: {
if (mainWindow.callState === LinphoneEnums.CallState.Error) {
centerLayout.currentIndex = 2
}
}
}
Item {
id: audioCallItem
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
Timer {
id: secondsTimer
interval: 1000
repeat: true
onTriggered: waitingTime.seconds += 1
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 30
visible: mainWindow.callState == LinphoneEnums.CallState.OutgoingInit
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingProgress
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingRinging
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingEarlyMedia
|| mainWindow.callState == LinphoneEnums.CallState.IncomingReceived
BusyIndicator {
indicatorColor: DefaultStyle.main2_100
Layout.alignment: Qt.AlignHCenter
}
Text {
id: waitingTime
property int seconds
text: UtilsCpp.formatElapsedTime(seconds)
color: DefaultStyle.grey_0
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.pointSize: DefaultStyle.ongoingCallElapsedTimeSize
Component.onCompleted: {
secondsTimer.restart()
}
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 2
// Avatar {
// Layout.alignment: Qt.AlignCenter
// visible: mainWindow.isInContactList
// image.source: AppIcons.avatar
// size: 100
// }
// DefaultAvatar {
// id: defaultAvatar
// Layout.alignment: Qt.AlignCenter
// visible: !mainWindow.isInContactList
// initials:{
// var usernameList = mainWindow.peerNameText.split(' ')
// for (var i = 0; i < usernameList.length; ++i) {
// initials += usernameList[i][0]
// }
// }
// Connections {
// target: mainWindow
// onPeerNameChanged: {
// defaultAvatar.initials = ""
// var usernameList = mainWindow.peerName.value.split(' ')
// for (var i = 0; i < usernameList.length; ++i) {
// defaultAvatar.initials += usernameList[i][0]
// }
// }
// }
// width: 100
// height: 100
// }
Text {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 15
visible: mainWindow.peerNameText.length > 0
text: mainWindow.peerNameText
color: DefaultStyle.grey_0
font {
pointSize: DefaultStyle.ongoingCallNameSize
capitalization: Font.Capitalize
}
}
Text {
Layout.alignment: Qt.AlignCenter
text: mainWindow.call.core.peerAddress
color: DefaultStyle.grey_0
font.pointSize: DefaultStyle.ongoingCallAddressSize
}
}
}
Image {
id: videoCallItem
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
}
ColumnLayout {
id: userNotFoundLayout
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
Layout.alignment: Qt.AlignCenter
Text {
text: qsTr(mainWindow.call.core.lastErrorMessage)
Layout.alignment: Qt.AlignCenter
color: DefaultStyle.grey_0
font.pointSize: DefaultStyle.ongoingCallNameSize
}
}
}
Text {
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.leftMargin: 10
anchors.bottomMargin: 10
text: mainWindow.peerNameText
color: DefaultStyle.grey_0
font.pointSize: DefaultStyle.ongoingCallAddressSize
}
}
}
OngoingCallRightPanel {
id: rightPanel
Layout.fillHeight: true
Layout.preferredWidth: 393 * DefaultStyle.dp
property int currentIndex: 0
Layout.rightMargin: 10
visible: false
headerContent: StackLayout {
currentIndex: rightPanel.currentIndex
anchors.verticalCenter: parent.verticalCenter
Text {
color: DefaultStyle.mainPageTitleColor
text: qsTr("Transfert d'appel")
font.bold: true
}
}
contentItem: StackLayout {
currentIndex: rightPanel.currentIndex
ContactsList {
Layout.fillWidth: true
Layout.fillHeight: true
sideMargin: 10
topMargin: 15
groupCallVisible: false
searchBarColor: DefaultStyle.grey_0
searchBarBorderColor: DefaultStyle.callRightPanelSearchBarBorderColor
onCallButtonPressed: (address) => {
mainWindow.call.core.lTransferCall(address)
}
}
}
}
}
GridLayout {
id: bottomButtonsLayout
rows: 1
columns: 3
Layout.alignment: Qt.AlignHCenter
layoutDirection: Qt.LeftToRight
columnSpacing: 20
Connections {
target: mainWindow
onCallStateChanged: if (mainWindow.callState === LinphoneEnums.CallState.Connected || mainWindow.callState === LinphoneEnums.CallState.StreamsRunning) {
bottomButtonsLayout.layoutDirection = Qt.RightToLeft
connectedCallButtons.visible = true
}
}
function setButtonsEnabled(enabled) {
for(var i=0; i < children.length; ++i) {
children[i].enabled = false
}
}
BottomButton {
Layout.row: 0
enabledIcon: AppIcons.endCall
checkable: false
Layout.column: mainWindow.callState == LinphoneEnums.CallState.OutgoingInit
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingProgress
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingRinging
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingEarlyMedia
|| mainWindow.callState == LinphoneEnums.CallState.IncomingReceived
? 0 : bottomButtonsLayout.columns - 1
Layout.preferredWidth: 75 * DefaultStyle.dp
Layout.preferredHeight: 55 * DefaultStyle.dp
background: Rectangle {
anchors.fill: parent
color: DefaultStyle.danger_500
radius: 71 * DefaultStyle.dp
}
onClicked: mainWindow.endCall()
}
RowLayout {
id: connectedCallButtons
visible: false
Layout.row: 0
Layout.column: 1
BottomButton {
Layout.preferredWidth: 55 * DefaultStyle.dp
Layout.preferredHeight: 55 * DefaultStyle.dp
background: Rectangle {
anchors.fill: parent
radius: 71 * DefaultStyle.dp
color: parent.enabled
? parent.checked
? DefaultStyle.success_500main
: parent.pressed
? DefaultStyle.main2_400
: DefaultStyle.grey_500
: DefaultStyle.grey_600
}
enabled: mainWindow.callState != LinphoneEnums.CallState.PausedByRemote
enabledIcon: enabled && checked ? AppIcons.play : AppIcons.pause
checked: mainWindow.call && (mainWindow.call.callState === LinphoneEnums.CallState.Paused
|| mainWindow.call.callState === LinphoneEnums.CallState.PausedByRemote) || false
onClicked: mainWindow.call.core.lSetPaused(!mainWindow.call.core.paused)
}
BottomButton {
id: transferCallButton
enabledIcon: AppIcons.transferCall
Layout.preferredWidth: 55 * DefaultStyle.dp
Layout.preferredHeight: 55 * DefaultStyle.dp
onClicked: {
rightPanel.visible = !rightPanel.visible
rightPanel.currentIndex = 0
}
}
}
RowLayout {
Layout.row: 0
Layout.column: mainWindow.callState == LinphoneEnums.CallState.OutgoingInit
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingProgress
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingRinging
|| mainWindow.callState == LinphoneEnums.CallState.OutgoingEarlyMedia
|| mainWindow.callState == LinphoneEnums.CallState.IncomingReceived
? bottomButtonsLayout.columns - 1 : 0
BottomButton {
enabled: false
enabledIcon: AppIcons.videoCamera
disabledIcon: AppIcons.videoCameraSlash
checked: !mainWindow.call.core.cameraEnabled
Layout.preferredWidth: 55 * DefaultStyle.dp
Layout.preferredHeight: 55 * DefaultStyle.dp
onClicked: mainWindow.call.core.lSetCameraEnabled(!mainWindow.call.core.cameraEnabled)
}
BottomButton {
id: micButton
enabledIcon: AppIcons.microphone
disabledIcon: AppIcons.microphoneSlash
checked: mainWindow.call.core.microphoneMuted
Layout.preferredWidth: 55 * DefaultStyle.dp
Layout.preferredHeight: 55 * DefaultStyle.dp
onClicked: mainWindow.call.core.lSetMicrophoneMuted(!mainWindow.call.core.microphoneMuted)
}
}
}
}
}
}

View file

@ -7,7 +7,7 @@ import Linphone
Window {
id: mainWindow
width: 1512 * DefaultStyle.dp
height: 930 * DefaultStyle.dp
height: 982 * DefaultStyle.dp
visible: true
title: qsTr("Linphone")
property bool firstConnection: true

View file

@ -1,11 +1,21 @@
list(APPEND _LINPHONEAPP_QML_FILES
view/App/Main.qml
view/App/CallsWindow.qml
view/App/Layout/LoginLayout.qml
view/App/Layout/MainLayout.qml
view/Item/Account/Accounts.qml
view/Item/Call/ContactsList.qml
view/Item/Call/OngoingCallRightPanel.qml
view/Item/Notification/Notification.qml
view/Item/Notification/NotificationReceivedCall.qml
view/Item/Prototype/CanvasCircle.qml
view/Item/BusyIndicator.qml
view/Item/Button.qml
view/Item/Carousel.qml
view/Item/CheckBox.qml
@ -17,10 +27,6 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Item/DesktopPopup.qml
view/Item/DigitInput.qml
view/Item/Notification/Notification.qml
view/Item/Notification/NotificationReceivedCall.qml
view/Item/EffectImage.qml
view/Item/NumericPad.qml
view/Item/PhoneNumberComboBox.qml
@ -48,7 +54,6 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Page/Main/AbstractMainPage.qml
view/Page/Main/CallPage.qml
# view/Page/Main/OngoingCallPage.qml
# Prototypes
view/Prototype/PhoneNumberPrototype.qml

View file

@ -0,0 +1,22 @@
import QtQuick 2.7
import QtQuick.Controls 2.2 as Control
import QtQuick.Effects
import Linphone
Item {
id: mainItem
property color indicatorColor: DefaultStyle.main1_500_main
width: busyIndicator.width
height: busyIndicator.height
Control.BusyIndicator {
id: busyIndicator
running: mainItem.visible
}
MultiEffect {
source: busyIndicator
anchors.fill: busyIndicator
colorizationColor: mainItem.indicatorColor
colorization: 1.0
}
}

View file

@ -19,12 +19,12 @@ Control.Button {
color: inversedColors
? mainItem.pressed
? DefaultStyle.buttonPressedInversedBackground
: DefaultStyle.buttonInversedBackground
: DefaultStyle.grey_0
: mainItem.pressed
? DefaultStyle.buttonPressedBackground
: DefaultStyle.buttonBackground
: DefaultStyle.main1_500_main
radius: 24
border.color: inversedColors ? DefaultStyle.buttonBackground : DefaultStyle.buttonInversedBackground
border.color: inversedColors ? DefaultStyle.main1_500_main : DefaultStyle.grey_0
MouseArea {
anchors.fill: parent
@ -42,17 +42,12 @@ Control.Button {
}
}
leftPadding: 13
rightPadding: 13
topPadding: 10
bottomPadding: 10
contentItem: Text {
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
wrapMode: Text.WordWrap
text: mainItem.text
color: inversedColors ? DefaultStyle.buttonInversedTextColor : DefaultStyle.buttonTextColor
color: inversedColors ? DefaultStyle.main1_500_main : DefaultStyle.grey_0
font {
bold: mainItem.boldText
pointSize: mainItem.textSize

View file

@ -0,0 +1,168 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2 as Control
import QtQuick.Effects
import Linphone
Item {
id: mainItem
property int sideMargin: 25
property int topMargin: 5
property bool groupCallVisible
property color searchBarColor: DefaultStyle.contactListSearchBarColor
property color searchBarBorderColor: "transparent"
signal callButtonPressed(string address)
clip: true
Control.Control {
id: listLayout
anchors.fill: parent
anchors.leftMargin: mainItem.sideMargin
anchors.rightMargin: mainItem.sideMargin
anchors.topMargin: mainItem.topMargin
background: Item {
anchors.fill: parent
}
contentItem: ColumnLayout {
anchors.fill: parent
spacing: 10
SearchBar {
id: searchBar
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.maximumWidth: mainItem.width
color: mainItem.searchBarColor
borderColor: mainItem.searchBarBorderColor
placeholderText: qsTr("Rechercher un contact")
numericPad: numPad
}
Button {
visible: mainItem.groupCallVisible
Layout.fillWidth: true
leftPadding: 0
topPadding: 0
rightPadding: 0
bottomPadding: 0
background: Rectangle {
color: DefaultStyle.groupCallButtonColor
anchors.fill: parent
radius: 50
}
contentItem: RowLayout {
Image {
source: AppIcons.groupCall
Layout.preferredWidth: 35
sourceSize.width: 35
fillMode: Image.PreserveAspectFit
}
Text {
text: "Appel de groupe"
font.bold: true
}
Item {
Layout.fillWidth: true
}
Image {
source: AppIcons.rightArrow
}
}
}
RowLayout {
visible: searchBar.text.length > 0 // && contactList.count === 0 (pas trouvé dans la liste)
Layout.maximumWidth: parent.width
Layout.fillWidth: true
Text {
text: searchBar.text
maximumLineCount: 1
elide: Text.ElideRight
}
Item {
Layout.fillWidth: true
}
Control.Button {
implicitWidth: 30
implicitHeight: 30
background: Item {
visible: false
}
contentItem: Image {
source: AppIcons.phone
width: 20
sourceSize.width: 20
fillMode: Image.PreserveAspectFit
}
onClicked: {
mainItem.callButtonPressed(searchBar.text)
}
}
}
ColumnLayout {
ListView {
id: contactList
Layout.fillWidth: true
Layout.fillHeight: true
// call history
model: 30
delegate: Item {
required property int index
width:contactList.width
height: 30
RowLayout {
anchors.fill: parent
Image {
source: AppIcons.info
}
ColumnLayout {
Text {
text: "John Doe"
}
// RowLayout {
// Image {
// source: AppIcons.incomingCall
// }
// Text {
// text: "info sur l'appel"
// }
// }
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
hoverEnabled: true
Rectangle {
anchors.fill: parent
opacity: 0.1
radius: 15
color: DefaultStyle.comboBoxHoverColor
visible: parent.containsMouse
}
onClicked: contactList.currentIndex = parent.index
}
}
}
}
}
}
Item {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: numPad.height
NumericPad {
id: numPad
// anchors.centerIn: parent
width: parent.width
onLaunchCall: {
var callVarObject = UtilsCpp.createCall(searchBar.text + "@sip.linphone.org")
// TODO : auto completion instead of sip linphone
var windowComp = Qt.createComponent("OngoingCallPage.qml")
var callWindow = windowComp.createObject({callVarObject: callVarObject})
callWindow.show()
}
}
}
}

View file

@ -0,0 +1,61 @@
import QtQuick 2.7
import QtQuick.Layouts
import QtQuick.Controls as Control
import Linphone
Control.Page {
id: mainItem
property alias headerContent: header.children
background: Rectangle {
width: mainItem.width
height: mainItem.height
color: DefaultStyle.callRightPanelBackgroundColor
radius: 15
}
header: Control.Control {
id: pageHeader
width: mainItem.width
background: Rectangle {
id: headerBackground
width: pageHeader.width
height: pageHeader.height
color: DefaultStyle.grey_0
radius: 15
Rectangle {
y: pageHeader.height/2
height: pageHeader.height/2
width: pageHeader.width
}
}
contentItem: RowLayout {
width: pageHeader.width
height: pageHeader.height
anchors.leftMargin: 10
anchors.left: pageHeader.left
Item {
id: header
}
Item {
Layout.fillWidth: true
}
Button {
id: closeButton
Layout.alignment: Qt.AlignRight
background: Item {
visible: false
}
contentItem: Image {
anchors.centerIn: closeButton
source: AppIcons.closeX
width: 10
sourceSize.width: 10
fillMode: Image.PreserveAspectFit
}
onClicked: mainItem.visible = false
}
}
}
}

View file

@ -91,7 +91,7 @@ ColumnLayout {
signal buttonClicked(int index)
background: Rectangle {
color: stackLayout.currentIndex == slideIndex ? DefaultStyle.buttonBackground : DefaultStyle.carouselLightGrayColor
color: stackLayout.currentIndex == slideIndex ? DefaultStyle.main1_500_main : DefaultStyle.carouselLightGrayColor
radius: 15
width: stackLayout.currentIndex == slideIndex ? 11 : 8
height: 8

View file

@ -11,15 +11,15 @@ Control.CheckBox {
x: (parent.width - width) / 2
y: (parent.height - height) / 2
radius: 3
border.color: DefaultStyle.checkboxBorderColor
border.color: DefaultStyle.main1_500_main
border.width: DefaultStyle.checkboxBorderWidth
// color: mainItem.checked ? DefaultStyle.checkboxBorderColor : "transparent"
// color: mainItem.checked ? DefaultStyle.main1_500_main : "transparent"
Text {
visible: mainItem.checked
text: "\u2714"
font.pointSize: 18
color: DefaultStyle.checkboxBorderColor
color: DefaultStyle.main1_500_main
anchors.centerIn: parent
}
}

View file

@ -17,7 +17,7 @@ ColumnLayout {
visible: label.length > 0
verticalAlignment: Text.AlignVCenter
text: mainItem.label
color: combobox.activeFocus ? DefaultStyle.formItemFocusBorderColor : DefaultStyle.formItemLabelColor
color: combobox.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.formItemLabelColor
font {
pointSize: DefaultStyle.formItemLabelSize
bold: true

View file

@ -5,7 +5,7 @@ import Linphone
Control.TextField {
id: mainItem
property int inputSize: 60
color: activeFocus ? DefaultStyle.digitInputFocusedColor : DefaultStyle.digitInputColor
color: activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.digitInputColor
rightPadding: inputSize / 4
leftPadding: inputSize / 4
validator: IntValidator{bottom: 0; top: 9}
@ -20,7 +20,7 @@ Control.TextField {
background: Rectangle {
// id: background
border.color: mainItem.activeFocus ? DefaultStyle.digitInputFocusedColor : DefaultStyle.digitInputColor
border.color: mainItem.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.digitInputColor
radius: mainItem.inputSize / 8
}
// cursorDelegate: Rectangle {
@ -34,6 +34,6 @@ Control.TextField {
// // anchors.left: parent.left
// // anchors.bottomMargin: inputSize/8
// // transform: [/*Translate {x: mainItem.cursorRectangle.height},*/ Rotation {angle: -90}]
// color:DefaultStyle.digitInputFocusedColor
// color:DefaultStyle.main1_500_main
// }
}

View file

@ -17,19 +17,20 @@ Item {
Image {
id: image
visible: !effect2.enabled
sourceSize.width: parent.width
sourceSize.height: parent.height
width: parent.width
height: parent.height
//sourceSize.width: parent.width
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
visible: !effect2.enabled
}
MultiEffect {
id: effect
visible: !effect2.enabled
anchors.fill: image
source: image
maskSource: image
visible: !effect2.enabled
brightness: effect2.enabled ? 1.0 : 0.0
}
MultiEffect {

View file

@ -94,7 +94,7 @@ ColumnLayout {
text: "Forgotten password?"
font{
underline: true
pointSize: DefaultStyle.defaultTextSize
pointSize: DefaultStyle.indicatorMessageTextSize
}
}
onClicked: console.debug("[LoginForm]User: forgotten password button clicked")

View file

@ -36,6 +36,10 @@ Notification {
Layout.rightMargin: 20
onClicked: {
notification.call.core.lAccept(true)
var windowComp = Qt.createComponent("OngoingCallPage.qml")
var callWindow = windowComp.createObject(null, {callVarObject: callVarObject})
callWindow.modality = Qt.ApplicationModal
callWindow.show()
}
}
Item{

View file

@ -8,6 +8,7 @@ Control.Popup {
clip: true
id: mainItem
signal buttonPressed(string text)
signal launchCall()
signal wipe()
closePolicy: Control.Popup.CloseOnEscape
leftPadding: closeButton.width
@ -61,7 +62,7 @@ Control.Popup {
implicitHeight: 40
background: Rectangle {
anchors.fill: parent
color: numPadButton.down ? DefaultStyle.numericPadPressedButtonColor : DefaultStyle.numericPadButtonColor
color: numPadButton.down ? DefaultStyle.numericPadPressedButtonColor : DefaultStyle.grey_0
radius: 20
}
contentItem: Text {
@ -91,7 +92,7 @@ Control.Popup {
implicitHeight: 40
background: Rectangle {
anchors.fill: parent
color: digitButton.down ? DefaultStyle.numericPadPressedButtonColor : DefaultStyle.numericPadButtonColor
color: digitButton.down ? DefaultStyle.numericPadPressedButtonColor : DefaultStyle.grey_0
radius: 20
}
contentItem: Item {
@ -131,7 +132,7 @@ Control.Popup {
bottomPadding: 15
background: Rectangle {
anchors.fill: parent
color: DefaultStyle.numericPadLaunchCallButtonColor
color: DefaultStyle.launchCallButtonColor
radius: 15
}
contentItem: EffectImage {
@ -142,8 +143,9 @@ Control.Popup {
width: 20
height: 20
image.fillMode: Image.PreserveAspectFit
effect.brightness: 1.0
colorizationColor: DefaultStyle.grey_0
}
onClicked: mainItem.launchCall()
}
Button {
leftPadding: 5

View file

@ -15,7 +15,7 @@ ColumnLayout {
visible: mainItem.label.length > 0
verticalAlignment: Text.AlignVCenter
text: mainItem.label
color: combobox.activeFocus ? DefaultStyle.formItemFocusBorderColor : DefaultStyle.formItemLabelColor
color: combobox.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.formItemLabelColor
font {
pointSize: DefaultStyle.formItemLabelSize
bold: true
@ -39,7 +39,7 @@ ColumnLayout {
? (mainItem.errorMessage.length > 0
? DefaultStyle.errorMessageColor
: textField.activeFocus
? DefaultStyle.formItemFocusBorderColor
? DefaultStyle.main1_500_main
: DefaultStyle.formItemBorderColor)
: "transparent"
}

View file

@ -19,7 +19,7 @@ ColumnLayout {
visible: label.length > 0
verticalAlignment: Text.AlignVCenter
text: mainItem.label + (mainItem.mandatory ? "*" : "")
color: (combobox.hasActiveFocus || textField.hasActiveFocus) ? DefaultStyle.formItemFocusBorderColor : DefaultStyle.formItemLabelColor
color: (combobox.hasActiveFocus || textField.hasActiveFocus) ? DefaultStyle.main1_500_main : DefaultStyle.formItemLabelColor
font {
pointSize: DefaultStyle.formItemLabelSize
bold: true
@ -34,7 +34,7 @@ ColumnLayout {
border.color: mainItem.errorMessage.length > 0
? DefaultStyle.errorMessageColor
: (textField.hasActiveFocus || combobox.hasActiveFocus)
? DefaultStyle.formItemFocusBorderColor
? DefaultStyle.main1_500_main
: DefaultStyle.formItemBorderColor
RowLayout {
anchors.fill: parent
@ -69,7 +69,7 @@ ColumnLayout {
elide: Text.ElideRight
wrapMode: Text.Wrap
font {
pointSize: DefaultStyle.defaultTextSize
pointSize: DefaultStyle.indicatorMessageTextSize
family: DefaultStyle.defaultFont
bold: true
}

View file

@ -0,0 +1,61 @@
import QtQuick 2.15
Item {
id: root
property int size: 150
property color borderColor
property color innerColor
width: size
height: size
onBorderColorChanged: c.requestPaint()
function requestPaint(animated) {
c.animated = animated
if (animated) animationTimer.restart()
else {
animationTimer.stop()
c.requestPaint()
}
}
Canvas {
id: c
property bool animated: false
property int offset: 0
anchors.fill: parent
antialiasing: true
onOffsetChanged: requestPaint()
Timer {
id: animationTimer
interval: 200
repeat: true
onTriggered: c.offset = (c.offset + 1)%360
}
onPaint: {
var ctx = getContext("2d");
ctx.reset()
ctx.setLineDash([3, 2]);
ctx.lineWidth = 2;
ctx.lineDashOffset = offset;
var x = root.width / 2;
var y = root.height / 2;
var radius = root.size / 2
var startAngle = (Math.PI / 180) * 270;
var fullAngle = (Math.PI / 180) * (270 + 360);
ctx.strokeStyle = root.borderColor;
ctx.fillStyle = root.innerColor;
ctx.beginPath();
ctx.arc(x, y, radius - 1, 0, 2 * Math.PI);
ctx.fill();
if (animated) {
ctx.stroke();
}
}
}
}

View file

@ -20,7 +20,7 @@ Control.RadioButton {
background: Rectangle {
color: DefaultStyle.formItemBackgroundColor
border.color: mainItem.checked ? DefaultStyle.radioButtonCheckedColor : "transparent"
border.color: mainItem.checked ? DefaultStyle.info_500_main : "transparent"
radius: 20
}
@ -34,7 +34,7 @@ Control.RadioButton {
implicitWidth: 16
implicitHeight: 16
radius: implicitWidth/2
border.color: mainItem.checked ? DefaultStyle.radioButtonCheckedColor : DefaultStyle.radioButtonUncheckedColor
border.color: mainItem.checked ? DefaultStyle.info_500_main : DefaultStyle.main1_500_main
Rectangle {
width: parent.width/2
@ -42,14 +42,14 @@ Control.RadioButton {
x: parent.width/4
y: parent.width/4
radius: width/2
color: DefaultStyle.radioButtonCheckedColor
color: DefaultStyle.info_500_main
visible: mainItem.checked
}
}
Text {
text: mainItem.title
font.bold: true
color: DefaultStyle.radioButtonTitleColor
color: DefaultStyle.grey_900
font.pointSize: DefaultStyle.radioButtonTitleSize
}
Control.Button {
@ -83,7 +83,7 @@ Control.RadioButton {
verticalAlignment: Text.AlignVCenter
Layout.preferredWidth: 220
Layout.preferredHeight: 100
font.pointSize: DefaultStyle.defaultTextSize
font.pointSize: DefaultStyle.descriptionTextSize
text: mainItem.contentText
Layout.fillHeight: true
}

View file

@ -1,9 +1,15 @@
import QtQuick 2.7
Rectangle {
function genRandomColor(){
return '#'+ Math.floor(Math.random()*255).toString(16)
+Math.floor(Math.random()*255).toString(16)
+Math.floor(Math.random()*255).toString(16)
}
anchors.fill: parent
color: "blue"
color: genRandomColor() //"blue"
opacity: 0.2
border.color: "green"
border.color: genRandomColor() //"red"
border.width: 2
}

View file

@ -8,11 +8,12 @@ Rectangle {
id: mainItem
property string placeholderText: ""
property int textInputWidth: 350
property var validator: RegularExpressionValidator{}
property color borderColor: "transparent"
property string text: textField.text
property var validator: RegularExpressionValidator{}
property var numericPad
property alias numericPadButton: dialerButton
readonly property bool hasActiveFocus: textField.activeFocus
property var numericPad
signal numericPadButtonPressed(bool checked)
onVisibleChanged: if (!visible && numericPad) numericPad.close()
@ -32,7 +33,7 @@ Rectangle {
implicitHeight: 30
radius: 20
color: DefaultStyle.formItemBackgroundColor
border.color: textField.activeFocus ? DefaultStyle.searchBarFocusBorderColor : "transparent"
border.color: textField.activeFocus ? DefaultStyle.searchBarFocusBorderColor : mainItem.borderColor
Image {
id: magnifier
anchors.left: parent.left
@ -43,9 +44,10 @@ Rectangle {
Control.TextField {
id: textField
anchors.left: magnifier.right
anchors.right: dialerButton.visible ? dialerButton.left : parent.right
anchors.right: clearTextButton.left
anchors.verticalCenter: parent.verticalCenter
placeholderText: mainItem.placeholderText
width: mainItem.width - dialerButton.width
echoMode: (mainItem.hidden && !dialerButton.checked) ? TextInput.Password : TextInput.Normal
font.family: DefaultStyle.defaultFont
font.pointSize: DefaultStyle.defaultFontPointSize
@ -57,13 +59,13 @@ Rectangle {
}
cursorDelegate: Rectangle {
visible: textField.activeFocus
color: DefaultStyle.formItemFocusBorderColor
color: DefaultStyle.main1_500_main
width: 2
}
}
Control.Button {
id: dialerButton
visible: numericPad != undefined
visible: numericPad != undefined && textField.text.length === 0
checkable: true
checked: false
background: Rectangle {
@ -82,4 +84,24 @@ Rectangle {
else mainItem.numericPad.close()
}
}
Control.Button {
id: clearTextButton
visible: textField.text.length > 0
checkable: true
checked: false
background: Rectangle {
color: "transparent"
}
contentItem: Image {
fillMode: Image.PreserveAspectFit
source: AppIcons.closeX
}
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.rightMargin: 10
onCheckedChanged: {
textField.clear()
}
}
}

View file

@ -25,7 +25,7 @@ Control.TabBar {
// Quick.Rectangle {
// height: 4
// color: DefaultStyle.orangeColor
// color: DefaultStyle.main1_500_main
// anchors.bottom: parent.bottom
// // anchors.left: mainItem.currentItem.left
// // anchors.right: mainItem.currentItem.right
@ -59,7 +59,7 @@ Control.TabBar {
Quick.Rectangle {
visible: mainItem.currentIndex === index
height: 4
color: DefaultStyle.orangeColor
color: DefaultStyle.main1_500_main
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right

View file

@ -32,7 +32,7 @@ ColumnLayout {
visible: mainItem.label.length > 0
verticalAlignment: Text.AlignVCenter
text: mainItem.label + (mainItem.mandatory ? "*" : "")
color: textField.activeFocus ? DefaultStyle.formItemFocusBorderColor : DefaultStyle.formItemLabelColor
color: textField.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.formItemLabelColor
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 1
@ -58,7 +58,7 @@ ColumnLayout {
? (mainItem.errorMessage.length > 0
? DefaultStyle.errorMessageColor
: textField.activeFocus
? DefaultStyle.formItemFocusBorderColor
? DefaultStyle.main1_500_main
: DefaultStyle.formItemBorderColor)
: "transparent"
Control.TextField {
@ -78,7 +78,7 @@ ColumnLayout {
}
cursorDelegate: Rectangle {
visible: textField.activeFocus
color: DefaultStyle.formItemFocusBorderColor
color: DefaultStyle.main1_500_main
width: 2
}
}
@ -107,7 +107,7 @@ ColumnLayout {
wrapMode: Text.Wrap
// maximumLineCount: 1
font {
pointSize: DefaultStyle.defaultTextSize
pointSize: DefaultStyle.indicatorMessageTextSize
family: DefaultStyle.defaultFont
bold: true
}

View file

@ -33,18 +33,18 @@ Control.TabBar {
anchors.fill: parent
Rectangle {
anchors.fill: parent
color: DefaultStyle.verticalTabBarColor
color: DefaultStyle.main1_500_main
radius: 25
}
Rectangle {
color: DefaultStyle.verticalTabBarColor
color: DefaultStyle.main1_500_main
anchors.left: parent.left
anchors.top: parent.top
width: parent.width/2
height: parent.height/2
}
Rectangle {
color: DefaultStyle.verticalTabBarColor
color: DefaultStyle.main1_500_main
x: parent.x + parent.width/2
y: parent.y + parent.height/2
width: parent.width/2
@ -69,9 +69,8 @@ Control.TabBar {
Layout.preferredWidth: buttonSize
Layout.preferredHeight: buttonSize
Layout.alignment: Qt.AlignHCenter
image.sourceSize.width: buttonSize
image.fillMode: Image.PreserveAspectFit
effect.brightness: 1.0
colorizationColor: DefaultStyle.grey_0
}
Text {
id: buttonText

View file

@ -26,7 +26,7 @@ LoginLayout {
Text {
Layout.rightMargin: 15
text: "No account yet ?"
font.pointSize: DefaultStyle.defaultTextSize
font.pointSize: DefaultStyle.indicatorMessageTextSize
}
Button {
Layout.alignment: Qt.AlignRight

View file

@ -85,7 +85,7 @@ LoginLayout {
Layout.rightMargin: 15
text: "Didn't receive the code ?"
color: DefaultStyle.questionTextColor
font.pointSize: DefaultStyle.defaultTextSize
font.pointSize: DefaultStyle.indicatorMessageTextSize
}
Button {
Layout.alignment: Qt.AlignRight

View file

@ -31,7 +31,7 @@ LoginLayout {
Layout.rightMargin: 15
color: DefaultStyle.questionTextColor
text: "Already have an account ?"
font.pointSize: DefaultStyle.defaultTextSize
font.pointSize: DefaultStyle.indicatorMessageTextSize
}
Button {
// Layout.alignment: Qt.AlignRight
@ -87,7 +87,7 @@ LoginLayout {
Text {
text: "The password must contain 6 characters minimum"
font {
pointSize: DefaultStyle.defaultTextSize
pointSize: DefaultStyle.indicatorMessageTextSize
}
}
}
@ -101,7 +101,7 @@ LoginLayout {
Text {
text: "The password must contain 6 characters minimum"
font {
pointSize: DefaultStyle.defaultTextSize
pointSize: DefaultStyle.indicatorMessageTextSize
}
}
}
@ -176,7 +176,7 @@ LoginLayout {
Text {
text: "The password must contain 6 characters minimum"
font {
pointSize: DefaultStyle.defaultTextSize
pointSize: DefaultStyle.indicatorMessageTextSize
}
}
}
@ -191,7 +191,7 @@ LoginLayout {
Text {
text: "The password must contain 6 characters minimum"
font {
pointSize: DefaultStyle.defaultTextSize
pointSize: DefaultStyle.indicatorMessageTextSize
}
}
}

View file

@ -41,7 +41,7 @@ LoginLayout {
Text {
Layout.rightMargin: 15
text: "No account yet ?"
font.pointSize: DefaultStyle.defaultTextSize
font.pointSize: DefaultStyle.indicatorMessageTextSize
}
Button {
Layout.alignment: Qt.AlignRight
@ -75,7 +75,7 @@ LoginLayout {
width: rootStackView.width
wrapMode: Text.WordWrap
color: DefaultStyle.darkGrayColor
font.pointSize: DefaultStyle.defaultTextSize
font.pointSize: DefaultStyle.descriptionTextSize
text: "<p>Some features require a Linphone account, such as group messaging, video conferences...</p>
<p>These features are hidden when you register with a third party SIP account.</p>
<p>To enable it in a commercial projet, please contact us. </p>"

View file

@ -15,9 +15,9 @@ Item {
property string newItemIconSource
property string emptyListText
property alias leftPanelContent: leftPanel.children
property Component rightPanelContent
property var rightPanelContent: rightPanelItem.children
property bool showDefaultItem: true
onShowDefaultItemChanged: stackView.replace(showDefaultItem ? defaultItem : rightPanel)
// onShowDefaultItemChanged: stackView.replace(showDefaultItem ? defaultItem : rightPanelItem)
signal noItemButtonPressed()
Control.SplitView {
@ -37,64 +37,74 @@ Item {
id: rightPanel
clip: true
color: DefaultStyle.mainPageRightPanelBackgroundColor
Control.StackView {
id: stackView
initialItem: defaultItem
StackLayout {
currentIndex: mainItem.showDefaultItem ? 0 : 1
anchors.fill: parent
Layout.alignment: Qt.AlignCenter
}
Component {
id: defaultItem
ColumnLayout {
Item {
Layout.fillHeight: true
}
ColumnLayout {
id: defaultItem
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
visible: mainItem.showDefaultItem
// anchors.centerIn: parent
Layout.alignment: Qt.AlignHCenter
spacing: 25
Image {
Layout.alignment: Qt.AlignHCenter
source: AppIcons.noItemImage
Layout.preferredWidth: 250
Layout.preferredHeight: 250
fillMode: Image.PreserveAspectFit
Item {
Layout.fillWidth: true
}
Text {
text: mainItem.emptyListText
Layout.alignment: Qt.AlignHCenter
font.bold: true
}
Button {
Layout.alignment: Qt.AlignHCenter
contentItem: RowLayout {
Layout.alignment: Qt.AlignVCenter
EffectImage {
effect.brightness: 1
image.source: mainItem.newItemIconSource
image.width: 20
image.fillMode: Image.PreserveAspectFit
}
Text {
text: mainItem.noItemButtonText
wrapMode: Text.WordWrap
color: DefaultStyle.buttonTextColor
font {
bold: true
pointSize: DefaultStyle.buttonTextSize
family: DefaultStyle.defaultFont
ColumnLayout {
Item {
Layout.fillHeight: true
}
Image {
Layout.alignment: Qt.AlignHCenter
source: AppIcons.noItemImage
Layout.preferredWidth: 250
Layout.preferredHeight: 250
fillMode: Image.PreserveAspectFit
}
Text {
text: mainItem.emptyListText
Layout.alignment: Qt.AlignHCenter
font.bold: true
}
Button {
Layout.alignment: Qt.AlignHCenter
contentItem: RowLayout {
Layout.alignment: Qt.AlignVCenter
EffectImage {
colorizationColor: DefaultStyle.grey_0
image.source: mainItem.newItemIconSource
image.width: 20
image.fillMode: Image.PreserveAspectFit
}
Text {
text: mainItem.noItemButtonText
wrapMode: Text.WordWrap
color: DefaultStyle.grey_0
font {
bold: true
pointSize: DefaultStyle.buttonTextSize
family: DefaultStyle.defaultFont
}
}
}
onPressed: mainItem.noItemButtonPressed()
}
onPressed: mainItem.noItemButtonPressed()
Item {
Layout.fillHeight: true
}
}
Item {
Layout.fillWidth: true
}
}
Item {
Layout.fillHeight: true
}
}
Item {
id: rightPanelItem
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}

View file

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtQuick.Layouts
import QtQuick.Controls as Control
import Linphone
import UtilsCpp 1.0
AbstractMainPage {
id: mainItem
@ -16,10 +17,12 @@ AbstractMainPage {
Layout.fillHeight: true
Control.StackView {
id: listStackView
clip: true
initialItem: listItem
anchors.fill: parent
anchors.leftMargin: 25
anchors.rightMargin: 25
property int sideMargin: 25
// anchors.leftMargin: 25
// anchors.rightMargin: 25
}
Component {
id: listItem
@ -27,6 +30,8 @@ AbstractMainPage {
ColumnLayout {
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: listStackView.sideMargin
Layout.rightMargin: listStackView.sideMargin
Text {
text: qsTr("Appels")
color: DefaultStyle.mainPageTitleColor
@ -65,6 +70,8 @@ AbstractMainPage {
id: listLayout
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: listStackView.sideMargin
Layout.rightMargin: listStackView.sideMargin
background: Rectangle {
anchors.fill: parent
@ -148,6 +155,11 @@ AbstractMainPage {
onCountChanged: mainItem.showDefaultItem = listView.count === 0
Connections {
target: mainItem
onShowDefaultItemChanged: mainItem.showDefaultItem = mainItem.showDefaultItem && listView.count === 0
}
Control.ScrollIndicator.vertical: Control.ScrollIndicator { }
}
}
@ -158,7 +170,13 @@ AbstractMainPage {
Component {
id: newCallItem
ColumnLayout {
Control.StackView.onActivating: {
mainItem.showDefaultItem = false
}
Control.StackView.onDeactivating: mainItem.showDefaultItem = true
RowLayout {
Layout.leftMargin: listStackView.sideMargin
Layout.rightMargin: listStackView.sideMargin
Control.Button {
background: Item {
}
@ -180,74 +198,26 @@ AbstractMainPage {
Layout.fillWidth: true
}
}
Control.Control {
id: listLayout
ContactsList {
Layout.fillWidth: true
Layout.fillHeight: true
background: Rectangle {
anchors.fill: parent
}
ColumnLayout {
anchors.fill: parent
SearchBar {
id: searchBar
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
placeholderText: qsTr("Rechercher un appel")
numericPad: numPad
}
Button {
Layout.fillWidth: true
leftPadding: 0
topPadding: 0
rightPadding: 0
bottomPadding: 0
background: Rectangle {
color: DefaultStyle.groupCallButtonColor
anchors.fill: parent
radius: 50
}
contentItem: RowLayout {
Image {
source: AppIcons.groupCall
Layout.preferredWidth: 35
sourceSize.width: 35
fillMode: Image.PreserveAspectFit
}
Text {
text: "Appel de groupe"
font.bold: true
}
Item {
Layout.fillWidth: true
}
Image {
source: AppIcons.rightArrow
}
}
}
ColumnLayout {
ListView {
Layout.fillHeight: true
// call history
}
}
Layout.maximumWidth: parent.width
groupCallVisible: true
searchBarColor: DefaultStyle.contactListSearchBarColor
onCallButtonPressed: (address) => {
var addressEnd = "@sip.linphone.org"
if (!address.endsWith(addressEnd)) address += addressEnd
var callVarObject = UtilsCpp.createCall(address)
// var windowComp = Qt.createComponent("CallsWindow.qml")
// var call = callVarObject.value
// var callWindow = windowComp.createObject(null, {callVarObject: callVarObject})
// callWindow.modality = Qt.ApplicationModal
// callWindow.show()
}
}
}
}
Item {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: numPad.height
NumericPad {
id: numPad
// anchors.centerIn: parent
width: parent.width
}
}
}
rightPanelContent: ColumnLayout {

View file

@ -25,6 +25,7 @@ QtObject {
property string phoneSelected: "image://internal/phone-selected.svg"
property string newCall: "image://internal/phone-plus.svg"
property string endCall: "image://internal/phone-disconnect.svg"
property string transferCall: "image://internal/phone-transfer.svg"
property string adressBook: "image://internal/address-book.svg"
property string adressBookSelected: "image://internal/address-book-selected.svg"
property string chatTeardropText: "image://internal/chat-teardrop-text.svg"
@ -44,5 +45,15 @@ QtObject {
property string incomingCallRejected: "image://internal/incoming_call_rejected.svg"
property string outgoingCall: "image://internal/outgoing_call.svg"
property string outgoingCallMissed: "image://internal/outgoing_call_missed.svg"
property string outgoingCallRejected: "image://internal/outgoing_call_rejected.svg"
property string microphone: "image://internal/microphone.svg"
property string microphoneSlash: "image://internal/microphone-slash.svg"
property string videoCamera: "image://internal/video-camera.svg"
property string videoCameraSlash: "image://internal/video-camera-slash.svg"
property string speaker: "image://internal/speaker-high.svg"
property string speakerSlash: "image://internal/speaker-slash.svg"
property string trusted: "image://internal/trusted.svg"
property string avatar: "image://internal/randomAvatar.png"
property string pause: "image://internal/pause.svg"
property string play: "image://internal/play.svg"
property string smiley: "image://internal/smiley.svg"
}

View file

@ -2,21 +2,34 @@ pragma Singleton
import QtQuick 2.15
QtObject {
property color main1_500_main: "#FE5E00"
property color main2_0: "#FAFEFF"
property color main2_100: "#EEF6F8"
property color main2_200: "#DFECF2"
property color main2_300: "#C0D1D9"
property color main2_400: "#9AABB5"
property color main2_500main: "#6C7A87"
property color main2_700: "#364860"
property color warning_600: "#DBB820"
property color grey_0: "#FFFFFF"
property color grey_500: "#4E4E4E"
property color grey_600: "#2E3030"
property color grey_900: "#070707"
property color danger_500: "#DD5F5F"
property color info_500_main: "#4AA8FF"
property color success_500main: "#4FAE80"
property string emojiFont: "Noto Color Emoji"
property color orangeColor: "#FE5E00"
property color buttonBackground: "#FE5E00"
property color buttonPressedBackground: "#c74b02"
property color buttonInversedBackground: "white"
property color buttonPressedInversedBackground: "#fff1e8"
property color buttonTextColor: "white"
property color radioButtonCheckedColor: "#4AA8FF"
property color radioButtonUncheckedColor: "#FE5E00"
property color radioButtonTitleColor: "#070707"
property int radioButtonTextSize: 8
property int radioButtonTitleSize: 9
property color buttonInversedTextColor: "#FE5E00"
property color checkboxBorderColor: "#FE5E00"
property double checkboxBorderWidth: 2
property int buttonTextSize: 10
property color carouselLightGrayColor: "#DFECF2"
@ -26,10 +39,8 @@ QtObject {
property color formItemDisableBackgroundColor: "#EDEDED"
property color formItemBackgroundColor: "#F9F9F9"
property color formItemBorderColor: "#EDEDED"
property color formItemFocusBorderColor: "#FE5E00"
property int tabButtonTextSize: 11
property color verticalTabBarColor: "#FE5E00"
property color verticalTabBarTextColor: "white"
property int verticalTabButtonTextSize: 6
@ -43,7 +54,6 @@ QtObject {
property color tooltipBackgroundColor: "#DFECF2"
property color digitInputFocusedColor: "#FE5E00"
property color digitInputColor: "#6C7A87"
property color darkBlueColor: "#22334D"
@ -53,7 +63,8 @@ QtObject {
property color defaultTextColor: "#4E6074"
property color disableTextColor: "#9AABB5"
property int defaultTextSize: 7
property int descriptionTextSize: 7
property int indicatorMessageTextSize: 7
property string defaultFont: "Noto Sans"
property int defaultFontPointSize: 10
property int title1FontPointSize: 50
@ -65,29 +76,30 @@ QtObject {
property int mainPageTitleSize: 15
property color searchBarFocusBorderColor: "#6C7A87"
property color contactListSearchBarColor: "#F9F9F9"
property color callRightPanelSearchBarBorderColor: "#EDEDED"
property color numericPadBackgroundColor: "#F9F9F9"
property color numericPadShadowColor: Qt.rgba(0.0, 0.0, 0.0, 0.1)
property color numericPadButtonColor: "#FFFFFF"
property int numericPadButtonTextSize: 15
property int numericPadButtonSubtextSize: 6
property color numericPadPressedButtonColor: "#EEF7F8"
property color numericPadLaunchCallButtonColor: "#4FAE80"
property color groupCallButtonColor: "#EEF7F8"
property color launchCallButtonColor: "#4FAE80"
property color callCheckedButtonColor: "#9AABB5"
property color splitViewHandleColor: "#F9F9F9"
property color splitViewHoveredHandleColor: "#EDEDED"
property color danger_500main: "#DD5F5F"
property color grey_0: "#FFFFFF"
property color success_500main: "#4FAE80"
property color warning_600: "#DBB820"
property color main2_200: "#DFECF2"
property color main2_300: "#C0D1D9"
property color main2_400: "#9AABB5"
property color main2_700: "#364860"
property color main2_500main: "#6C7A87"
property color ongoingCallWindowColor: "#000000"
property color ongoingCallBackgroundColor: "#2E3030"
property int ongoingCallElapsedTimeSize: 15
property int ongoingCallNameSize: 10
property int ongoingCallAddressSize: 6
property color callRightPanelBackgroundColor: "#F9F9F9"
property color defaultAvatarBackgroundColor: "#DFECF2"
property double dp: 1.0//0.66
}