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 <QFileSelector>
#include <QGuiApplication> #include <QGuiApplication>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QQmlComponent>
#include <QQmlContext> #include <QQmlContext>
#include <QQmlFileSelector> #include <QQmlFileSelector>
#include <QQuickWindow>
#include <QTimer> #include <QTimer>
#include "core/account/AccountCore.hpp" #include "core/account/AccountCore.hpp"
@ -45,11 +47,14 @@
#include "core/singleapplication/singleapplication.h" #include "core/singleapplication/singleapplication.h"
#include "model/object/VariantObject.hpp" #include "model/object/VariantObject.hpp"
#include "tool/Constants.hpp" #include "tool/Constants.hpp"
#include "tool/EnumsToString.hpp"
#include "tool/Utils.hpp" #include "tool/Utils.hpp"
#include "tool/providers/AvatarProvider.hpp" #include "tool/providers/AvatarProvider.hpp"
#include "tool/providers/ImageProvider.hpp" #include "tool/providers/ImageProvider.hpp"
#include "tool/thread/Thread.hpp" #include "tool/thread/Thread.hpp"
DEFINE_ABSTRACT_OBJECT(App)
App::App(int &argc, char *argv[]) App::App(int &argc, char *argv[])
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) { : SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
mLinphoneThread = new Thread(this); mLinphoneThread = new Thread(this);
@ -83,7 +88,7 @@ void App::init() {
if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true); if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true);
if (!mLinphoneThread->isRunning()) { if (!mLinphoneThread->isRunning()) {
qDebug() << "[App] Starting Thread"; qDebug() << log().arg("Starting Thread");
mLinphoneThread->start(); mLinphoneThread->start();
} }
setQuitOnLastWindowClosed(true); // TODO: use settings to set it 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"); if (version.majorVersion() == 5 && version.minorVersion() == 9) selectors.push_back("5.9");
auto selector = new QQmlFileSelector(mEngine, mEngine); auto selector = new QQmlFileSelector(mEngine, mEngine);
selector->setExtraSelectors(selectors); selector->setExtraSelectors(selectors);
qInfo() << QStringLiteral("[App] Activated selectors:") << selector->selector()->allSelectors(); qInfo() << log().arg("Activated selectors:") << selector->selector()->allSelectors();
mEngine->addImportPath(":/"); mEngine->addImportPath(":/");
mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath()); mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath());
@ -110,9 +115,9 @@ void App::init() {
const QUrl url(u"qrc:/Linphone/view/App/Main.qml"_qs); const QUrl url(u"qrc:/Linphone/view/App/Main.qml"_qs);
QObject::connect( QObject::connect(
mEngine, &QQmlApplicationEngine::objectCreated, this, mEngine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) { [this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == 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); exit(-1);
} }
}, },
@ -129,6 +134,9 @@ void App::initCppInterfaces() {
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Constants(engine); }); [](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Constants(engine); });
qmlRegisterSingletonType<Utils>("UtilsCpp", 1, 0, "UtilsCpp", qmlRegisterSingletonType<Utils>("UtilsCpp", 1, 0, "UtilsCpp",
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Utils(engine); }); [](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<PhoneNumberProxy>(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy");
qmlRegisterType<VariantObject>(Constants::MainQmlUri, 1, 0, "VariantObject"); qmlRegisterType<VariantObject>(Constants::MainQmlUri, 1, 0, "VariantObject");
@ -189,9 +197,70 @@ bool App::notify(QObject *receiver, QEvent *event) {
try { try {
done = QApplication::notify(receiver, event); done = QApplication::notify(receiver, event);
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
qCritical() << "[App] Exception has been catch in notify"; qCritical() << log().arg("Exception has been catch in notify");
} catch (...) { } catch (...) {
qCritical() << "[App] Generic exeption has been catch in notify"; qCritical() << log().arg("Generic exeption has been catch in notify");
} }
return done; 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 "core/singleapplication/singleapplication.h"
#include "model/core/CoreModel.hpp" #include "model/core/CoreModel.hpp"
#include "tool/AbstractObject.hpp"
class CallGui;
class Thread; class Thread;
class Notifier; class Notifier;
class QQuickWindow;
class App : public SingleApplication { class App : public SingleApplication, public AbstractObject {
public: public:
App(int &argc, char *argv[]); App(int &argc, char *argv[]);
static App *getInstance(); static App *getInstance();
@ -53,6 +56,20 @@ public:
QMetaObject::invokeMethod(App::getInstance(), callable); QMetaObject::invokeMethod(App::getInstance(), callable);
} }
template <typename Func, typename... Args> 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) { static auto postModelSync(Func &&callable, Args &&...args) {
if (QThread::currentThread() != CoreModel::getInstance()->thread()) { if (QThread::currentThread() != CoreModel::getInstance()->thread()) {
bool end = false; bool end = false;
@ -73,8 +90,13 @@ public:
void onLoggerInitialized(); void onLoggerInitialized();
QQuickWindow *getCallsWindow(QVariant callGui);
void closeCallsWindow();
Q_INVOKABLE static void smartShowWindow(QQuickWindow *window);
QQmlApplicationEngine *mEngine = nullptr; QQmlApplicationEngine *mEngine = nullptr;
bool notify(QObject *receiver, QEvent *event); bool notify(QObject *receiver, QEvent *event) override;
enum class StatusCode { gRestartCode = 1000, gDeleteDataCode = 1001 }; enum class StatusCode { gRestartCode = 1000, gDeleteDataCode = 1001 };
@ -84,4 +106,9 @@ private:
QCommandLineParser *mParser = nullptr; QCommandLineParser *mParser = nullptr;
Thread *mLinphoneThread = nullptr; Thread *mLinphoneThread = nullptr;
Notifier *mNotifier = 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 "CallCore.hpp"
#include "core/App.hpp" #include "core/App.hpp"
#include "model/object/VariantObject.hpp" #include "model/object/VariantObject.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp" #include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.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); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
// Should be call from model Thread // Should be call from model Thread
mustBeInLinphoneThread(getClassName()); mustBeInLinphoneThread(getClassName());
mDir = LinphoneEnums::fromLinphone(call->getDir());
mCallModel = Utils::makeQObject_ptr<CallModel>(call);
mCallModel->setSelf(mCallModel);
mDuration = call->getDuration(); mDuration = call->getDuration();
mMicrophoneMuted = call->getMicrophoneMuted(); mMicrophoneMuted = call->getMicrophoneMuted();
mCallModel = Utils::makeQObject_ptr<CallModel>(call); // mSpeakerMuted = call->getSpeakerMuted();
connect(mCallModel.get(), &CallModel::stateChanged, this, &CallCore::onStateChanged); mCameraEnabled = call->cameraEnabled();
connect(this, &CallCore::lAccept, mCallModel.get(), &CallModel::accept); mDuration = call->getDuration();
connect(this, &CallCore::lDecline, mCallModel.get(), &CallModel::decline);
connect(this, &CallCore::lTerminate, mCallModel.get(), &CallModel::terminate);
mCallModel->setSelf(mCallModel);
mState = LinphoneEnums::fromLinphone(call->getState()); 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() { CallCore::~CallCore() {
@ -65,9 +76,72 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::microphoneMutedChanged, [this](bool isMuted) { mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::microphoneMutedChanged, [this](bool isMuted) {
mAccountModelConnection->invokeToCore([this, isMuted]() { setMicrophoneMuted(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->makeConnect(mCallModel.get(), &CallModel::durationChanged, [this](int duration) {
mAccountModelConnection->invokeToCore([this, duration]() { setDuration(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 { 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 { LinphoneEnums::CallState CallCore::getState() const {
return mState; 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 { QString CallCore::getLastErrorMessage() const {
return mLastErrorMessage; return mLastErrorMessage;
} }
@ -130,3 +212,47 @@ void CallCore::setMicrophoneMuted(bool isMuted) {
emit microphoneMutedChanged(); 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 { class CallCore : public QObject, public AbstractObject {
Q_OBJECT Q_OBJECT
// Q_PROPERTY(QString peerDisplayName MEMBER mPeerDisplayName)
Q_PROPERTY(LinphoneEnums::CallStatus status READ getStatus NOTIFY statusChanged) 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(LinphoneEnums::CallState state READ getState NOTIFY stateChanged)
Q_PROPERTY(QString lastErrorMessage READ getLastErrorMessage NOTIFY lastErrorMessageChanged) 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 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: public:
// Should be call from model Thread. Will be automatically in App thread after initialization // Should be call from model Thread. Will be automatically in App thread after initialization
@ -48,9 +55,11 @@ public:
LinphoneEnums::CallStatus getStatus() const; LinphoneEnums::CallStatus getStatus() const;
void setStatus(LinphoneEnums::CallStatus status); void setStatus(LinphoneEnums::CallStatus status);
LinphoneEnums::CallDir getDir() const;
void setDir(LinphoneEnums::CallDir dir);
LinphoneEnums::CallState getState() const; LinphoneEnums::CallState getState() const;
void setState(LinphoneEnums::CallState state, const QString &message); void setState(LinphoneEnums::CallState state, const QString &message);
void onStateChanged(linphone::Call::State state, const std::string &message);
QString getLastErrorMessage() const; QString getLastErrorMessage() const;
void setLastErrorMessage(const QString &message); void setLastErrorMessage(const QString &message);
@ -61,18 +70,39 @@ public:
bool getMicrophoneMuted() const; bool getMicrophoneMuted() const;
void setMicrophoneMuted(bool isMuted); 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: signals:
void statusChanged(LinphoneEnums::CallStatus status); void statusChanged(LinphoneEnums::CallStatus status);
void stateChanged(LinphoneEnums::CallState state); void stateChanged(LinphoneEnums::CallState state);
void dirChanged(LinphoneEnums::CallDir dir);
void lastErrorMessageChanged(); void lastErrorMessageChanged();
void peerAddressChanged();
void durationChanged(int duration); void durationChanged(int duration);
void microphoneMutedChanged(); void microphoneMutedChanged();
void cameraEnabledChanged();
void pausedChanged();
void transferStateChanged();
void peerSecuredChanged();
// Linphone commands // Linphone commands
void lAccept(bool withVideo); // Accept an incoming call void lAccept(bool withVideo); // Accept an incoming call
void lDecline(); // Decline an incoming call void lDecline(); // Decline an incoming call
void lTerminate(); // Hangup a call void lTerminate(); // Hangup a call
void lSetMicrophoneMuted(bool isMuted); void lSetMicrophoneMuted(bool isMuted);
void lSetCameraEnabled(bool enabled);
void lSetPaused(bool paused);
void lTransferCall(const QString &dest);
/* TODO /* TODO
Q_INVOKABLE void acceptWithVideo(); Q_INVOKABLE void acceptWithVideo();
@ -99,9 +129,15 @@ private:
std::shared_ptr<CallModel> mCallModel; std::shared_ptr<CallModel> mCallModel;
LinphoneEnums::CallStatus mStatus; LinphoneEnums::CallStatus mStatus;
LinphoneEnums::CallState mState; LinphoneEnums::CallState mState;
LinphoneEnums::CallState mTransferState;
LinphoneEnums::CallDir mDir;
QString mLastErrorMessage; QString mLastErrorMessage;
QString mPeerAddress;
bool mPeerSecured;
int mDuration = 0; int mDuration = 0;
bool mMicrophoneMuted; bool mMicrophoneMuted;
bool mCameraEnabled;
bool mPaused = false;
QSharedPointer<SafeConnection> mAccountModelConnection; QSharedPointer<SafeConnection> mAccountModelConnection;
DECLARE_ABSTRACT_OBJECT 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-selected.svg"
"data/image/phone-plus.svg" "data/image/phone-plus.svg"
"data/image/phone-disconnect.svg" "data/image/phone-disconnect.svg"
"data/image/phone-transfer.svg"
"data/image/address-book.svg" "data/image/address-book.svg"
"data/image/address-book-selected.svg" "data/image/address-book-selected.svg"
"data/image/chat-teardrop-text.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/magnifying-glass.svg"
"data/image/backspace-fill.svg" "data/image/backspace-fill.svg"
"data/image/x.svg" "data/image/x.svg"
"data/image/play.svg"
"data/image/incoming_call.svg" "data/image/incoming_call.svg"
"data/image/incoming_call_missed.svg" "data/image/incoming_call_missed.svg"
"data/image/incoming_call_rejected.svg" "data/image/incoming_call_rejected.svg"
"data/image/outgoing_call.svg" "data/image/outgoing_call.svg"
"data/image/outgoing_call_missed.svg" "data/image/outgoing_call_missed.svg"
"data/image/outgoing_call_rejected.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.vert.qsb
data/shaders/roundEffect.frag.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 <QDebug>
#include "model/core/CoreModel.hpp" #include "model/core/CoreModel.hpp"
#include "tool/Utils.hpp"
DEFINE_ABSTRACT_OBJECT(CallModel) DEFINE_ABSTRACT_OBJECT(CallModel)
@ -41,7 +42,6 @@ CallModel::~CallModel() {
void CallModel::accept(bool withVideo) { void CallModel::accept(bool withVideo) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto core = CoreModel::getInstance()->getCore(); auto core = CoreModel::getInstance()->getCore();
auto params = core->createCallParams(mMonitor); auto params = core->createCallParams(mMonitor);
params->enableVideo(withVideo); params->enableVideo(withVideo);
@ -68,12 +68,53 @@ void CallModel::terminate() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
mMonitor->terminate(); 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) { void CallModel::setMicrophoneMuted(bool isMuted) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
mMonitor->setMicrophoneMuted(isMuted); mMonitor->setMicrophoneMuted(isMuted);
emit microphoneMutedChanged(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) { void CallModel::onDtmfReceived(const std::shared_ptr<linphone::Call> &call, int dtmf) {
emit dtmfReceived(call, dtmf); emit dtmfReceived(call, dtmf);
} }
@ -108,6 +149,14 @@ void CallModel::onStateChanged(const std::shared_ptr<linphone::Call> &call,
emit stateChanged(state, message); 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, void CallModel::onStatsUpdated(const std::shared_ptr<linphone::Call> &call,
const std::shared_ptr<const linphone::CallStats> &stats) { const std::shared_ptr<const linphone::CallStats> &stats) {
emit statsUpdated(call, stats); emit statsUpdated(call, stats);

View file

@ -41,10 +41,20 @@ public:
void terminate(); void terminate();
void setMicrophoneMuted(bool isMuted); 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: signals:
void microphoneMutedChanged(bool isMuted); void microphoneMutedChanged(bool isMuted);
void speakerMutedChanged(bool isMuted);
void cameraEnabledChanged(bool enabled);
void durationChanged(int); void durationChanged(int);
void pausedChanged(bool paused);
private: private:
QTimer mDurationTimer; QTimer mDurationTimer;
@ -67,6 +77,8 @@ private:
virtual void onStateChanged(const std::shared_ptr<linphone::Call> &call, virtual void onStateChanged(const std::shared_ptr<linphone::Call> &call,
linphone::Call::State state, linphone::Call::State state,
const std::string &message) override; 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, virtual void onStatsUpdated(const std::shared_ptr<linphone::Call> &call,
const std::shared_ptr<const linphone::CallStats> &stats) override; const std::shared_ptr<const linphone::CallStats> &stats) override;
virtual void onTransferStateChanged(const std::shared_ptr<linphone::Call> &call, virtual void onTransferStateChanged(const std::shared_ptr<linphone::Call> &call,
@ -94,6 +106,8 @@ signals:
void infoMessageReceived(const std::shared_ptr<linphone::Call> &call, void infoMessageReceived(const std::shared_ptr<linphone::Call> &call,
const std::shared_ptr<const linphone::InfoMessage> &message); const std::shared_ptr<const linphone::InfoMessage> &message);
void stateChanged(linphone::Call::State state, const std::string &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, void statsUpdated(const std::shared_ptr<linphone::Call> &call,
const std::shared_ptr<const linphone::CallStats> &stats); const std::shared_ptr<const linphone::CallStats> &stats);
void transferStateChanged(const std::shared_ptr<linphone::Call> &call, linphone::Call::State state); void transferStateChanged(const std::shared_ptr<linphone::Call> &call, linphone::Call::State state);

View file

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

View file

@ -1,5 +1,6 @@
list(APPEND _LINPHONEAPP_SOURCES list(APPEND _LINPHONEAPP_SOURCES
tool/Constants.cpp tool/Constants.cpp
tool/EnumsToString.cpp
tool/Utils.cpp tool/Utils.cpp
tool/LinphoneEnums.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) { linphone::Conference::Layout LinphoneEnums::toLinphone(const LinphoneEnums::ConferenceLayout &layout) {
if (layout != LinphoneEnums::ConferenceLayout::AudioOnly) return static_cast<linphone::Conference::Layout>(layout); if (layout != LinphoneEnums::ConferenceLayout::AudioOnly) return static_cast<linphone::Conference::Layout>(layout);
else return linphone::Conference::Layout::Grid; // Audio Only mode 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); LinphoneEnums::CallStatus fromLinphone(const linphone::Call::Status &data);
QString toString(const LinphoneEnums::CallStatus &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 { enum class ConferenceLayout {
Grid = int(linphone::Conference::Layout::Grid), Grid = int(linphone::Conference::Layout::Grid),
ActiveSpeaker = int(linphone::Conference::Layout::ActiveSpeaker), ActiveSpeaker = int(linphone::Conference::Layout::ActiveSpeaker),

View file

@ -27,6 +27,7 @@
#include "model/tool/ToolModel.hpp" #include "model/tool/ToolModel.hpp"
#include "tool/providers/AvatarProvider.hpp" #include "tool/providers/AvatarProvider.hpp"
#include <QImageReader> #include <QImageReader>
#include <QQuickWindow>
// ============================================================================= // =============================================================================
@ -81,7 +82,13 @@ VariantObject *Utils::createCall(const QString &sipAddress,
data->makeRequest([sipAddress, prepareTransfertAddress, headers]() { data->makeRequest([sipAddress, prepareTransfertAddress, headers]() {
auto call = ToolModel::createCall(sipAddress, prepareTransfertAddress, headers); auto call = ToolModel::createCall(sipAddress, prepareTransfertAddress, headers);
if (call) { 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(); } else return QVariant();
}); });
data->requestValue(); data->requestValue();
@ -89,6 +96,10 @@ VariantObject *Utils::createCall(const QString &sipAddress,
return data; return data;
} }
void Utils::closeCallsWindow() {
App::getInstance()->closeCallsWindow();
}
VariantObject *Utils::haveAccount() { VariantObject *Utils::haveAccount() {
VariantObject *result = new VariantObject(); VariantObject *result = new VariantObject();
@ -134,3 +145,36 @@ QString Utils::createAvatar(const QUrl &fileUrl) {
} }
return fileUri; 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 // if defined(__GNUC__) && __GNUC__ >= 7
#endif // ifndef UTILS_NO_BREAK #endif // ifndef UTILS_NO_BREAK
class QQuickWindow;
class VariantObject; class VariantObject;
class Utils : public QObject { class Utils : public QObject {
@ -53,8 +54,10 @@ public:
Q_INVOKABLE static VariantObject *createCall(const QString &sipAddress, Q_INVOKABLE static VariantObject *createCall(const QString &sipAddress,
const QString &prepareTransfertAddress = "", const QString &prepareTransfertAddress = "",
const QHash<QString, QString> &headers = {}); const QHash<QString, QString> &headers = {});
Q_INVOKABLE static void closeCallsWindow();
Q_INVOKABLE static VariantObject *haveAccount(); Q_INVOKABLE static VariantObject *haveAccount();
Q_INVOKABLE static QString createAvatar(const QUrl &fileUrl); // Return the avatar path 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) { static inline QString coreStringToAppString(const std::string &str) {
if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return QString::fromStdString(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/"; QString path = ":/data/image/";
QStringList filters; QStringList filters;
filters << "*.svg"; filters << "*.svg";
filters << "*.png";
QDir imageDir(path); QDir imageDir(path);
if (!imageDir.exists()) { if (!imageDir.exists()) {
qDebug() << QStringLiteral("[ImageProvider] Dir doesn't exist: `%1`.").arg(path); 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 { Window {
id: mainWindow id: mainWindow
width: 1512 * DefaultStyle.dp width: 1512 * DefaultStyle.dp
height: 930 * DefaultStyle.dp height: 982 * DefaultStyle.dp
visible: true visible: true
title: qsTr("Linphone") title: qsTr("Linphone")
property bool firstConnection: true property bool firstConnection: true

View file

@ -1,11 +1,21 @@
list(APPEND _LINPHONEAPP_QML_FILES list(APPEND _LINPHONEAPP_QML_FILES
view/App/Main.qml view/App/Main.qml
view/App/CallsWindow.qml
view/App/Layout/LoginLayout.qml view/App/Layout/LoginLayout.qml
view/App/Layout/MainLayout.qml view/App/Layout/MainLayout.qml
view/Item/Account/Accounts.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/Button.qml
view/Item/Carousel.qml view/Item/Carousel.qml
view/Item/CheckBox.qml view/Item/CheckBox.qml
@ -17,10 +27,6 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Item/DesktopPopup.qml view/Item/DesktopPopup.qml
view/Item/DigitInput.qml view/Item/DigitInput.qml
view/Item/Notification/Notification.qml
view/Item/Notification/NotificationReceivedCall.qml
view/Item/EffectImage.qml view/Item/EffectImage.qml
view/Item/NumericPad.qml view/Item/NumericPad.qml
view/Item/PhoneNumberComboBox.qml view/Item/PhoneNumberComboBox.qml
@ -48,7 +54,6 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Page/Main/AbstractMainPage.qml view/Page/Main/AbstractMainPage.qml
view/Page/Main/CallPage.qml view/Page/Main/CallPage.qml
# view/Page/Main/OngoingCallPage.qml
# Prototypes # Prototypes
view/Prototype/PhoneNumberPrototype.qml 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 color: inversedColors
? mainItem.pressed ? mainItem.pressed
? DefaultStyle.buttonPressedInversedBackground ? DefaultStyle.buttonPressedInversedBackground
: DefaultStyle.buttonInversedBackground : DefaultStyle.grey_0
: mainItem.pressed : mainItem.pressed
? DefaultStyle.buttonPressedBackground ? DefaultStyle.buttonPressedBackground
: DefaultStyle.buttonBackground : DefaultStyle.main1_500_main
radius: 24 radius: 24
border.color: inversedColors ? DefaultStyle.buttonBackground : DefaultStyle.buttonInversedBackground border.color: inversedColors ? DefaultStyle.main1_500_main : DefaultStyle.grey_0
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -42,17 +42,12 @@ Control.Button {
} }
} }
leftPadding: 13
rightPadding: 13
topPadding: 10
bottomPadding: 10
contentItem: Text { contentItem: Text {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent anchors.centerIn: parent
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: mainItem.text text: mainItem.text
color: inversedColors ? DefaultStyle.buttonInversedTextColor : DefaultStyle.buttonTextColor color: inversedColors ? DefaultStyle.main1_500_main : DefaultStyle.grey_0
font { font {
bold: mainItem.boldText bold: mainItem.boldText
pointSize: mainItem.textSize 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) signal buttonClicked(int index)
background: Rectangle { background: Rectangle {
color: stackLayout.currentIndex == slideIndex ? DefaultStyle.buttonBackground : DefaultStyle.carouselLightGrayColor color: stackLayout.currentIndex == slideIndex ? DefaultStyle.main1_500_main : DefaultStyle.carouselLightGrayColor
radius: 15 radius: 15
width: stackLayout.currentIndex == slideIndex ? 11 : 8 width: stackLayout.currentIndex == slideIndex ? 11 : 8
height: 8 height: 8

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import Linphone
Control.TextField { Control.TextField {
id: mainItem id: mainItem
property int inputSize: 60 property int inputSize: 60
color: activeFocus ? DefaultStyle.digitInputFocusedColor : DefaultStyle.digitInputColor color: activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.digitInputColor
rightPadding: inputSize / 4 rightPadding: inputSize / 4
leftPadding: inputSize / 4 leftPadding: inputSize / 4
validator: IntValidator{bottom: 0; top: 9} validator: IntValidator{bottom: 0; top: 9}
@ -20,7 +20,7 @@ Control.TextField {
background: Rectangle { background: Rectangle {
// id: background // id: background
border.color: mainItem.activeFocus ? DefaultStyle.digitInputFocusedColor : DefaultStyle.digitInputColor border.color: mainItem.activeFocus ? DefaultStyle.main1_500_main : DefaultStyle.digitInputColor
radius: mainItem.inputSize / 8 radius: mainItem.inputSize / 8
} }
// cursorDelegate: Rectangle { // cursorDelegate: Rectangle {
@ -34,6 +34,6 @@ Control.TextField {
// // anchors.left: parent.left // // anchors.left: parent.left
// // anchors.bottomMargin: inputSize/8 // // anchors.bottomMargin: inputSize/8
// // transform: [/*Translate {x: mainItem.cursorRectangle.height},*/ Rotation {angle: -90}] // // 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 { Image {
id: image id: image
visible: !effect2.enabled
sourceSize.width: parent.width
sourceSize.height: parent.height
width: parent.width width: parent.width
height: parent.height height: parent.height
//sourceSize.width: parent.width
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
anchors.centerIn: parent anchors.centerIn: parent
visible: !effect2.enabled
} }
MultiEffect { MultiEffect {
id: effect id: effect
visible: !effect2.enabled
anchors.fill: image anchors.fill: image
source: image source: image
maskSource: image maskSource: image
visible: !effect2.enabled
brightness: effect2.enabled ? 1.0 : 0.0 brightness: effect2.enabled ? 1.0 : 0.0
} }
MultiEffect { MultiEffect {

View file

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

View file

@ -36,6 +36,10 @@ Notification {
Layout.rightMargin: 20 Layout.rightMargin: 20
onClicked: { onClicked: {
notification.call.core.lAccept(true) 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{ Item{

View file

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

View file

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

View file

@ -19,7 +19,7 @@ ColumnLayout {
visible: label.length > 0 visible: label.length > 0
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
text: mainItem.label + (mainItem.mandatory ? "*" : "") 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 { font {
pointSize: DefaultStyle.formItemLabelSize pointSize: DefaultStyle.formItemLabelSize
bold: true bold: true
@ -34,7 +34,7 @@ ColumnLayout {
border.color: mainItem.errorMessage.length > 0 border.color: mainItem.errorMessage.length > 0
? DefaultStyle.errorMessageColor ? DefaultStyle.errorMessageColor
: (textField.hasActiveFocus || combobox.hasActiveFocus) : (textField.hasActiveFocus || combobox.hasActiveFocus)
? DefaultStyle.formItemFocusBorderColor ? DefaultStyle.main1_500_main
: DefaultStyle.formItemBorderColor : DefaultStyle.formItemBorderColor
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
@ -69,7 +69,7 @@ ColumnLayout {
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.Wrap wrapMode: Text.Wrap
font { font {
pointSize: DefaultStyle.defaultTextSize pointSize: DefaultStyle.indicatorMessageTextSize
family: DefaultStyle.defaultFont family: DefaultStyle.defaultFont
bold: true 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 { background: Rectangle {
color: DefaultStyle.formItemBackgroundColor color: DefaultStyle.formItemBackgroundColor
border.color: mainItem.checked ? DefaultStyle.radioButtonCheckedColor : "transparent" border.color: mainItem.checked ? DefaultStyle.info_500_main : "transparent"
radius: 20 radius: 20
} }
@ -34,7 +34,7 @@ Control.RadioButton {
implicitWidth: 16 implicitWidth: 16
implicitHeight: 16 implicitHeight: 16
radius: implicitWidth/2 radius: implicitWidth/2
border.color: mainItem.checked ? DefaultStyle.radioButtonCheckedColor : DefaultStyle.radioButtonUncheckedColor border.color: mainItem.checked ? DefaultStyle.info_500_main : DefaultStyle.main1_500_main
Rectangle { Rectangle {
width: parent.width/2 width: parent.width/2
@ -42,14 +42,14 @@ Control.RadioButton {
x: parent.width/4 x: parent.width/4
y: parent.width/4 y: parent.width/4
radius: width/2 radius: width/2
color: DefaultStyle.radioButtonCheckedColor color: DefaultStyle.info_500_main
visible: mainItem.checked visible: mainItem.checked
} }
} }
Text { Text {
text: mainItem.title text: mainItem.title
font.bold: true font.bold: true
color: DefaultStyle.radioButtonTitleColor color: DefaultStyle.grey_900
font.pointSize: DefaultStyle.radioButtonTitleSize font.pointSize: DefaultStyle.radioButtonTitleSize
} }
Control.Button { Control.Button {
@ -83,7 +83,7 @@ Control.RadioButton {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.preferredWidth: 220 Layout.preferredWidth: 220
Layout.preferredHeight: 100 Layout.preferredHeight: 100
font.pointSize: DefaultStyle.defaultTextSize font.pointSize: DefaultStyle.descriptionTextSize
text: mainItem.contentText text: mainItem.contentText
Layout.fillHeight: true Layout.fillHeight: true
} }

View file

@ -1,9 +1,15 @@
import QtQuick 2.7 import QtQuick 2.7
Rectangle { 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 anchors.fill: parent
color: "blue" color: genRandomColor() //"blue"
opacity: 0.2 opacity: 0.2
border.color: "green" border.color: genRandomColor() //"red"
border.width: 2 border.width: 2
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -41,7 +41,7 @@ LoginLayout {
Text { Text {
Layout.rightMargin: 15 Layout.rightMargin: 15
text: "No account yet ?" text: "No account yet ?"
font.pointSize: DefaultStyle.defaultTextSize font.pointSize: DefaultStyle.indicatorMessageTextSize
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
@ -75,7 +75,7 @@ LoginLayout {
width: rootStackView.width width: rootStackView.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: DefaultStyle.darkGrayColor 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> 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>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>" <p>To enable it in a commercial projet, please contact us. </p>"

View file

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

View file

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls as Control import QtQuick.Controls as Control
import Linphone import Linphone
import UtilsCpp 1.0
AbstractMainPage { AbstractMainPage {
id: mainItem id: mainItem
@ -16,10 +17,12 @@ AbstractMainPage {
Layout.fillHeight: true Layout.fillHeight: true
Control.StackView { Control.StackView {
id: listStackView id: listStackView
clip: true
initialItem: listItem initialItem: listItem
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 25 property int sideMargin: 25
anchors.rightMargin: 25 // anchors.leftMargin: 25
// anchors.rightMargin: 25
} }
Component { Component {
id: listItem id: listItem
@ -27,6 +30,8 @@ AbstractMainPage {
ColumnLayout { ColumnLayout {
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: listStackView.sideMargin
Layout.rightMargin: listStackView.sideMargin
Text { Text {
text: qsTr("Appels") text: qsTr("Appels")
color: DefaultStyle.mainPageTitleColor color: DefaultStyle.mainPageTitleColor
@ -65,6 +70,8 @@ AbstractMainPage {
id: listLayout id: listLayout
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin: listStackView.sideMargin
Layout.rightMargin: listStackView.sideMargin
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
@ -148,6 +155,11 @@ AbstractMainPage {
onCountChanged: mainItem.showDefaultItem = listView.count === 0 onCountChanged: mainItem.showDefaultItem = listView.count === 0
Connections {
target: mainItem
onShowDefaultItemChanged: mainItem.showDefaultItem = mainItem.showDefaultItem && listView.count === 0
}
Control.ScrollIndicator.vertical: Control.ScrollIndicator { } Control.ScrollIndicator.vertical: Control.ScrollIndicator { }
} }
} }
@ -158,7 +170,13 @@ AbstractMainPage {
Component { Component {
id: newCallItem id: newCallItem
ColumnLayout { ColumnLayout {
Control.StackView.onActivating: {
mainItem.showDefaultItem = false
}
Control.StackView.onDeactivating: mainItem.showDefaultItem = true
RowLayout { RowLayout {
Layout.leftMargin: listStackView.sideMargin
Layout.rightMargin: listStackView.sideMargin
Control.Button { Control.Button {
background: Item { background: Item {
} }
@ -180,72 +198,24 @@ AbstractMainPage {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
Control.Control { ContactsList {
id: listLayout
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
background: Rectangle { Layout.maximumWidth: parent.width
anchors.fill: parent groupCallVisible: true
} searchBarColor: DefaultStyle.contactListSearchBarColor
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
}
}
}
}
}
}
Item { onCallButtonPressed: (address) => {
anchors.bottom: parent.bottom var addressEnd = "@sip.linphone.org"
anchors.left: parent.left if (!address.endsWith(addressEnd)) address += addressEnd
anchors.right: parent.right var callVarObject = UtilsCpp.createCall(address)
height: numPad.height // var windowComp = Qt.createComponent("CallsWindow.qml")
NumericPad { // var call = callVarObject.value
id: numPad // var callWindow = windowComp.createObject(null, {callVarObject: callVarObject})
// anchors.centerIn: parent // callWindow.modality = Qt.ApplicationModal
width: parent.width // callWindow.show()
}
}
} }
} }
} }

View file

@ -25,6 +25,7 @@ QtObject {
property string phoneSelected: "image://internal/phone-selected.svg" property string phoneSelected: "image://internal/phone-selected.svg"
property string newCall: "image://internal/phone-plus.svg" property string newCall: "image://internal/phone-plus.svg"
property string endCall: "image://internal/phone-disconnect.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 adressBook: "image://internal/address-book.svg"
property string adressBookSelected: "image://internal/address-book-selected.svg" property string adressBookSelected: "image://internal/address-book-selected.svg"
property string chatTeardropText: "image://internal/chat-teardrop-text.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 incomingCallRejected: "image://internal/incoming_call_rejected.svg"
property string outgoingCall: "image://internal/outgoing_call.svg" property string outgoingCall: "image://internal/outgoing_call.svg"
property string outgoingCallMissed: "image://internal/outgoing_call_missed.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 import QtQuick 2.15
QtObject { 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 string emojiFont: "Noto Color Emoji"
property color orangeColor: "#FE5E00"
property color buttonBackground: "#FE5E00"
property color buttonPressedBackground: "#c74b02" property color buttonPressedBackground: "#c74b02"
property color buttonInversedBackground: "white"
property color buttonPressedInversedBackground: "#fff1e8" 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 radioButtonTextSize: 8
property int radioButtonTitleSize: 9 property int radioButtonTitleSize: 9
property color buttonInversedTextColor: "#FE5E00"
property color checkboxBorderColor: "#FE5E00"
property double checkboxBorderWidth: 2 property double checkboxBorderWidth: 2
property int buttonTextSize: 10 property int buttonTextSize: 10
property color carouselLightGrayColor: "#DFECF2" property color carouselLightGrayColor: "#DFECF2"
@ -26,10 +39,8 @@ QtObject {
property color formItemDisableBackgroundColor: "#EDEDED" property color formItemDisableBackgroundColor: "#EDEDED"
property color formItemBackgroundColor: "#F9F9F9" property color formItemBackgroundColor: "#F9F9F9"
property color formItemBorderColor: "#EDEDED" property color formItemBorderColor: "#EDEDED"
property color formItemFocusBorderColor: "#FE5E00"
property int tabButtonTextSize: 11 property int tabButtonTextSize: 11
property color verticalTabBarColor: "#FE5E00"
property color verticalTabBarTextColor: "white" property color verticalTabBarTextColor: "white"
property int verticalTabButtonTextSize: 6 property int verticalTabButtonTextSize: 6
@ -43,7 +54,6 @@ QtObject {
property color tooltipBackgroundColor: "#DFECF2" property color tooltipBackgroundColor: "#DFECF2"
property color digitInputFocusedColor: "#FE5E00"
property color digitInputColor: "#6C7A87" property color digitInputColor: "#6C7A87"
property color darkBlueColor: "#22334D" property color darkBlueColor: "#22334D"
@ -53,7 +63,8 @@ QtObject {
property color defaultTextColor: "#4E6074" property color defaultTextColor: "#4E6074"
property color disableTextColor: "#9AABB5" property color disableTextColor: "#9AABB5"
property int defaultTextSize: 7 property int descriptionTextSize: 7
property int indicatorMessageTextSize: 7
property string defaultFont: "Noto Sans" property string defaultFont: "Noto Sans"
property int defaultFontPointSize: 10 property int defaultFontPointSize: 10
property int title1FontPointSize: 50 property int title1FontPointSize: 50
@ -65,29 +76,30 @@ QtObject {
property int mainPageTitleSize: 15 property int mainPageTitleSize: 15
property color searchBarFocusBorderColor: "#6C7A87" property color searchBarFocusBorderColor: "#6C7A87"
property color contactListSearchBarColor: "#F9F9F9"
property color callRightPanelSearchBarBorderColor: "#EDEDED"
property color numericPadBackgroundColor: "#F9F9F9" property color numericPadBackgroundColor: "#F9F9F9"
property color numericPadShadowColor: Qt.rgba(0.0, 0.0, 0.0, 0.1) property color numericPadShadowColor: Qt.rgba(0.0, 0.0, 0.0, 0.1)
property color numericPadButtonColor: "#FFFFFF"
property int numericPadButtonTextSize: 15 property int numericPadButtonTextSize: 15
property int numericPadButtonSubtextSize: 6 property int numericPadButtonSubtextSize: 6
property color numericPadPressedButtonColor: "#EEF7F8" property color numericPadPressedButtonColor: "#EEF7F8"
property color numericPadLaunchCallButtonColor: "#4FAE80"
property color groupCallButtonColor: "#EEF7F8" property color groupCallButtonColor: "#EEF7F8"
property color launchCallButtonColor: "#4FAE80"
property color callCheckedButtonColor: "#9AABB5"
property color splitViewHandleColor: "#F9F9F9" property color splitViewHandleColor: "#F9F9F9"
property color splitViewHoveredHandleColor: "#EDEDED" property color splitViewHoveredHandleColor: "#EDEDED"
property color danger_500main: "#DD5F5F" property color ongoingCallWindowColor: "#000000"
property color grey_0: "#FFFFFF" property color ongoingCallBackgroundColor: "#2E3030"
property color success_500main: "#4FAE80" property int ongoingCallElapsedTimeSize: 15
property color warning_600: "#DBB820" property int ongoingCallNameSize: 10
property color main2_200: "#DFECF2" property int ongoingCallAddressSize: 6
property color main2_300: "#C0D1D9" property color callRightPanelBackgroundColor: "#F9F9F9"
property color main2_400: "#9AABB5"
property color main2_700: "#364860" property color defaultAvatarBackgroundColor: "#DFECF2"
property color main2_500main: "#6C7A87"
property double dp: 1.0//0.66 property double dp: 1.0//0.66
} }