From 4143d15f3465bc0dcf39750ae0166f307f384871 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Thu, 25 Jul 2024 08:34:11 +0000 Subject: [PATCH] Account settings & parameters --- Linphone/core/App.cpp | 2 + Linphone/core/CMakeLists.txt | 2 + Linphone/core/account/AccountCore.cpp | 369 ++++++++++++++++- Linphone/core/account/AccountCore.hpp | 123 ++++++ Linphone/core/account/AccountDeviceCore.cpp | 64 +++ Linphone/core/account/AccountDeviceCore.hpp | 58 +++ Linphone/core/account/AccountDeviceGui.cpp | 38 ++ Linphone/core/account/AccountDeviceGui.hpp | 41 ++ Linphone/core/notifier/Notifier.cpp | 11 + Linphone/data/image/calendar.svg | 4 +- Linphone/data/image/desktop.svg | 3 + Linphone/data/image/mobile.svg | 3 + Linphone/model/CMakeLists.txt | 2 +- Linphone/model/account/AccountModel.cpp | 186 ++++++++- Linphone/model/account/AccountModel.hpp | 36 ++ Linphone/tool/Utils.cpp | 35 ++ Linphone/tool/Utils.hpp | 5 + ...gsLayout.qml => AbstractDetailsLayout.qml} | 28 +- .../Account/AccountSettingsGeneralLayout.qml | 385 ++++++++++++++++++ .../AccountSettingsParametersLayout.qml | 171 ++++++++ Linphone/view/App/Layout/MainLayout.qml | 78 ++-- .../Layout/Settings/CallSettingsLayout.qml | 13 +- .../Layout/Settings/DebugSettingsLayout.qml | 8 +- .../Settings/SecuritySettingsLayout.qml | 5 +- Linphone/view/App/Main.qml | 3 + Linphone/view/CMakeLists.txt | 11 +- Linphone/view/Item/ComboBox.qml | 1 + ...tingsFamily.qml => MasterDetailFamily.qml} | 0 Linphone/view/Item/Settings/ComboSetting.qml | 9 +- Linphone/view/Item/Settings/SwitchSetting.qml | 11 +- Linphone/view/Item/TextField.qml | 3 +- Linphone/view/Layout/FormItemLayout.qml | 2 +- Linphone/view/Layout/ValidatedTextField.qml | 56 +++ .../Page/Main/AbstractMasterDetailPage.qml | 83 ++++ .../view/Page/Main/AccountSettingsPage.qml | 18 + Linphone/view/Page/Main/HelpPage.qml | 3 +- Linphone/view/Page/Main/SettingsPage.qml | 77 +--- Linphone/view/Style/AppIcons.qml | 3 + Linphone/view/Style/Typography.qml | 7 + 39 files changed, 1800 insertions(+), 157 deletions(-) create mode 100644 Linphone/core/account/AccountDeviceCore.cpp create mode 100644 Linphone/core/account/AccountDeviceCore.hpp create mode 100644 Linphone/core/account/AccountDeviceGui.cpp create mode 100644 Linphone/core/account/AccountDeviceGui.hpp create mode 100644 Linphone/data/image/desktop.svg create mode 100644 Linphone/data/image/mobile.svg rename Linphone/view/App/Layout/{Settings/GenericSettingsLayout.qml => AbstractDetailsLayout.qml} (57%) create mode 100644 Linphone/view/App/Layout/Account/AccountSettingsGeneralLayout.qml create mode 100644 Linphone/view/App/Layout/Account/AccountSettingsParametersLayout.qml rename Linphone/view/Item/{Settings/SettingsFamily.qml => MasterDetailFamily.qml} (100%) create mode 100644 Linphone/view/Layout/ValidatedTextField.qml create mode 100644 Linphone/view/Page/Main/AbstractMasterDetailPage.qml create mode 100644 Linphone/view/Page/Main/AccountSettingsPage.qml diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 3659e7153..acb3f93e1 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -36,6 +36,7 @@ #include #include "core/account/AccountCore.hpp" +#include "core/account/AccountDeviceGui.hpp" #include "core/account/AccountProxy.hpp" #include "core/call-history/CallHistoryProxy.hpp" #include "core/call/CallCore.hpp" @@ -334,6 +335,7 @@ void App::initCppInterfaces() { qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "PhoneNumber", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountGui"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountDeviceGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable")); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallProxy"); diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index 9543c892c..1c9ef5b87 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -3,6 +3,8 @@ list(APPEND _LINPHONEAPP_SOURCES core/account/AccountGui.cpp core/account/AccountList.cpp core/account/AccountProxy.cpp + core/account/AccountDeviceCore.cpp + core/account/AccountDeviceGui.cpp core/App.cpp core/call/CallCore.cpp core/call/CallGui.cpp diff --git a/Linphone/core/account/AccountCore.cpp b/Linphone/core/account/AccountCore.cpp index 7a39e5746..0c9db467b 100644 --- a/Linphone/core/account/AccountCore.cpp +++ b/Linphone/core/account/AccountCore.cpp @@ -22,6 +22,7 @@ #include "core/App.hpp" #include "tool/Utils.hpp" #include "tool/thread/SafeConnection.hpp" +#include DEFINE_ABSTRACT_OBJECT(AccountCore) @@ -47,10 +48,42 @@ AccountCore::AccountCore(const std::shared_ptr &account) : QO mIsDefaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount() == account; // mUnreadNotifications = account->getUnreadChatMessageCount() + account->getMissedCallsCount(); // TODO mUnreadNotifications = account->getMissedCallsCount(); + mDisplayName = Utils::coreStringToAppString(identityAddress->getDisplayName()); + mRegisterEnabled = params->registerEnabled(); + mMwiServerAddress = + params->getMwiServerAddress() ? Utils::coreStringToAppString(params->getMwiServerAddress()->asString()) : ""; + mTransports << "TCP" << "UDP" << "TLS" << "DTLS"; + mTransport = LinphoneEnums::toString(LinphoneEnums::fromLinphone(params->getTransport())); + mServerAddress = + params->getServerAddress() ? Utils::coreStringToAppString(params->getServerAddress()->asString()) : ""; + mOutboundProxyEnabled = params->outboundProxyEnabled(); + auto policy = params->getNatPolicy() ? params->getNatPolicy() : account->getCore()->createNatPolicy(); + mStunServer = Utils::coreStringToAppString(policy->getStunServer()); + mIceEnabled = policy->iceEnabled(); + mAvpfEnabled = account->avpfEnabled(); + mBundleModeEnabled = params->rtpBundleEnabled(); + mExpire = params->getExpires(); + mConferenceFactoryAddress = params->getConferenceFactoryAddress() + ? Utils::coreStringToAppString(params->getConferenceFactoryAddress()->asString()) + : ""; + mAudioVideoConferenceFactoryAddress = + params->getAudioVideoConferenceFactoryAddress() + ? Utils::coreStringToAppString(params->getAudioVideoConferenceFactoryAddress()->asString()) + : ""; + mLimeServerUrl = Utils::coreStringToAppString(params->getLimeServerUrl()); // Add listener mAccountModel = Utils::makeQObject_ptr(account); // OK mAccountModel->setSelf(mAccountModel); + mNotificationsAllowed = mAccountModel->getNotificationsAllowed(); + mDialPlan = " "; + mDialPlans << mDialPlan; + for (auto dialPlan : linphone::Factory::get()->getDialPlans()) { + mDialPlans << mAccountModel->dialPlanAsString(dialPlan); + if (dialPlan->getCountryCallingCode() == account->getParams()->getInternationalPrefix()) { + mDialPlan = mAccountModel->dialPlanAsString(dialPlan); + } + } } AccountCore::~AccountCore() { @@ -83,6 +116,59 @@ void AccountCore::setSelf(QSharedPointer me) { this->setUnreadMessageNotifications(unreadMessagesCount); }); }); + mAccountModelConnection->makeConnectToModel(&AccountModel::displayNameChanged, [this](QString displayName) { + mAccountModelConnection->invokeToCore([this, displayName]() { this->onDisplayNameChanged(displayName); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::dialPlanChanged, [this](int index) { + auto dialPlan = mDialPlans[index + 1]; + mAccountModelConnection->invokeToCore([this, dialPlan]() { this->onDialPlanChanged(dialPlan); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::registerEnabledChanged, [this](bool enabled) { + mAccountModelConnection->invokeToCore([this, enabled]() { this->onRegisterEnabledChanged(enabled); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::notificationsAllowedChanged, [this](bool value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onNotificationsAllowedChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::mwiServerAddressChanged, [this](QString value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onMwiServerAddressChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::transportChanged, [this](linphone::TransportType value) { + mAccountModelConnection->invokeToCore( + [this, value]() { this->onTransportChanged(LinphoneEnums::toString(LinphoneEnums::fromLinphone(value))); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::serverAddressChanged, [this](QString value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onServerAddressChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::outboundProxyEnabledChanged, [this](bool value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onOutboundProxyEnabledChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::stunServerChanged, [this](QString value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onStunServerChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::iceEnabledChanged, [this](bool value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onIceEnabledChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::avpfEnabledChanged, [this](bool value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onAvpfEnabledChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::bundleModeEnabledChanged, [this](bool value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onBundleModeEnabledChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::expireChanged, [this](int value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onExpireChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::conferenceFactoryAddressChanged, [this](QString value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onConferenceFactoryAddressChanged(value); }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::audioVideoConferenceFactoryAddressChanged, + [this](QString value) { + mAccountModelConnection->invokeToCore([this, value]() { + this->onAudioVideoConferenceFactoryAddressChanged(value); + }); + }); + mAccountModelConnection->makeConnectToModel(&AccountModel::limeServerUrlChanged, [this](QString value) { + mAccountModelConnection->invokeToCore([this, value]() { this->onLimeServerUrlChanged(value); }); + }); // From GUI mAccountModelConnection->makeConnectToCore(&AccountCore::lSetPictureUri, [this](QString uri) { @@ -108,6 +194,62 @@ void AccountCore::setSelf(QSharedPointer me) { mAccountModelConnection->makeConnectToCore(&AccountCore::unreadNotificationsChanged, [this]() { mAccountModelConnection->invokeToModel([this]() { CoreModel::getInstance()->unreadNotificationsChanged(); }); }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetDisplayName, [this](QString displayName) { + mAccountModelConnection->invokeToModel([this, displayName]() { mAccountModel->setDisplayName(displayName); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetDialPlan, [this](QString dialPlan) { + auto dialPlanIndex = getDialPlanIndex(dialPlan); + mAccountModelConnection->invokeToModel( + [this, dialPlanIndex]() { mAccountModel->setDialPlan(dialPlanIndex - 1); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetRegisterEnabled, [this](bool enabled) { + mAccountModelConnection->invokeToModel([this, enabled]() { mAccountModel->setRegisterEnabled(enabled); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetNotificationsAllowed, [this](bool value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setNotificationsAllowed(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetMwiServerAddress, [this](QString value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setMwiServerAddress(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetTransport, [this](QString value) { + LinphoneEnums::TransportType transport; + LinphoneEnums::fromString(value, &transport); + mAccountModelConnection->invokeToModel( + [this, value, transport]() { mAccountModel->setTransport(LinphoneEnums::toLinphone(transport)); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetServerAddress, [this](QString value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setServerAddress(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetOutboundProxyEnabled, [this](bool value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setOutboundProxyEnabled(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetStunServer, [this](QString value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setStunServer(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetIceEnabled, [this](bool value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setIceEnabled(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetAvpfEnabled, [this](bool value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setAvpfEnabled(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetBundleModeEnabled, [this](bool value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setBundleModeEnabled(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetExpire, [this](int value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setExpire(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetConferenceFactoryAddress, [this](QString value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setConferenceFactoryAddress(value); }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetAudioVideoConferenceFactoryAddress, + [this](QString value) { + mAccountModelConnection->invokeToModel([this, value]() { + mAccountModel->setAudioVideoConferenceFactoryAddress(value); + }); + }); + mAccountModelConnection->makeConnectToCore(&AccountCore::lSetLimeServerUrl, [this](QString value) { + mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setLimeServerUrl(value); }); + }); } QString AccountCore::getContactAddress() const { @@ -176,10 +318,231 @@ void AccountCore::onDefaultAccountChanged(bool isDefault) { } void AccountCore::onPictureUriChanged(QString uri) { - mPictureUri = uri; - emit pictureUriChanged(); + if (uri != mPictureUri) { + mPictureUri = uri; + emit pictureUriChanged(); + } } void AccountCore::removeAccount() { mAccountModelConnection->invokeToModel([this]() { mAccountModel->removeAccount(); }); -} \ No newline at end of file +} + +QString AccountCore::getDisplayName() const { + return mDisplayName; +} + +void AccountCore::onDisplayNameChanged(QString displayName) { + if (displayName != mDisplayName) { + mDisplayName = displayName; + emit displayNameChanged(); + } +} + +QStringList AccountCore::getDialPlans() { + return mDialPlans; +} + +QString AccountCore::getDialPlan() const { + return mDialPlan; +} + +void AccountCore::onDialPlanChanged(QString dialPlan) { + if (dialPlan != mDialPlan) { + mDialPlan = dialPlan; + emit dialPlanChanged(); + } +} + +int AccountCore::getDialPlanIndex(QString dialPlanString) { + return mDialPlans.indexOf(dialPlanString); +} + +QString AccountCore::getHumanReadableRegistrationState() const { + switch (mRegistrationState) { + case LinphoneEnums::RegistrationState::Ok: + return tr("Connecté"); + case LinphoneEnums::RegistrationState::Refreshing: + return tr("En cours de rafraîchissement…"); + case LinphoneEnums::RegistrationState::Progress: + return tr("En cours de connexion…"); + case LinphoneEnums::RegistrationState::Failed: + return tr("Erreur"); + case LinphoneEnums::RegistrationState::None: + case LinphoneEnums::RegistrationState::Cleared: + return tr("Désactivé"); + default: + return " "; + } +} + +QString AccountCore::getHumanReadableRegistrationStateExplained() const { + switch (mRegistrationState) { + case LinphoneEnums::RegistrationState::Ok: + return tr("Vous êtes en ligne et joignable."); + case LinphoneEnums::RegistrationState::Failed: + return tr("Erreur de connexion, vérifiez vos paramètres."); + case LinphoneEnums::RegistrationState::None: + case LinphoneEnums::RegistrationState::Cleared: + return tr("Compte désactivé, vous ne recevrez ni appel ni message."); + default: + return " "; + } +} + +bool AccountCore::getRegisterEnabled() const { + return mRegisterEnabled; +} + +void AccountCore::onRegisterEnabledChanged(bool enabled) { + if (enabled != mRegisterEnabled) { + mRegisterEnabled = enabled; + emit registerEnabledChanged(); + } +} + +bool AccountCore::getNotificationsAllowed() { + return mNotificationsAllowed; +} + +QString AccountCore::getMwiServerAddress() { + return mMwiServerAddress; +} + +QStringList AccountCore::getTransports() { + return mTransports; +} + +QString AccountCore::getTransport() { + return mTransport; +} + +QString AccountCore::getServerAddress() { + return mServerAddress; +} + +bool AccountCore::getOutboundProxyEnabled() { + return mOutboundProxyEnabled; +} + +QString AccountCore::getStunServer() { + return mStunServer; +} + +bool AccountCore::getIceEnabled() { + return mIceEnabled; +} + +bool AccountCore::getAvpfEnabled() { + return mAvpfEnabled; +} + +bool AccountCore::getBundleModeEnabled() { + return mBundleModeEnabled; +} + +int AccountCore::getExpire() { + return mExpire; +} + +QString AccountCore::getConferenceFactoryAddress() { + return mConferenceFactoryAddress; +} + +QString AccountCore::getAudioVideoConferenceFactoryAddress() { + return mAudioVideoConferenceFactoryAddress; +} + +QString AccountCore::getLimeServerUrl() { + return mLimeServerUrl; +} + +void AccountCore::onNotificationsAllowedChanged(bool value) { + if (value != mNotificationsAllowed) { + mNotificationsAllowed = value; + emit notificationsAllowedChanged(); + } +} + +void AccountCore::onMwiServerAddressChanged(QString value) { + if (value != mMwiServerAddress) { + mMwiServerAddress = value; + emit mwiServerAddressChanged(); + } +} + +void AccountCore::onTransportChanged(QString value) { + if (value != mTransport) { + mTransport = value; + emit transportChanged(); + } +} + +void AccountCore::onServerAddressChanged(QString value) { + if (value != mServerAddress) { + mServerAddress = value; + emit serverAddressChanged(); + } +} + +void AccountCore::onOutboundProxyEnabledChanged(bool value) { + if (value != mOutboundProxyEnabled) { + mOutboundProxyEnabled = value; + emit outboundProxyEnabledChanged(); + } +} +void AccountCore::onStunServerChanged(QString value) { + if (value != mStunServer) { + mStunServer = value; + emit stunServerChanged(); + } +} + +void AccountCore::onIceEnabledChanged(bool value) { + if (value != mIceEnabled) { + mIceEnabled = value; + emit iceEnabledChanged(); + } +} + +void AccountCore::onAvpfEnabledChanged(bool value) { + if (value != mAvpfEnabled) { + mAvpfEnabled = value; + emit avpfEnabledChanged(); + } +} + +void AccountCore::onBundleModeEnabledChanged(bool value) { + if (value != mBundleModeEnabled) { + mBundleModeEnabled = value; + emit bundleModeEnabledChanged(); + } +} + +void AccountCore::onExpireChanged(int value) { + if (value != mExpire) { + mExpire = value; + emit expireChanged(); + } +} + +void AccountCore::onConferenceFactoryAddressChanged(QString value) { + if (value != mConferenceFactoryAddress) { + mConferenceFactoryAddress = value; + emit conferenceFactoryAddressChanged(); + } +} + +void AccountCore::onAudioVideoConferenceFactoryAddressChanged(QString value) { + if (value != mAudioVideoConferenceFactoryAddress) { + mAudioVideoConferenceFactoryAddress = value; + emit audioVideoConferenceFactoryAddressChanged(); + } +} + +void AccountCore::onLimeServerUrlChanged(QString value) { + if (value != mLimeServerUrl) { + mLimeServerUrl = value; + emit limeServerUrlChanged(); + } +} diff --git a/Linphone/core/account/AccountCore.hpp b/Linphone/core/account/AccountCore.hpp index 4d33c8e71..5a03bb7fa 100644 --- a/Linphone/core/account/AccountCore.hpp +++ b/Linphone/core/account/AccountCore.hpp @@ -41,6 +41,36 @@ class AccountCore : public QObject, public AbstractObject { Q_PROPERTY(int unreadCallNotifications READ getUnreadCallNotifications NOTIFY unreadCallNotificationsChanged) Q_PROPERTY( int unreadMessageNotifications READ getUnreadMessageNotifications NOTIFY unreadMessageNotificationsChanged) + Q_PROPERTY(QString displayName READ getDisplayName WRITE lSetDisplayName NOTIFY displayNameChanged) + Q_PROPERTY(QStringList dialPlans READ getDialPlans CONSTANT) + Q_PROPERTY(QString dialPlan READ getDialPlan WRITE lSetDialPlan NOTIFY dialPlanChanged) + Q_PROPERTY( + QString humaneReadableRegistrationState READ getHumanReadableRegistrationState NOTIFY registrationStateChanged) + Q_PROPERTY(QString humaneReadableRegistrationStateExplained READ getHumanReadableRegistrationStateExplained NOTIFY + registrationStateChanged) + Q_PROPERTY(bool registerEnabled READ getRegisterEnabled WRITE lSetRegisterEnabled NOTIFY registerEnabledChanged) + Q_PROPERTY(QVariantList devices MEMBER mDevices NOTIFY devicesChanged) + + Q_PROPERTY(bool notificationsAllowed READ getNotificationsAllowed WRITE lSetNotificationsAllowed NOTIFY + notificationsAllowedChanged) + Q_PROPERTY( + QString mwiServerAddress READ getMwiServerAddress WRITE lSetMwiServerAddress NOTIFY mwiServerAddressChanged) + Q_PROPERTY(QStringList transports READ getTransports CONSTANT) + Q_PROPERTY(QString transport READ getTransport WRITE lSetTransport NOTIFY transportChanged) + Q_PROPERTY(QString serverAddress READ getServerAddress WRITE lSetServerAddress NOTIFY serverAddressChanged) + Q_PROPERTY(bool outboundProxyEnabled READ getOutboundProxyEnabled WRITE lSetOutboundProxyEnabled NOTIFY + outboundProxyEnabledChanged) + Q_PROPERTY(QString stunServer READ getStunServer WRITE lSetStunServer NOTIFY stunServerChanged) + Q_PROPERTY(bool iceEnabled READ getIceEnabled WRITE lSetIceEnabled NOTIFY iceEnabledChanged) + Q_PROPERTY(bool avpfEnabled READ getAvpfEnabled WRITE lSetAvpfEnabled NOTIFY avpfEnabledChanged) + Q_PROPERTY( + bool bundleModeEnabled READ getBundleModeEnabled WRITE lSetBundleModeEnabled NOTIFY bundleModeEnabledChanged) + Q_PROPERTY(int expire READ getExpire WRITE lSetExpire NOTIFY expireChanged) + Q_PROPERTY(QString conferenceFactoryAddress READ getConferenceFactoryAddress WRITE lSetConferenceFactoryAddress + NOTIFY conferenceFactoryAddressChanged) + Q_PROPERTY(QString audioVideoConferenceFactoryAddress READ getAudioVideoConferenceFactoryAddress WRITE + lSetAudioVideoConferenceFactoryAddress NOTIFY audioVideoConferenceFactoryAddressChanged) + Q_PROPERTY(QString limeServerUrl READ getLimeServerUrl WRITE lSetLimeServerUrl NOTIFY limeServerUrlChanged) public: static QSharedPointer create(const std::shared_ptr &account); @@ -68,6 +98,45 @@ public: void onDefaultAccountChanged(bool isDefault); Q_INVOKABLE void removeAccount(); + QString getDisplayName() const; + void onDisplayNameChanged(QString displayName); + QStringList getDialPlans(); + int getDialPlanIndex(QString dialPlanString); + QString getDialPlan() const; + void onDialPlanChanged(QString internationalPrefix); + QString getHumanReadableRegistrationState() const; + QString getHumanReadableRegistrationStateExplained() const; + bool getRegisterEnabled() const; + void onRegisterEnabledChanged(bool enabled); + + bool getNotificationsAllowed(); + QString getMwiServerAddress(); + QString getTransport(); + QStringList getTransports(); + QString getServerAddress(); + bool getOutboundProxyEnabled(); + QString getStunServer(); + bool getIceEnabled(); + bool getAvpfEnabled(); + bool getBundleModeEnabled(); + int getExpire(); + QString getConferenceFactoryAddress(); + QString getAudioVideoConferenceFactoryAddress(); + QString getLimeServerUrl(); + + void onNotificationsAllowedChanged(bool value); + void onMwiServerAddressChanged(QString value); + void onTransportChanged(QString value); + void onServerAddressChanged(QString value); + void onOutboundProxyEnabledChanged(bool value); + void onStunServerChanged(QString value); + void onIceEnabledChanged(bool value); + void onAvpfEnabledChanged(bool value); + void onBundleModeEnabledChanged(bool value); + void onExpireChanged(int value); + void onConferenceFactoryAddressChanged(QString value); + void onAudioVideoConferenceFactoryAddressChanged(QString value); + void onLimeServerUrlChanged(QString value); signals: void pictureUriChanged(); @@ -76,22 +145,76 @@ signals: void unreadNotificationsChanged(int unread); void unreadCallNotificationsChanged(int unread); void unreadMessageNotificationsChanged(int unread); + void displayNameChanged(); + void dialPlanChanged(); + void registerEnabledChanged(); + void allAddressesChanged(); + void devicesChanged(); + void notificationsAllowedChanged(); + void mwiServerAddressChanged(); + void transportChanged(); + void serverAddressChanged(); + void outboundProxyEnabledChanged(); + void stunServerChanged(); + void iceEnabledChanged(); + void avpfEnabledChanged(); + void bundleModeEnabledChanged(); + void expireChanged(); + void conferenceFactoryAddressChanged(); + void audioVideoConferenceFactoryAddressChanged(); + void limeServerUrlChanged(); // Account requests void lSetPictureUri(QString pictureUri); void lSetDefaultAccount(); void lResetMissedCalls(); void lRefreshNotifications(); + void lSetDisplayName(QString displayName); + void lSetDialPlan(QString internationalPrefix); + void lSetRegisterEnabled(bool enabled); + void lSetNotificationsAllowed(bool value); + void lSetMwiServerAddress(QString value); + void lSetTransport(QString value); + void lSetServerAddress(QString value); + void lSetOutboundProxyEnabled(bool value); + void lSetStunServer(QString value); + void lSetIceEnabled(bool value); + void lSetAvpfEnabled(bool value); + void lSetBundleModeEnabled(bool value); + void lSetExpire(int value); + void lSetConferenceFactoryAddress(QString value); + void lSetAudioVideoConferenceFactoryAddress(QString value); + void lSetLimeServerUrl(QString value); private: QString mContactAddress; QString mIdentityAddress; QString mPictureUri; + QString mDisplayName; + QStringList mDialPlans; + QString mDialPlan; + bool mRegisterEnabled; bool mIsDefaultAccount = false; LinphoneEnums::RegistrationState mRegistrationState; int mUnreadNotifications = 0; int mUnreadCallNotifications = 0; int mUnreadMessageNotifications = 0; + QVariantList mDevices; + bool mNotificationsAllowed; + QString mMwiServerAddress; + QString mTransport; + QStringList mTransports; + QString mServerAddress; + bool mOutboundProxyEnabled; + QString mStunServer; + bool mIceEnabled; + bool mAvpfEnabled; + bool mBundleModeEnabled; + int mExpire; + QString mConferenceFactoryAddress; + QString mAudioVideoConferenceFactoryAddress; + QString mLimeServerUrl; + std::shared_ptr mAccountModel; QSharedPointer> mAccountModelConnection; QSharedPointer> mCoreModelConnection; diff --git a/Linphone/core/account/AccountDeviceCore.cpp b/Linphone/core/account/AccountDeviceCore.cpp new file mode 100644 index 000000000..c50b36e2e --- /dev/null +++ b/Linphone/core/account/AccountDeviceCore.cpp @@ -0,0 +1,64 @@ +/* + * 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 . + */ + +#include "AccountDeviceCore.hpp" +#include "core/App.hpp" +#include "tool/Utils.hpp" +#include "tool/thread/SafeConnection.hpp" + +DEFINE_ABSTRACT_OBJECT(AccountDeviceCore) + +AccountDeviceCore::AccountDeviceCore(QString name, QString userAgent, QDateTime last) : QObject(nullptr) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + mustBeInLinphoneThread(getClassName()); + + mDeviceName = name; + mUserAgent = userAgent; + mLastUpdateTimestamp = last; +} + +QSharedPointer AccountDeviceCore::createDummy(QString name, QString userAgent, QDateTime last) { + auto core = QSharedPointer(new AccountDeviceCore(name,userAgent,last)); + core->moveToThread(App::getInstance()->thread()); + return core; +} + +QSharedPointer AccountDeviceCore::create(const std::shared_ptr &device) { + auto core = QSharedPointer(new AccountDeviceCore(device)); + core->moveToThread(App::getInstance()->thread()); + return core; +} + +AccountDeviceCore::AccountDeviceCore(const std::shared_ptr &device) : QObject(nullptr) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + mustBeInLinphoneThread(getClassName()); + + mDeviceName = Utils::coreStringToAppString(device->getName()); + mUserAgent = Utils::coreStringToAppString(device->getUserAgent()); + mLastUpdateTimestamp = QDateTime::fromSecsSinceEpoch(device->getLastUpdateTimestamp()); +} + +AccountDeviceCore::~AccountDeviceCore() { + mustBeInMainThread("~" + getClassName()); +} + +void AccountDeviceCore::removeDevice() { + // TODO (core thread) +} diff --git a/Linphone/core/account/AccountDeviceCore.hpp b/Linphone/core/account/AccountDeviceCore.hpp new file mode 100644 index 000000000..062b558d9 --- /dev/null +++ b/Linphone/core/account/AccountDeviceCore.hpp @@ -0,0 +1,58 @@ +/* + * 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 . + */ + +#ifndef ACCOUNT_DEVICE_CORE_H_ +#define ACCOUNT_DEVICE_CORE_H_ + +#include +#include +#include +#include "tool/AbstractObject.hpp" +#include + +class AccountDeviceCore : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(QString deviceName MEMBER mDeviceName CONSTANT) + Q_PROPERTY(QString userAgent MEMBER mUserAgent CONSTANT) + Q_PROPERTY(QDateTime lastUpdateTimestamp MEMBER mLastUpdateTimestamp CONSTANT) + +public: + + static QSharedPointer create(const std::shared_ptr &device); + AccountDeviceCore(const std::shared_ptr &device); + AccountDeviceCore(QString name, QString userAgent, QDateTime last); + static QSharedPointer createDummy(QString name, QString userAgent, QDateTime last); + + + + ~AccountDeviceCore(); + Q_INVOKABLE void removeDevice(); + +private: + QString mDeviceName; + QString mUserAgent; + QDateTime mLastUpdateTimestamp; + + DECLARE_ABSTRACT_OBJECT + +}; + +#endif diff --git a/Linphone/core/account/AccountDeviceGui.cpp b/Linphone/core/account/AccountDeviceGui.cpp new file mode 100644 index 000000000..1a6e1a9dc --- /dev/null +++ b/Linphone/core/account/AccountDeviceGui.cpp @@ -0,0 +1,38 @@ +/* + * 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 . + */ + +#include "AccountDeviceGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(AccountDeviceGui) + +AccountDeviceGui::AccountDeviceGui(QSharedPointer core) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; + if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); +} + +AccountDeviceGui::~AccountDeviceGui() { + mustBeInMainThread("~" + getClassName()); +} + +AccountDeviceCore *AccountDeviceGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/account/AccountDeviceGui.hpp b/Linphone/core/account/AccountDeviceGui.hpp new file mode 100644 index 000000000..694d867bd --- /dev/null +++ b/Linphone/core/account/AccountDeviceGui.hpp @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#ifndef ACCOUNT_DEVICE_GUI_H_ +#define ACCOUNT_DEVICE_GUI_H_ + +#include "AccountDeviceCore.hpp" +#include +#include + +class AccountDeviceGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(AccountDeviceCore *core READ getCore CONSTANT) + +public: + AccountDeviceGui(QSharedPointer core); + ~AccountDeviceGui(); + AccountDeviceCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/notifier/Notifier.cpp b/Linphone/core/notifier/Notifier.cpp index a35973141..8017420a6 100644 --- a/Linphone/core/notifier/Notifier.cpp +++ b/Linphone/core/notifier/Notifier.cpp @@ -36,6 +36,7 @@ #include "tool/LinphoneEnums.hpp" #include "tool/providers/AvatarProvider.hpp" #include "tool/providers/ImageProvider.hpp" +#include "model/tool/ToolModel.hpp" DEFINE_ABSTRACT_OBJECT(Notifier) @@ -277,6 +278,16 @@ void Notifier::deleteNotification(QVariant notification) { void Notifier::notifyReceivedCall(const shared_ptr &call) { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto account = ToolModel::findAccount(call->getToAddress()); + if (account) { + auto accountModel = Utils::makeQObject_ptr(account); + accountModel->setSelf(accountModel); + if (!accountModel->getNotificationsAllowed()) { + qInfo() << "Notifications have been disabled for this account - not creating a notification for incoming call"; + return; + } + } + auto model = CallCore::create(call); auto gui = new CallGui(model); gui->moveToThread(App::getInstance()->thread()); diff --git a/Linphone/data/image/calendar.svg b/Linphone/data/image/calendar.svg index 5caacdbef..d566f699a 100644 --- a/Linphone/data/image/calendar.svg +++ b/Linphone/data/image/calendar.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/Linphone/data/image/desktop.svg b/Linphone/data/image/desktop.svg new file mode 100644 index 000000000..d4b44c70f --- /dev/null +++ b/Linphone/data/image/desktop.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/mobile.svg b/Linphone/data/image/mobile.svg new file mode 100644 index 000000000..342967e8f --- /dev/null +++ b/Linphone/data/image/mobile.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/model/CMakeLists.txt b/Linphone/model/CMakeLists.txt index bd7d1019f..5cb903321 100644 --- a/Linphone/model/CMakeLists.txt +++ b/Linphone/model/CMakeLists.txt @@ -3,7 +3,7 @@ list(APPEND _LINPHONEAPP_SOURCES model/account/AccountManager.cpp model/account/AccountManagerServicesModel.cpp model/account/AccountManagerServicesRequestModel.cpp - + model/auth/OIDCModel.cpp model/call/CallModel.cpp diff --git a/Linphone/model/account/AccountModel.cpp b/Linphone/model/account/AccountModel.cpp index f0207e8aa..5bae46662 100644 --- a/Linphone/model/account/AccountModel.cpp +++ b/Linphone/model/account/AccountModel.cpp @@ -57,8 +57,7 @@ void AccountModel::onRegistrationStateChanged(const std::shared_ptr(mMonitor); - auto params = account->getParams()->clone(); + auto params = mMonitor->getParams()->clone(); auto oldPictureUri = Utils::coreStringToAppString(params->getPictureUri()); if (!oldPictureUri.isEmpty()) { QString appPrefix = QStringLiteral("image://%1/").arg(AvatarProvider::ProviderId); @@ -69,11 +68,11 @@ void AccountModel::setPictureUri(QString uri) { if (!oldPicture.remove()) qWarning() << log().arg("Cannot delete old avatar file at " + oldPictureUri); } params->setPictureUri(Utils::appStringToCoreString(uri)); - account->setParams(params); + mMonitor->setParams(params); // Hack because Account doesn't provide callbacks on updated data // emit pictureUriChanged(uri); - emit CoreModel::getInstance()->defaultAccountChanged(CoreModel::getInstance()->getCore(), - CoreModel::getInstance()->getCore()->getDefaultAccount()); + emit CoreModel::getInstance() -> defaultAccountChanged(CoreModel::getInstance()->getCore(), + CoreModel::getInstance()->getCore()->getDefaultAccount()); } void AccountModel::onDefaultAccountChanged() { @@ -107,4 +106,179 @@ int AccountModel::getMissedCallsCount() const { int AccountModel::getUnreadMessagesCount() const { return mMonitor->getUnreadChatMessageCount(); -} \ No newline at end of file +} + +void AccountModel::setDisplayName(QString displayName) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + auto address = params->getIdentityAddress()->clone(); + address->setDisplayName(Utils::appStringToCoreString(displayName)); + params->setIdentityAddress(address); + mMonitor->setParams(params); + emit displayNameChanged(displayName); +} + +void AccountModel::setDialPlan(int index) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + std::string callingCode = ""; + std::string countryCode = ""; + if (index != -1) { + auto plans = linphone::Factory::get()->getDialPlans(); + std::vector> vectorPlans(plans.begin(), plans.end()); + auto plan = vectorPlans[index]; + callingCode = plan->getCountryCallingCode(); + countryCode = plan->getIsoCountryCode(); + } + auto params = mMonitor->getParams()->clone(); + params->setInternationalPrefix(callingCode); + params->setInternationalPrefixIsoCountryCode(countryCode); + mMonitor->setParams(params); + emit dialPlanChanged(index); +} + +void AccountModel::setRegisterEnabled(bool enabled) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + params->enableRegister(enabled); + mMonitor->setParams(params); + emit registerEnabledChanged(enabled); +} + +std::string AccountModel::getConfigAccountUiSection() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return "ui_" + mMonitor->getParams()->getIdentityAddress()->asStringUriOnly(); +} + +bool AccountModel::getNotificationsAllowed() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return mMonitor->getCore()->getConfig()->getBool(getConfigAccountUiSection(), "notifications_allowed", true); +} + +void AccountModel::setNotificationsAllowed(bool value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mMonitor->getCore()->getConfig()->setBool(getConfigAccountUiSection(), "notifications_allowed", value); + emit notificationsAllowedChanged(value); +} + +void AccountModel::setMwiServerAddress(QString value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + auto address = linphone::Factory::get()->createAddress(Utils::appStringToCoreString(value)); + if (address) { + params->setMwiServerAddress(address); + mMonitor->setParams(params); + emit mwiServerAddressChanged(value); + } else qWarning() << "Unable to set MWI address, failed creating address from" << value; +} + +void AccountModel::setTransport(linphone::TransportType value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + params->setTransport(value); + mMonitor->setParams(params); + emit transportChanged(value); +} + +void AccountModel::setServerAddress(QString value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + auto address = linphone::Factory::get()->createAddress(Utils::appStringToCoreString(value)); + if (address) { + params->setServerAddress(address); + mMonitor->setParams(params); + emit serverAddressChanged(value); + } else qWarning() << "Unable to set ServerAddress, failed creating address from" << value; +} + +void AccountModel::setOutboundProxyEnabled(bool value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + params->enableOutboundProxy(value); + mMonitor->setParams(params); + emit outboundProxyEnabledChanged(value); +} + +void AccountModel::setStunServer(QString value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + auto policy = params->getNatPolicy(); + if (!policy) policy = mMonitor->getCore()->createNatPolicy(); + policy->setStunServer(Utils::appStringToCoreString(value)); + params->setNatPolicy(policy); + mMonitor->setParams(params); + emit stunServerChanged(value); + qWarning() << "cdes stun server set to" << value; +} + +void AccountModel::setIceEnabled(bool value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + auto policy = params->getNatPolicy(); + if (!policy) policy = mMonitor->getCore()->createNatPolicy(); + policy->enableIce(value); + params->setNatPolicy(policy); + mMonitor->setParams(params); + emit iceEnabledChanged(value); +} + +void AccountModel::setAvpfEnabled(bool value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + params->setAvpfMode(value ? linphone::AVPFMode::Enabled : linphone::AVPFMode::Disabled); + mMonitor->setParams(params); + emit avpfEnabledChanged(value); +} + +void AccountModel::setBundleModeEnabled(bool value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + params->enableRtpBundle(value); + mMonitor->setParams(params); + emit bundleModeEnabledChanged(value); +} + +void AccountModel::setExpire(int value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + params->setExpires(value); + mMonitor->setParams(params); + emit expireChanged(value); +} + +void AccountModel::setConferenceFactoryAddress(QString value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + auto address = linphone::Factory::get()->createAddress(Utils::appStringToCoreString(value)); + if (address) { + params->setConferenceFactoryAddress(address); + mMonitor->setParams(params); + emit conferenceFactoryAddressChanged(value); + } else qWarning() << "Unable to set ConferenceFactoryAddress address, failed creating address from" << value; +} + +void AccountModel::setAudioVideoConferenceFactoryAddress(QString value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + auto address = linphone::Factory::get()->createAddress(Utils::appStringToCoreString(value)); + if (address) { + params->setAudioVideoConferenceFactoryAddress(address); + mMonitor->setParams(params); + emit audioVideoConferenceFactoryAddressChanged(value); + } else + qWarning() << "Unable to set AudioVideoConferenceFactoryAddress address, failed creating address from" << value; +} + +void AccountModel::setLimeServerUrl(QString value) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto params = mMonitor->getParams()->clone(); + auto address = linphone::Factory::get()->createAddress(Utils::appStringToCoreString(value)); + params->setLimeServerUrl(Utils::appStringToCoreString(value)); + mMonitor->setParams(params); + emit limeServerUrlChanged(value); +} + +QString AccountModel::dialPlanAsString(const std::shared_ptr &dialPlan) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return Utils::coreStringToAppString(dialPlan->getFlag() + " " + dialPlan->getCountry() + " | +" + + dialPlan->getCountryCallingCode()); +} diff --git a/Linphone/model/account/AccountModel.hpp b/Linphone/model/account/AccountModel.hpp index cc181e066..ab8ef4b74 100644 --- a/Linphone/model/account/AccountModel.hpp +++ b/Linphone/model/account/AccountModel.hpp @@ -40,6 +40,8 @@ public: const std::string &message) override; void onDefaultAccountChanged(); + std::string getConfigAccountUiSection(); + void setPictureUri(QString uri); void setDefault(); void removeAccount(); @@ -47,6 +49,24 @@ public: void refreshUnreadNotifications(); int getMissedCallsCount() const; int getUnreadMessagesCount() const; + void setDisplayName(QString displayName); + void setDialPlan(int index); + void setRegisterEnabled(bool enabled); + bool getNotificationsAllowed(); + void setNotificationsAllowed(bool value); + void setMwiServerAddress(QString value); + void setTransport(linphone::TransportType value); + void setServerAddress(QString value); + void setOutboundProxyEnabled(bool value); + void setStunServer(QString value); + void setIceEnabled(bool value); + void setAvpfEnabled(bool value); + void setBundleModeEnabled(bool value); + void setExpire(int value); + void setConferenceFactoryAddress(QString value); + void setAudioVideoConferenceFactoryAddress(QString value); + void setLimeServerUrl(QString value); + QString dialPlanAsString(const std::shared_ptr &dialPlan); signals: void registrationStateChanged(const std::shared_ptr &account, @@ -56,6 +76,22 @@ signals: void pictureUriChanged(QString uri); void unreadNotificationsChanged(int unreadMessagesCount, int unreadCallsCount); + void displayNameChanged(QString displayName); + void dialPlanChanged(int index); + void registerEnabledChanged(bool enabled); + void notificationsAllowedChanged(bool value); + void mwiServerAddressChanged(QString value); + void transportChanged(linphone::TransportType value); + void serverAddressChanged(QString value); + void outboundProxyEnabledChanged(bool value); + void stunServerChanged(QString value); + void iceEnabledChanged(bool value); + void avpfEnabledChanged(bool value); + void bundleModeEnabledChanged(bool value); + void expireChanged(int value); + void conferenceFactoryAddressChanged(QString value); + void audioVideoConferenceFactoryAddressChanged(QString value); + void limeServerUrlChanged(QString value); private: DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 95779d018..5b29b0aa9 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -32,9 +32,11 @@ #include #include +#include #include #include #include +#include // ============================================================================= @@ -260,6 +262,10 @@ QString Utils::formatDate(const QDateTime &date, bool includeTime) { return dateDay + " | " + time; } +QString Utils::formatTime(const QDateTime &date) { + return date.time().toString("hh:mm"); +} + QString Utils::formatDateElapsedTime(const QDateTime &date) { // auto y = floor(seconds / 31104000); // if (y > 0) return QString::number(y) + " years"; @@ -300,6 +306,35 @@ QString Utils::interpretUrl(const QString &uri) { return address; } +bool Utils::isValidSIPAddress(const QString &uri) { + bool isValid = false; + App::postModelBlock([&isValid, uri]() mutable { + isValid = linphone::Factory::get()->createAddress(Utils::appStringToCoreString(uri)) != nullptr; + }); + return isValid; +} + +bool Utils::isValidIPAddress(const QString &host) { + QHostAddress address; + return address.setAddress(host); +} + +bool Utils::isValidHostname(const QString &hostname) { + QRegularExpression regex("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?$"); + QStringList labels = hostname.split('.'); + if (labels.size() > 127) // More than 127 labels is invalid + return false; + foreach (const QString &label, labels) { + if (!regex.match(label).hasMatch() || label.length() > 63) // Label length must be between 1 and 63 + return false; + } + return hostname.length() <= 253; // Total length should be <= 253 +} + +bool Utils::isValidURL(const QString &url) { + return QUrl(url).isValid(); +} + QString Utils::findAvatarByAddress(const QString &address) { QString avatar; diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 8bd3b1af2..55e07b742 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -82,6 +82,7 @@ public: bool dotsSeparator = true); // Return the elapsed time formated Q_INVOKABLE static QString formatDate(const QDateTime &date, bool includeTime = true); // Return the date formated Q_INVOKABLE static QString formatDateElapsedTime(const QDateTime &date); + Q_INVOKABLE static QString formatTime(const QDateTime &date); // Return the time formated Q_INVOKABLE static QStringList generateSecurityLettersArray(int arraySize, int correctIndex, QString correctCode); Q_INVOKABLE static int getRandomIndex(int size); Q_INVOKABLE static bool copyToClipboard(const QString &text); @@ -112,6 +113,10 @@ public: Q_INVOKABLE static QDateTime addSecs(QDateTime date, int secs); Q_INVOKABLE static QDateTime addYears(QDateTime date, int years); Q_INVOKABLE static QString interpretUrl(const QString &uri); + Q_INVOKABLE static bool isValidSIPAddress(const QString &uri); + Q_INVOKABLE static bool isValidIPAddress(const QString &host); + Q_INVOKABLE static bool isValidHostname(const QString& hostname); + Q_INVOKABLE static bool isValidURL(const QString& url); Q_INVOKABLE static QString findAvatarByAddress(const QString &address); Q_INVOKABLE static VariantObject *findFriendByAddress(const QString &address); static QString generateSavedFilename(const QString &from, const QString &to); diff --git a/Linphone/view/App/Layout/Settings/GenericSettingsLayout.qml b/Linphone/view/App/Layout/AbstractDetailsLayout.qml similarity index 57% rename from Linphone/view/App/Layout/Settings/GenericSettingsLayout.qml rename to Linphone/view/App/Layout/AbstractDetailsLayout.qml index d155acd5d..6013c39a6 100644 --- a/Linphone/view/App/Layout/Settings/GenericSettingsLayout.qml +++ b/Linphone/view/App/Layout/AbstractDetailsLayout.qml @@ -7,17 +7,35 @@ import Linphone Rectangle { id: mainItem - anchors.fill: parent + width: container.width + height: container.height property string titleText property var component + property var model color: 'white' - - Rectangle { - width: parent.width - 2 * 45 * DefaultStyle.dp + property var container + + Control.ScrollView { + id: scrollView height: parent.height + width: parent.width - 2 * 45 * DefaultStyle.dp anchors.centerIn: parent - + contentHeight: content.height + 20 * DefaultStyle.dp + contentWidth: parent.width - 2 * 45 * DefaultStyle.dp + Control.ScrollBar.vertical: ScrollBar { + active: scrollView.contentHeight > container.height + interactive: true + policy: Control.ScrollBar.AsNeeded + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: -15 * DefaultStyle.dp + } + Control.ScrollBar.horizontal: ScrollBar { + active: false + } ColumnLayout { + id: content width: parent.width spacing: 10 * DefaultStyle.dp Text { diff --git a/Linphone/view/App/Layout/Account/AccountSettingsGeneralLayout.qml b/Linphone/view/App/Layout/Account/AccountSettingsGeneralLayout.qml new file mode 100644 index 000000000..c2d48a28a --- /dev/null +++ b/Linphone/view/App/Layout/Account/AccountSettingsGeneralLayout.qml @@ -0,0 +1,385 @@ +import QtCore +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as Control +import QtQuick.Dialogs +import Linphone +import SettingsCpp 1.0 +import UtilsCpp + +AbstractDetailsLayout { + id: mainItem + component: main + property alias account: mainItem.model + Component { + id: main + ColumnLayout { + width: parent.width + spacing: 5 * DefaultStyle.dp + RowLayout { + Layout.topMargin: 16 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + ColumnLayout { + Layout.fillWidth: true + spacing: 5 * DefaultStyle.dp + ColumnLayout { + Layout.preferredWidth: 341 * DefaultStyle.dp + Layout.maximumWidth: 341 * DefaultStyle.dp + Layout.minimumWidth: 341 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + Text { + Layout.fillWidth: true + text: qsTr("Détails") + font: Typography.h4 + wrapMode: Text.WordWrap + color: DefaultStyle.main2_600 + } + Text { + text: qsTr("Editer les informations de votre compte.") + font: Typography.p1s + wrapMode: Text.WordWrap + color: DefaultStyle.main2_600 + Layout.fillWidth: true + } + } + Item { + Layout.fillHeight: true + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 20 * DefaultStyle.dp + Layout.rightMargin: 44 * DefaultStyle.dp + Layout.topMargin: 20 * DefaultStyle.dp + Layout.leftMargin: 64 * DefaultStyle.dp + Avatar { + account: model + displayPresence: false + Layout.preferredWidth: 100 * DefaultStyle.dp + Layout.preferredHeight: 100 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + } + IconLabelButton { + visible: model.core.pictureUri.length === 0 + Layout.preferredWidth: width + Layout.preferredHeight: 17 * DefaultStyle.dp + iconSource: AppIcons.camera + iconSize: 17 * DefaultStyle.dp + text: qsTr("Ajouter une image") + onClicked: fileDialog.open() + Layout.alignment: Qt.AlignHCenter + color: DefaultStyle.main2_600 + } + RowLayout { + visible: model.core.pictureUri.length > 0 + Layout.alignment: Qt.AlignHCenter + spacing: 5 * DefaultStyle.dp + IconLabelButton { + Layout.preferredWidth: width + Layout.preferredHeight: 17 * DefaultStyle.dp + iconSource: AppIcons.pencil + iconSize: 17 * DefaultStyle.dp + text: qsTr("Modifier l'image") + color: DefaultStyle.main2_600 + onClicked: fileDialog.open() + } + IconLabelButton { + Layout.preferredWidth: width + Layout.preferredHeight: 17 * DefaultStyle.dp + iconSource: AppIcons.trashCan + iconSize: 17 * DefaultStyle.dp + text: qsTr("Supprimer l'image") + color: DefaultStyle.main2_600 + onClicked: model.core.pictureUri = "" + } + } + FileDialog { + id: fileDialog + currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + onAccepted: { + var avatarPath = UtilsCpp.createAvatar( selectedFile ) + if(avatarPath){ + model.core.pictureUri = avatarPath + } + } + } + RowLayout { + Layout.fillWidth: true + spacing: 5 * DefaultStyle.dp + Text { + Layout.alignment: Qt.AlignLeft + text: qsTr("Adresse SIP :") + color: DefaultStyle.main2_600 + font: Typography.p2l + } + Text { + Layout.alignment: Qt.AlignLeft + text: model.core.identityAddress + color: DefaultStyle.main2_600 + font: Typography.p1 + } + Item { + Layout.fillWidth: true + } + IconLabelButton { + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 20 * DefaultStyle.dp + Layout.preferredHeight: 20 * DefaultStyle.dp + iconSize: 24 * DefaultStyle.dp + iconSource: AppIcons.copy + color: DefaultStyle.main2_600 + onClicked: UtilsCpp.copyToClipboard(model.core.identityAddress) + } + } + ColumnLayout { + spacing: 5 * DefaultStyle.dp + Layout.alignment: Qt.AlignLeft + Text { + text: qsTr("Nom d’affichage") + color: DefaultStyle.main2_600 + font: Typography.p2l + } + Text { + text: qsTr("Le nom qui sera affiché à vos correspondants lors de vos échanges.") + color: DefaultStyle.main2_600 + font: Typography.p1 + } + } + TextField { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.preferredHeight: 49 * DefaultStyle.dp + initialText: model.core.displayName + backgroundColor: DefaultStyle.grey_100 + onEditingFinished: { + if (text.length != 0) model.core.displayName = text + } + } + Text { + text: qsTr("Indicatif international*") + color: DefaultStyle.main2_600 + font: Typography.p2l + } + ComboSetting { + Layout.fillWidth: true + Layout.topMargin: -15 * DefaultStyle.dp + entries: account.core.dialPlans + propertyName: "dialPlan" + propertyOwner: account.core + } + SwitchSetting { + titleText: account.core.humaneReadableRegistrationState + subTitleText: account.core.humaneReadableRegistrationStateExplained + propertyName: "registerEnabled" + propertyOwner: account.core + } + RowLayout { + id:mainItem + spacing : 20 * DefaultStyle.dp + ColumnLayout { + spacing : 5 * DefaultStyle.dp + Text { + text: qsTr("Supprimer mon compte") + font: Typography.p2l + wrapMode: Text.WordWrap + color: DefaultStyle.danger_500main + Layout.fillWidth: true + } + Text { + text: qsTr("Votre compte sera retiré de ce client linphone, mais vous restez connecté sur vos autres clients") + font: Typography.p1 + wrapMode: Text.WordWrap + color: DefaultStyle.main2_500main + Layout.fillWidth: true + } + } + Item { + Layout.fillWidth: true + } + Button { + background: Item{} + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 5 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + contentItem: RowLayout { + Layout.alignment: Qt.AlignRight + EffectImage { + imageSource: AppIcons.trashCan + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + colorizationColor: DefaultStyle.danger_500main + } + } + onClicked: { + var mainWin = UtilsCpp.getMainWindow() + mainWin.showConfirmationLambdaPopup( + qsTr("Supprimer ") + (model.core.displayName.length > 0 ? model.core.displayName : qsTr("le compte")) + " ?", + qsTr("Vous pouvez vous reconnecter à tout moment en cliquant sur \"Ajouter un compte\".\nCependant toutes les informations stockées sur ce périphérique seront supprimées."), + function (confirmed) { + if (confirmed) { + account.core.removeAccount() + } + } + ) + } + } + } + } + Component.onCompleted: { + } + Component.onDestruction: { + } + } + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 16 * DefaultStyle.dp + height: 1 * DefaultStyle.dp + color: DefaultStyle.main2_500main + } + RowLayout { + Layout.fillWidth: true + Layout.topMargin: 16 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + ColumnLayout { + Layout.fillWidth: true + spacing: 5 * DefaultStyle.dp + ColumnLayout { + Layout.preferredWidth: 341 * DefaultStyle.dp + Layout.maximumWidth: 341 * DefaultStyle.dp + Layout.minimumWidth: 341 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + Text { + Layout.fillWidth: true + text: qsTr("Vos appareils") + font: Typography.h4 + wrapMode: Text.WordWrap + color: DefaultStyle.main2_600 + } + Text { + text: qsTr("La liste des appareils connectés à votre compte. Vous pouvez retirer les appareils que vous n’utilisez plus. (TODO connecter API SDK quand dispo)") + font: Typography.p1s + wrapMode: Text.WordWrap + color: DefaultStyle.main2_600 + Layout.fillWidth: true + } + } + Item { + Layout.fillHeight: true + } + } + Rectangle { + color: DefaultStyle.grey_100 + Layout.fillWidth: true + Layout.minimumHeight: account.core.devices.length * 133 * DefaultStyle.dp + (account.core.devices.length - 1) * 15 * DefaultStyle.dp + 2 * 21 * DefaultStyle.dp + radius: 15 * DefaultStyle.dp + Layout.rightMargin: 30 * DefaultStyle.dp + Layout.topMargin: 20 * DefaultStyle.dp + Layout.bottomMargin: 21 * DefaultStyle.dp + Layout.leftMargin: 44 * DefaultStyle.dp + ColumnLayout { + anchors.fill: parent + anchors.bottomMargin: 21 * DefaultStyle.dp + spacing: 15 * DefaultStyle.dp + Repeater { + id: devices + model: account.core.devices + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 133 * DefaultStyle.dp + Layout.topMargin: (index == 0 ? 21 : 0) * DefaultStyle.dp + Layout.leftMargin: 17 * DefaultStyle.dp + Layout.rightMargin: 17 * DefaultStyle.dp + color: 'white' + radius: 10 * DefaultStyle.dp + ColumnLayout { + anchors.topMargin: 26 * DefaultStyle.dp + anchors.bottomMargin: 26 * DefaultStyle.dp + anchors.centerIn: parent + width: parent.width + spacing: 20 * DefaultStyle.dp + RowLayout { + Layout.rightMargin: 36 * DefaultStyle.dp + Layout.leftMargin: 33 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + EffectImage { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + colorizationColor: DefaultStyle.main2_600 + imageSource: modelData.core.userAgent.toLowerCase().includes('ios') || modelData.core.userAgent.toLowerCase().includes('android') ? AppIcons.mobile : AppIcons.desktop + } + Text { + text: modelData.core.deviceName + color: DefaultStyle.main2_600 + font: Typography.p2 + } + Item { + Layout.fillWidth: true + } + MediumButton { + Layout.alignment: Qt.AlignRight + text: qsTr("Supprimer") + icon.source: AppIcons.trashCan + icon.width: 16 * DefaultStyle.dp + icon.height: 16 * DefaultStyle.dp + contentImageColor: DefaultStyle.main1_500_main + onClicked: { + var mainWin = UtilsCpp.getMainWindow() + mainWin.showConfirmationLambdaPopup( + qsTr("Supprimer ") + modelData.core.deviceName + " ?", + function (confirmed) { + if (confirmed) { + modelData.core.removeDevice() + } + } + ) + } + } + } + RowLayout { + Layout.rightMargin: 36 * DefaultStyle.dp + Layout.leftMargin: 33 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + Text { + text: qsTr("Dernière connexion:") + color: DefaultStyle.main2_600 + font: Typography.p2 + } + EffectImage { + Layout.preferredWidth: 20 * DefaultStyle.dp + Layout.preferredHeight: 20 * DefaultStyle.dp + imageSource: AppIcons.calendar + colorizationColor: DefaultStyle.main2_600 + fillMode: Image.PreserveAspectFit + } + Text { + text: UtilsCpp.formatDate(modelData.core.lastUpdateTimestamp,false) + color: DefaultStyle.main2_600 + font: Typography.p1 + } + EffectImage { + Layout.preferredWidth: 20 * DefaultStyle.dp + Layout.preferredHeight: 20 * DefaultStyle.dp + imageSource: AppIcons.clock + colorizationColor: DefaultStyle.main2_600 + fillMode: Image.PreserveAspectFit + } + Text { + text: UtilsCpp.formatTime(modelData.core.lastUpdateTimestamp) + color: DefaultStyle.main2_600 + font: Typography.p1 + } + } + } + } + } + } + } + } + } + } +} diff --git a/Linphone/view/App/Layout/Account/AccountSettingsParametersLayout.qml b/Linphone/view/App/Layout/Account/AccountSettingsParametersLayout.qml new file mode 100644 index 000000000..c99f19758 --- /dev/null +++ b/Linphone/view/App/Layout/Account/AccountSettingsParametersLayout.qml @@ -0,0 +1,171 @@ +import QtCore +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as Control +import QtQuick.Dialogs +import Linphone +import SettingsCpp 1.0 +import UtilsCpp + +AbstractDetailsLayout { + id: mainItem + component: main + property alias account: mainItem.model + Component { + id: main + ColumnLayout { + width: parent.width + spacing: 5 * DefaultStyle.dp + RowLayout { + Layout.topMargin: 16 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + ColumnLayout { + Layout.fillWidth: true + spacing: 5 * DefaultStyle.dp + ColumnLayout { + Layout.preferredWidth: 341 * DefaultStyle.dp + Layout.maximumWidth: 341 * DefaultStyle.dp + Layout.minimumWidth: 341 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + Text { + Layout.fillWidth: true + text: qsTr("Paramètres") + font: Typography.h4 + wrapMode: Text.WordWrap + color: DefaultStyle.main2_600 + } + } + Item { + Layout.fillHeight: true + } + } + ColumnLayout { + id: column + Layout.fillWidth: true + spacing: 20 * DefaultStyle.dp + Layout.rightMargin: 44 * DefaultStyle.dp + Layout.leftMargin: 64 * DefaultStyle.dp + Layout.topMargin: 20 * DefaultStyle.dp + ValidatedTextField { + propertyName: "mwiServerAddress" + propertyOwner: account.core + title: qsTr("URI du serveur de messagerie vocale") + isValid: function(text) { return UtilsCpp.isValidSIPAddress(text); } + } + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } + } + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 16 * DefaultStyle.dp + height: 1 * DefaultStyle.dp + color: DefaultStyle.main2_500main + } + RowLayout { + Layout.topMargin: 16 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + ColumnLayout { + Layout.fillWidth: true + spacing: 5 * DefaultStyle.dp + ColumnLayout { + Layout.preferredWidth: 341 * DefaultStyle.dp + Layout.maximumWidth: 341 * DefaultStyle.dp + Layout.minimumWidth: 341 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + Text { + Layout.fillWidth: true + text: qsTr("Paramètres avancés") + font: Typography.h4 + wrapMode: Text.WordWrap + color: DefaultStyle.main2_600 + } + } + Item { + Layout.fillHeight: true + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 20 * DefaultStyle.dp + Layout.rightMargin: 44 * DefaultStyle.dp + Layout.topMargin: 20 * DefaultStyle.dp + Layout.leftMargin: 64 * DefaultStyle.dp + Text { + text: qsTr("Transport") + color: DefaultStyle.main2_600 + font: Typography.p2l + } + ComboSetting { + Layout.fillWidth: true + Layout.topMargin: -15 * DefaultStyle.dp + entries: account.core.transports + propertyName: "transport" + propertyOwner: account.core + } + ValidatedTextField { + title: qsTr("URL du serveur mandataire") + propertyName: "serverAddress" + propertyOwner: account.core + isValid: function(text) { return UtilsCpp.isValidSIPAddress(text); } + } + SwitchSetting { + titleText: qsTr("Serveur mandataire sortant") + propertyName: "outboundProxyEnabled" + propertyOwner: account.core + } + ValidatedTextField { + propertyName: "stunServer" + propertyOwner: account.core + title: qsTr("Adresse du serveur STUN") + isValid: function(text) { return UtilsCpp.isValidIPAddress(text) || UtilsCpp.isValidHostname(text); } + } + SwitchSetting { + titleText: qsTr("Activer ICE") + propertyName: "iceEnabled" + propertyOwner: account.core + } + SwitchSetting { + titleText: qsTr("AVPF") + propertyName: "avpfEnabled" + propertyOwner: account.core + } + SwitchSetting { + titleText: qsTr("Mode bundle") + propertyName: "bundleModeEnabled" + propertyOwner: account.core + } + ValidatedTextField { + propertyName: "expire" + propertyOwner: account.core + title: qsTr("Expiration (en seconde)") + isValid: function(text) { return !isNaN(Number(text)); } + } + ValidatedTextField { + title: qsTr("URI de l’usine à conversations") + propertyName: "conferenceFactoryAddress" + propertyOwner: account.core + isValid: function(text) { return UtilsCpp.isValidSIPAddress(text); } + } + ValidatedTextField { + title: qsTr("URI de l’usine à réunions") + propertyName: "audioVideoConferenceFactoryAddress" + propertyOwner: account.core + isValid: function(text) { return UtilsCpp.isValidSIPAddress(text); } + } + ValidatedTextField { + title: qsTr("URL du serveur d’échange de clés de chiffrement") + propertyName: "limeServerUrl" + propertyOwner: account.core + isValid: function(text) { return UtilsCpp.isValidURL(text); } + } + Item { + Layout.fillHeight: true + } + } + } + } + } +} diff --git a/Linphone/view/App/Layout/MainLayout.qml b/Linphone/view/App/Layout/MainLayout.qml index a1215eabc..74948cf19 100644 --- a/Linphone/view/App/Layout/MainLayout.qml +++ b/Linphone/view/App/Layout/MainLayout.qml @@ -15,9 +15,8 @@ import SettingsCpp Item { id: mainItem property var callObj - property bool settingsHidden: true - property bool helpHidden: true - + property var contextualMenuOpenedComponent: undefined + signal addAccountRequest() signal openNewCall() signal openCallHistory() @@ -41,6 +40,23 @@ Item { tabbar.currentIndex = 1 mainItem.createContactRequested(name, address) } + + function openContextualMenuComponent(component) { + if (mainItem.contextualMenuOpenedComponent && mainItem.contextualMenuOpenedComponent != component) { + mainStackView.pop() + mainItem.contextualMenuOpenedComponent = undefined + } + if (!mainItem.contextualMenuOpenedComponent) { + mainStackView.push(component) + mainItem.contextualMenuOpenedComponent = component + } + settingsButton.popup.close() + } + + function closeContextualMenuComponent() { + mainStackView.pop() + mainItem.contextualMenuOpenedComponent = undefined + } AccountProxy { id: accountProxy @@ -99,13 +115,8 @@ Item { ] onCurrentIndexChanged: { if (currentIndex === 0) accountProxy.defaultAccount.core.lResetMissedCalls() - if (!mainItem.settingsHidden) { - mainStackView.pop() - mainItem.settingsHidden = true - } - if (!mainItem.helpHidden) { - mainStackView.pop() - mainItem.helpHidden = true + if (mainItem.contextualMenuOpenedComponent) { + closeContextualMenuComponent() } } } @@ -302,7 +313,7 @@ Item { iconSize: 32 * DefaultStyle.dp text: qsTr("Mon compte") iconSource: AppIcons.manageProfile - onClicked: console.log("TODO : manage profile") + onClicked: openContextualMenuComponent(myAccountSettingsPageComponent) } IconLabelButton { Layout.preferredHeight: 32 * DefaultStyle.dp @@ -310,19 +321,7 @@ Item { iconSize: 32 * DefaultStyle.dp text: qsTr("Paramètres") iconSource: AppIcons.settings - onClicked: { - if (!mainItem.helpHidden) { - mainStackView.pop() - mainItem.helpHidden = true - } - if (mainItem.settingsHidden) { - mainStackView.push(settingsPageComponent) - settingsButton.popup.close() - mainItem.settingsHidden = false - } else { - settingsButton.popup.close() - } - } + onClicked: openContextualMenuComponent(settingsPageComponent) } IconLabelButton { Layout.preferredHeight: 32 * DefaultStyle.dp @@ -336,19 +335,7 @@ Item { iconSize: 32 * DefaultStyle.dp text: qsTr("Aide") iconSource: AppIcons.question - onClicked: { - if (!mainItem.settingsHidden) { - mainStackView.pop() - mainItem.settingsHidden = true - } - if (mainItem.helpHidden) { - mainStackView.push(helpPageComponent) - settingsButton.popup.close() - mainItem.helpHidden = false - } else { - settingsButton.popup.close() - } - } + onClicked: openContextualMenuComponent(helpPageComponent) } Rectangle { Layout.fillWidth: true @@ -402,22 +389,23 @@ Item { MeetingPage{} } } + Component { + id: myAccountSettingsPageComponent + AccountSettingsPage { + account: accountProxy.defaultAccount + onGoBack: closeContextualMenuComponent() + } + } Component { id: settingsPageComponent SettingsPage { - onGoBack: { - mainStackView.pop() - mainItem.settingsHidden = true - } + onGoBack: closeContextualMenuComponent() } } Component { id: helpPageComponent HelpPage { - onGoBack: { - mainStackView.pop() - mainItem.helpHidden = true - } + onGoBack: closeContextualMenuComponent() } } Control.StackView { diff --git a/Linphone/view/App/Layout/Settings/CallSettingsLayout.qml b/Linphone/view/App/Layout/Settings/CallSettingsLayout.qml index af1ed0c1b..17a6650e8 100644 --- a/Linphone/view/App/Layout/Settings/CallSettingsLayout.qml +++ b/Linphone/view/App/Layout/Settings/CallSettingsLayout.qml @@ -5,7 +5,7 @@ import QtQuick.Controls as Control import Linphone import SettingsCpp 1.0 -GenericSettingsLayout { +AbstractDetailsLayout { component: settings width: parent.width Component { @@ -29,12 +29,14 @@ GenericSettingsLayout { titleText: qsTr("Annulateur d'écho") subTitleText: qsTr("Évite que de l'écho soit entendu par votre correspondant") propertyName: "echoCancellationEnabled" + propertyOwner: SettingsCpp } SwitchSetting { Layout.fillWidth: true titleText: qsTr("Activer l’enregistrement automatique des appels") subTitleText: qsTr("Enregistrer tous les appels par défaut") propertyName: "automaticallyRecordCallsEnabled" + propertyOwner: SettingsCpp } } } @@ -101,8 +103,9 @@ GenericSettingsLayout { Layout.fillWidth: true Layout.topMargin: 12 * DefaultStyle.dp Layout.preferredWidth: parent.width - model: SettingsCpp.playbackDevices + entries: SettingsCpp.playbackDevices propertyName: "playbackDevice" + propertyOwner: SettingsCpp } Slider { id: speakerVolume @@ -145,8 +148,9 @@ GenericSettingsLayout { Layout.topMargin: 12 * DefaultStyle.dp Layout.bottomMargin: 22 * DefaultStyle.dp Layout.preferredWidth: parent.width - model: SettingsCpp.captureDevices + entries: SettingsCpp.captureDevices propertyName: "captureDevice" + propertyOwner: SettingsCpp } Slider { id: microVolume @@ -224,8 +228,9 @@ GenericSettingsLayout { Layout.fillWidth: true Layout.topMargin: 12 * DefaultStyle.dp Layout.preferredWidth: parent.width - model: SettingsCpp.videoDevices + entries: SettingsCpp.videoDevices propertyName: "videoDevice" + propertyOwner: SettingsCpp } Item { Layout.fillHeight: true diff --git a/Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml b/Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml index 5a7e164db..65cedcd1f 100644 --- a/Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml +++ b/Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml @@ -7,7 +7,7 @@ import Linphone import SettingsCpp 1.0 import UtilsCpp 1.0 -GenericSettingsLayout { +AbstractDetailsLayout { Layout.fillWidth: true Layout.fillHeight: true id: mainItem @@ -50,19 +50,21 @@ GenericSettingsLayout { SwitchSetting { titleText: qsTr("Activer les traces de débogage") propertyName: "logsEnabled" + propertyOwner: SettingsCpp } SwitchSetting { titleText: qsTr("Activer les traces de débogage intégrales") propertyName: "fullLogsEnabled" + propertyOwner: SettingsCpp } MediumButton { - text: qsTr("Supprimer les traces") + text: qsTr("Supprimer les traces") onClicked: { deleteLogs.open() } } MediumButton { - text: qsTr("Partager les traces") + text: qsTr("Partager les traces") enabled: SettingsCpp.logsEnabled || SettingsCpp.fullLogsEnabled onClicked: { UtilsCpp.getMainWindow().showLoadingPopup(qsTr("Téléversement des traces en cours ...")) diff --git a/Linphone/view/App/Layout/Settings/SecuritySettingsLayout.qml b/Linphone/view/App/Layout/Settings/SecuritySettingsLayout.qml index 649b072fc..f9a4430cf 100644 --- a/Linphone/view/App/Layout/Settings/SecuritySettingsLayout.qml +++ b/Linphone/view/App/Layout/Settings/SecuritySettingsLayout.qml @@ -2,10 +2,10 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls as Control - +import SettingsCpp 1.0 import Linphone -GenericSettingsLayout { +AbstractDetailsLayout { Component { id: settings Column { @@ -14,6 +14,7 @@ GenericSettingsLayout { titleText: qsTr("Chiffrer tous les fichiers") subTitleText: qsTr("Attention, vous ne pourrez pas revenir en arrière !") propertyName: "vfsEnabled" + propertyOwner: SettingsCpp } } } diff --git a/Linphone/view/App/Main.qml b/Linphone/view/App/Main.qml index 54936656e..1a2ef2b1b 100644 --- a/Linphone/view/App/Main.qml +++ b/Linphone/view/App/Main.qml @@ -49,6 +49,9 @@ AppWindow { AccountProxy { id: accountProxy + onHaveAccountChanged: { + initStackViewItem() + } } StackView { id: mainWindowStackView diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 64cb5009f..aefd210cf 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -14,14 +14,18 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Layout/Meeting/AddParticipantsLayout.qml view/Layout/FormItemLayout.qml + view/Layout/ValidatedTextField.qml view/Layout/Mosaic.qml view/Layout/RightPanelLayout.qml view/Layout/Section.qml - view/App/Layout/Settings/GenericSettingsLayout.qml + view/App/Layout/AbstractDetailsLayout.qml view/App/Layout/Settings/SecuritySettingsLayout.qml view/App/Layout/Settings/CallSettingsLayout.qml view/App/Layout/Settings/DebugSettingsLayout.qml + + view/App/Layout/Account/AccountSettingsGeneralLayout.qml + view/App/Layout/Account/AccountSettingsParametersLayout.qml view/Item/Account/Accounts.qml @@ -96,7 +100,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/Test/ItemsTest.qml - view/Item/Settings/SettingsFamily.qml + view/Item/MasterDetailFamily.qml view/Item/Settings/SwitchSetting.qml view/Item/Settings/ComboSetting.qml @@ -114,8 +118,11 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Page/Main/ContactPage.qml view/Page/Main/MeetingPage.qml + view/Page/Main/AbstractMasterDetailPage.qml view/Page/Main/SettingsPage.qml view/Page/Main/HelpPage.qml + view/Page/Main/AccountSettingsPage.qml + view/Tool/utils.js # Prototypes diff --git a/Linphone/view/Item/ComboBox.qml b/Linphone/view/Item/ComboBox.qml index 7df578f62..8d568634e 100644 --- a/Linphone/view/Item/ComboBox.qml +++ b/Linphone/view/Item/ComboBox.qml @@ -105,6 +105,7 @@ Control.ComboBox { width: mainItem.width implicitHeight: contentItem.implicitHeight padding: 1 * DefaultStyle.dp + height: Math.min(listView.contentHeight, 300) contentItem: ListView { id: listView diff --git a/Linphone/view/Item/Settings/SettingsFamily.qml b/Linphone/view/Item/MasterDetailFamily.qml similarity index 100% rename from Linphone/view/Item/Settings/SettingsFamily.qml rename to Linphone/view/Item/MasterDetailFamily.qml diff --git a/Linphone/view/Item/Settings/ComboSetting.qml b/Linphone/view/Item/Settings/ComboSetting.qml index c83a0cd03..94ab741ef 100644 --- a/Linphone/view/Item/Settings/ComboSetting.qml +++ b/Linphone/view/Item/Settings/ComboSetting.qml @@ -2,23 +2,24 @@ import QtQuick import QtQuick.Controls.Material import QtQuick.Layouts import Linphone -import SettingsCpp 1.0 import 'qrc:/Linphone/view/Tool/utils.js' as Utils ComboBox { id: comboBox Layout.preferredHeight: 49 * DefaultStyle.dp property string propertyName + property var propertyOwner + property alias entries: comboBox.model oneLine: true currentIndex: Utils.findIndex(model, function (entry) { - return entry === SettingsCpp[propertyName] + return entry === propertyOwner[propertyName] }) onCurrentTextChanged: { - binding.when = currentText != SettingsCpp[propertyName] + binding.when = currentText != propertyOwner[propertyName] } Binding { id: binding - target: SettingsCpp + target: propertyOwner property: propertyName value: comboBox.currentText when: false diff --git a/Linphone/view/Item/Settings/SwitchSetting.qml b/Linphone/view/Item/Settings/SwitchSetting.qml index 6a92574be..c85a913de 100644 --- a/Linphone/view/Item/Settings/SwitchSetting.qml +++ b/Linphone/view/Item/Settings/SwitchSetting.qml @@ -2,19 +2,20 @@ import QtQuick import QtQuick.Controls.Material import QtQuick.Layouts import Linphone -import SettingsCpp 1.0 RowLayout { id:mainItem property string titleText property string subTitleText property string propertyName + property var propertyOwner property bool enabled: true spacing : 20 * DefaultStyle.dp + Layout.minimumHeight: 32 * DefaultStyle.dp ColumnLayout { Text { text: titleText - font: Typography.p2 + font: Typography.p2l wrapMode: Text.WordWrap color: DefaultStyle.main2_600 Layout.fillWidth: true @@ -30,8 +31,8 @@ RowLayout { } SwitchButton { id: switchButton - Layout.alignment: Qt.AlignRight - checked: SettingsCpp[mainItem.propertyName] + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + checked: propertyOwner[mainItem.propertyName] enabled: mainItem.enabled onToggled: { binding.when = true @@ -39,7 +40,7 @@ RowLayout { } Binding { id: binding - target: SettingsCpp + target: propertyOwner property: mainItem.propertyName value: switchButton.checked when: false diff --git a/Linphone/view/Item/TextField.qml b/Linphone/view/Item/TextField.qml index 903f558c0..3e8b2f986 100644 --- a/Linphone/view/Item/TextField.qml +++ b/Linphone/view/Item/TextField.qml @@ -5,7 +5,8 @@ import Linphone Control.TextField { id: mainItem - width: 360 * DefaultStyle.dp + property var customWidth + width: (customWidth ? customWidth - 1 : 360) * DefaultStyle.dp height: 49 * DefaultStyle.dp leftPadding: 15 * DefaultStyle.dp rightPadding: eyeButton.visible ? 5 * DefaultStyle.dp + eyeButton.width + eyeButton.rightMargin : 15 * DefaultStyle.dp diff --git a/Linphone/view/Layout/FormItemLayout.qml b/Linphone/view/Layout/FormItemLayout.qml index 0f928852a..ce3383057 100644 --- a/Linphone/view/Layout/FormItemLayout.qml +++ b/Linphone/view/Layout/FormItemLayout.qml @@ -48,4 +48,4 @@ ColumnLayout { } } -} \ No newline at end of file +} diff --git a/Linphone/view/Layout/ValidatedTextField.qml b/Linphone/view/Layout/ValidatedTextField.qml new file mode 100644 index 000000000..1f3d05c21 --- /dev/null +++ b/Linphone/view/Layout/ValidatedTextField.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Controls as Control +import QtQuick.Layouts 1.0 +import QtQuick.Effects +import UtilsCpp +import Linphone + +FormItemLayout { + id: mainItem + label: title + mandatory: false + enableErrorText: true + property string propertyName + property var propertyOwner + property var title + property var placeHolder + property bool useTitleAsPlaceHolder: true + property int idleTimeOut: 200 + property var isValid: function(text) { + return true; + } + contentItem: TextField { + id: textField + property var initialReading: true + placeholderText: useTitleAsPlaceHolder ? mainItem.title : mainItem.placeHolder + initialText: mainItem.propertyOwner[mainItem.propertyName] + customWidth: mainItem.parent.width + Timer { + id: idleTimer + running: false + interval: mainItem.idleTimeOut + repeat: false + onTriggered: textField.editingFinished() + } + onEditingFinished: { + if (initialReading) { + initialReading = false + return + } + if (text.length != 0) { + if (isValid(text)) { + mainItem.errorMessage = "" + if (mainItem.propertyOwner[mainItem.propertyName] != text) + mainItem.propertyOwner[mainItem.propertyName] = text + } else { + mainItem.errorMessage = qsTr("Format non reconnu") + } + } else + mainItem.errorMessage = "" + } + onTextChanged: { + idleTimer.restart() + } + } +} + diff --git a/Linphone/view/Page/Main/AbstractMasterDetailPage.qml b/Linphone/view/Page/Main/AbstractMasterDetailPage.qml new file mode 100644 index 000000000..2866940d1 --- /dev/null +++ b/Linphone/view/Page/Main/AbstractMasterDetailPage.qml @@ -0,0 +1,83 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp + +AbstractMainPage { + + id: mainItem + showDefaultItem: false + + property var layoutsPath + property var titleText + + signal goBack() + + function layoutUrl(name) { + return layoutsPath+"/"+name+".qml" + } + + property var families + + leftPanelContent: ColumnLayout { + id: leftPanel + Layout.fillWidth: true + Layout.fillHeight: true + property int sideMargin: 45 * DefaultStyle.dp + spacing: 5 * DefaultStyle.dp + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + spacing: 5 * DefaultStyle.dp + Button { + Layout.preferredHeight: 24 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + icon.source: AppIcons.leftArrow + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + background: Item { + anchors.fill: parent + } + onClicked: { + mainItem.goBack() + } + } + Text { + text: titleText + color: DefaultStyle.main2_700 + font: Typography.h3 + } + Item { + Layout.fillWidth: true + } + } + + ListView { + id: familiesList + Layout.fillWidth: true + Layout.fillHeight: true + model: mainItem.families + Layout.topMargin: 41 * DefaultStyle.dp + Layout.leftMargin: leftPanel.sideMargin + property int selectedIndex: 0 + + delegate: MasterDetailFamily { + titleText: modelData.title + visible: modelData.visible != undefined ? modelData.visible : true + isSelected: familiesList.selectedIndex == index + onSelected: { + familiesList.selectedIndex = index + rightPanelStackView.clear() + rightPanelStackView.push(layoutUrl(modelData.layout), { titleText: modelData.title, model: modelData.model, container: rightPanelStackView}) + } + } + } + Component.onCompleted: { + let initialEntry = mainItem.families[familiesList.selectedIndex] + rightPanelStackView.push(layoutUrl(initialEntry.layout), { titleText: initialEntry.title, model: initialEntry.model, container: rightPanelStackView}) + } + } +} diff --git a/Linphone/view/Page/Main/AccountSettingsPage.qml b/Linphone/view/Page/Main/AccountSettingsPage.qml new file mode 100644 index 000000000..b639e9fb2 --- /dev/null +++ b/Linphone/view/Page/Main/AccountSettingsPage.qml @@ -0,0 +1,18 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 +import SettingsCpp 1.0 + +AbstractMasterDetailPage { + layoutsPath: "qrc:/Linphone/view/App/Layout/Account" + titleText: qsTr("Mon compte") + property AccountProxy accounts: AccountProxy{id: accountProxy} + property AccountGui account: accountProxy.defaultAccount + families: [ + {title: qsTr("Général"), layout: "AccountSettingsGeneralLayout", model: account}, + {title: qsTr("Paramètres de compte"), layout: "AccountSettingsParametersLayout", model: account} + ] +} diff --git a/Linphone/view/Page/Main/HelpPage.qml b/Linphone/view/Page/Main/HelpPage.qml index 0ce66764a..60820ab4c 100644 --- a/Linphone/view/Page/Main/HelpPage.qml +++ b/Linphone/view/Page/Main/HelpPage.qml @@ -18,11 +18,12 @@ AbstractMainPage { Layout.fillWidth: true Layout.fillHeight: true property int sideMargin: 45 * DefaultStyle.dp - + spacing: 5 * DefaultStyle.dp RowLayout { Layout.fillWidth: true Layout.leftMargin: leftPanel.sideMargin Layout.rightMargin: leftPanel.sideMargin + spacing: 5 * DefaultStyle.dp Button { Layout.preferredHeight: 24 * DefaultStyle.dp Layout.preferredWidth: 24 * DefaultStyle.dp diff --git a/Linphone/view/Page/Main/SettingsPage.qml b/Linphone/view/Page/Main/SettingsPage.qml index 69d301107..55c29a21f 100644 --- a/Linphone/view/Page/Main/SettingsPage.qml +++ b/Linphone/view/Page/Main/SettingsPage.qml @@ -2,22 +2,12 @@ import QtQuick import QtQuick.Effects import QtQuick.Layouts import QtQuick.Controls as Control -import Linphone -import UtilsCpp import SettingsCpp -AbstractMainPage { - - id: mainItem - showDefaultItem: false - - signal goBack() - - function layoutUrl(name) { - return "qrc:/Linphone/view/App/Layout/Settings/"+name+".qml" - } - - property var settingsFamilies: [ +AbstractMasterDetailPage { + layoutsPath: "qrc:/Linphone/view/App/Layout/Settings" + titleText: qsTr("Paramètres") + families: [ {title: qsTr("Appels"), layout: "CallSettingsLayout"}, //{title: qsTr("Sécurité"), layout: "SecuritySettingsLayout"}, {title: qsTr("Conversations"), layout: "ChatSettingsLayout", visible: !SettingsCpp.disableChatFeature}, @@ -27,63 +17,4 @@ AbstractMainPage { {title: qsTr("Réseau"), layout: "NetworkSettingsLayout"}, {title: qsTr("Paramètres avancés"), layout: "AdvancedSettingsLayout"} ] - - leftPanelContent: ColumnLayout { - id: leftPanel - Layout.fillWidth: true - Layout.fillHeight: true - property int sideMargin: 45 * DefaultStyle.dp - - RowLayout { - Layout.fillWidth: true - Layout.leftMargin: leftPanel.sideMargin - Layout.rightMargin: leftPanel.sideMargin - Button { - Layout.preferredHeight: 24 * DefaultStyle.dp - Layout.preferredWidth: 24 * DefaultStyle.dp - icon.source: AppIcons.leftArrow - width: 24 * DefaultStyle.dp - height: 24 * DefaultStyle.dp - background: Item { - anchors.fill: parent - } - onClicked: { - mainItem.goBack() - } - } - Text { - text: qsTr("Paramètres") - color: DefaultStyle.main2_700 - font: Typography.h3 - } - Item { - Layout.fillWidth: true - } - } - - ListView { - id: settingsFamiliesList - Layout.fillWidth: true - Layout.fillHeight: true - model: mainItem.settingsFamilies - Layout.topMargin: 41 * DefaultStyle.dp - Layout.leftMargin: leftPanel.sideMargin - property int selectedIndex: 0 - - delegate: SettingsFamily { - titleText: modelData.title - visible: modelData.visible != undefined ? modelData.visible : true - isSelected: settingsFamiliesList.selectedIndex == index - onSelected: { - settingsFamiliesList.selectedIndex = index - rightPanelStackView.clear() - rightPanelStackView.push(layoutUrl(modelData.layout), { titleText: modelData.title }) - } - } - } - Component.onCompleted: { - let initialEntry = mainItem.settingsFamilies[settingsFamiliesList.selectedIndex] - rightPanelStackView.push(layoutUrl(initialEntry.layout), { titleText: initialEntry.title }) - } - } } diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index f2d880991..62275fc62 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -109,4 +109,7 @@ QtObject { property string cellSignalMedium: "image://internal/cell-signal-medium.svg" property string cellSignalLow: "image://internal/cell-signal-low.svg" property string cellSignalNone: "image://internal/cell-signal-none.svg" + property string mobile: "image://internal/mobile.svg" + property string desktop: "image://internal/desktop.svg" + property string calendar: "image://internal/calendar.svg" } diff --git a/Linphone/view/Style/Typography.qml b/Linphone/view/Style/Typography.qml index 84ce77eda..0761a98ec 100644 --- a/Linphone/view/Style/Typography.qml +++ b/Linphone/view/Style/Typography.qml @@ -52,6 +52,13 @@ QtObject { weight: 400 * DefaultStyle.dp }) + // Text/P1 - Paratraph text + property font p1s: Qt.font( { + family: DefaultStyle.defaultFont, + pixelSize: 13 * DefaultStyle.dp, + weight: 400 * DefaultStyle.dp + }) + // Bouton/B2 - Medium Bouton property font b2: Qt.font( { family: DefaultStyle.defaultFont,