From 305973038daac02f42ab44cf2d2d3db5d5764955 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Fri, 2 May 2025 21:15:44 +0200 Subject: [PATCH] Call Forward --- Linphone/core/setting/SettingsCore.cpp | 7 + Linphone/core/setting/SettingsCore.hpp | 2 + Linphone/data/image/call-forward.svg | 5 + Linphone/data/languages/en.ts | 99 +++++++++++++ Linphone/model/setting/SettingsModel.cpp | 31 +++- Linphone/model/setting/SettingsModel.hpp | 5 + Linphone/view/CMakeLists.txt | 1 + .../view/Control/Input/DecoratedTextField.qml | 4 + .../Form/Settings/AbstractSettingsMenu.qml | 3 +- .../view/Page/Form/Settings/SettingsPage.qml | 2 + Linphone/view/Page/Layout/Main/MainLayout.qml | 6 + .../Settings/CallForwardSettingsLayout.qml | 139 ++++++++++++++++++ Linphone/view/Page/Main/Call/CallPage.qml | 38 +++++ Linphone/view/Style/AppIcons.qml | 1 + Linphone/view/Style/DefaultStyle.qml | 3 +- 15 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 Linphone/data/image/call-forward.svg create mode 100644 Linphone/view/Page/Layout/Settings/CallForwardSettingsLayout.qml diff --git a/Linphone/core/setting/SettingsCore.cpp b/Linphone/core/setting/SettingsCore.cpp index af50e269..f02743db 100644 --- a/Linphone/core/setting/SettingsCore.cpp +++ b/Linphone/core/setting/SettingsCore.cpp @@ -124,6 +124,8 @@ SettingsCore::SettingsCore(QObject *parent) : QObject(parent) { INIT_CORE_MEMBER(CallToneIndicationsEnabled, settingsModel) INIT_CORE_MEMBER(CommandLine, settingsModel) INIT_CORE_MEMBER(DisableCommandLine, settingsModel) + INIT_CORE_MEMBER(DisableCallForward, settingsModel) + INIT_CORE_MEMBER(CallForwardToAddress, settingsModel) } SettingsCore::SettingsCore(const SettingsCore &settingsCore) { @@ -198,6 +200,7 @@ SettingsCore::SettingsCore(const SettingsCore &settingsCore) { mCallToneIndicationsEnabled = settingsCore.mCallToneIndicationsEnabled; mCommandLine = settingsCore.mCommandLine; mDisableCommandLine = settingsCore.mDisableCommandLine; + mDisableCallForward = settingsCore.mDisableCallForward; mDefaultDomain = settingsCore.mDefaultDomain; mShowAccountDevices = settingsCore.mShowAccountDevices; @@ -417,6 +420,10 @@ void SettingsCore::setSelf(QSharedPointer me) { commandLine, CommandLine) DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, bool, disableCommandLine, DisableCommandLine) + DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, bool, + disableCallForward, DisableCallForward) + DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, QString, + callForwardToAddress, CallForwardToAddress) auto coreModelConnection = SafeConnection::create(me, CoreModel::getInstance()); diff --git a/Linphone/core/setting/SettingsCore.hpp b/Linphone/core/setting/SettingsCore.hpp index 94324e9f..bc57a0d7 100644 --- a/Linphone/core/setting/SettingsCore.hpp +++ b/Linphone/core/setting/SettingsCore.hpp @@ -231,6 +231,8 @@ public: DECLARE_CORE_GETSET_MEMBER(bool, callToneIndicationsEnabled, CallToneIndicationsEnabled) DECLARE_CORE_GETSET_MEMBER(bool, disableCommandLine, DisableCommandLine) DECLARE_CORE_GETSET_MEMBER(QString, commandLine, CommandLine) + DECLARE_CORE_GETSET_MEMBER(bool, disableCallForward, DisableCallForward) + DECLARE_CORE_GETSET_MEMBER(QString, callForwardToAddress, CallForwardToAddress) signals: diff --git a/Linphone/data/image/call-forward.svg b/Linphone/data/image/call-forward.svg new file mode 100644 index 00000000..b864059a --- /dev/null +++ b/Linphone/data/image/call-forward.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Linphone/data/languages/en.ts b/Linphone/data/languages/en.ts index d2d3b792..d31233c0 100644 --- a/Linphone/data/languages/en.ts +++ b/Linphone/data/languages/en.ts @@ -871,6 +871,18 @@ CallPage + + + call_forward_to_address_info + "Forward calls to: " + Forward calls to: + + + + call_forward_to_address_info_voicemail + "Voicemail" + Voicemail + history_call_start_title @@ -3930,6 +3942,12 @@ To enable them in a commercial project, please contact us. "Appels" Calls + + + settings_call_forward + "Transfert d'appels" + Call forward + settings_conversations_title @@ -5506,4 +5524,85 @@ To enable them in a commercial project, please contact us. Ok + + CallForwardSettingsLayout + + + settings_call_forward_activate_title + "Forward calls" + Forward calls + + + + settings_call_forward_activate_subtitle + "Forward calls" + Forward calls to voicemail or a Number / SIP Address / number + + + + settings_call_forward_destination_choose + "Forward calls to" + Forward calls to: + + + + settings_call_forward_to_voicemail + "Voicemail" + Voicemail + + + + settings_call_forward_to_sipaddress + "Number / SIP address" + Number / SIP Address + + + + settings_call_forward_sipaddress_title + "Number / Sip address:" + Number / SIP Address: + + + + settings_call_forward_sipaddress_placeholder + "John.doe" + John.doe + + + + settings_call_forward_address_cannot_be_empty + "A number or SIP address is mandatory" + A number or SIP address is mandatory + + + + settings_call_forward_address_timeout + "Unable to set call forward, request timeout" + Unable to set call forward, request timeout + + + + settings_call_forward_address_progress_disabling + "Disabling call forward" + Disabling call forward + + + + settings_call_forward_address_progress_enabling + "Enabling call forward to:" + Enabling call forward to: + + + + settings_call_forward_activation_success + "Call forward activated to : " + Call forward activated to : + + + + settings_call_forward_deactivation_success + "Call forward deactivated" + Call forward deactivated + + diff --git a/Linphone/model/setting/SettingsModel.cpp b/Linphone/model/setting/SettingsModel.cpp index 0fae2411..c7e65c27 100644 --- a/Linphone/model/setting/SettingsModel.cpp +++ b/Linphone/model/setting/SettingsModel.cpp @@ -706,6 +706,29 @@ QString SettingsModel::getDefaultDomain() const { mConfig->getString(SettingsModel::AppSection, "default_domain", "sip.linphone.org")); } +void SettingsModel::enableCallForward(QString destination) { + // TODO implement business logic to activate call forward to destination on PBX via external API (contains voicemail + // or a destination). + mConfig->setString(UiSection, "call_forward_to_address", Utils::appStringToCoreString(destination)); + emit callForwardToAddressChanged(getCallForwardToAddress()); +} + +void SettingsModel::disableCallForward() { + // TODO implement business logic to de-activate call forward on PBX via external API + mConfig->setString(UiSection, "call_forward_to_address", ""); +} + +QString SettingsModel::getCallForwardToAddress() const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return Utils::coreStringToAppString(mConfig->getString(UiSection, "call_forward_to_address", "")); +} + +void SettingsModel::setCallForwardToAddress(QString data) { + if (data == "") disableCallForward(); + else enableCallForward(data); + emit(callForwardToAddressChanged(data)); +} + // clang-format off void SettingsModel::notifyConfigReady(){ DEFINE_NOTIFY_CONFIG_READY(disableChatFeature, DisableChatFeature) @@ -874,5 +897,11 @@ DEFINE_GETSET_CONFIG(SettingsModel, DisableCommandLine, "disable_command_line", false) - +DEFINE_GETSET_CONFIG(SettingsModel, + bool, + Bool, + disableCallForward, + DisableCallForward, + "disable_call_forward", + true) // clang-format on diff --git a/Linphone/model/setting/SettingsModel.hpp b/Linphone/model/setting/SettingsModel.hpp index dcab5025..5cbdcdef 100644 --- a/Linphone/model/setting/SettingsModel.hpp +++ b/Linphone/model/setting/SettingsModel.hpp @@ -186,6 +186,8 @@ public: DECLARE_GETSET(bool, usernameOnlyForCardDAVLookupsInCalls, UsernameOnlyForCardDAVLookupsInCalls) DECLARE_GETSET(QString, commandLine, CommandLine) DECLARE_GETSET(bool, disableCommandLine, DisableCommandLine) + DECLARE_GETSET(bool, disableCallForward, DisableCallForward) + DECLARE_GETSET(QString, callForwardToAddress, CallForwardToAddress) signals: void logsUploadUrlChanged(); @@ -235,6 +237,9 @@ private: MediastreamerUtils::SimpleCaptureGraph *mSimpleCaptureGraph = nullptr; int mCaptureGraphListenerCount = 0; + void enableCallForward(QString destination); + void disableCallForward(); + static std::shared_ptr gSettingsModel; DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 4c4f5c35..f0c859d3 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -122,6 +122,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Page/Layout/Settings/AccountSettingsGeneralLayout.qml view/Page/Layout/Settings/AccountSettingsParametersLayout.qml view/Page/Layout/Settings/CallSettingsLayout.qml + view/Page/Layout/Settings/CallForwardSettingsLayout.qml view/Page/Layout/Settings/ContactsSettingsLayout.qml view/Page/Layout/Settings/MeetingsSettingsLayout.qml view/Page/Layout/Settings/ContactsSettingsProviderLayout.qml diff --git a/Linphone/view/Control/Input/DecoratedTextField.qml b/Linphone/view/Control/Input/DecoratedTextField.qml index 1cb75b25..73b9759b 100644 --- a/Linphone/view/Control/Input/DecoratedTextField.qml +++ b/Linphone/view/Control/Input/DecoratedTextField.qml @@ -29,6 +29,10 @@ FormItemLayout { property var isValid: function(text) { return true } + + function empty() { + textField.text = "" + } contentItem: TextField { id: textField diff --git a/Linphone/view/Page/Form/Settings/AbstractSettingsMenu.qml b/Linphone/view/Page/Form/Settings/AbstractSettingsMenu.qml index 271ea531..be81d943 100644 --- a/Linphone/view/Page/Form/Settings/AbstractSettingsMenu.qml +++ b/Linphone/view/Page/Form/Settings/AbstractSettingsMenu.qml @@ -22,6 +22,7 @@ AbstractMainPage { } property var families + property var defaultIndex: -1 leftPanelContent: ColumnLayout { id: leftPanel @@ -62,7 +63,7 @@ AbstractMainPage { model: mainItem.families Layout.topMargin: Math.round(41 * DefaultStyle.dp) Layout.leftMargin: leftPanel.sideMargin - property int selectedIndex: 0 + property int selectedIndex: mainItem.defaultIndex != -1 ? mainItem.defaultIndex : 0 activeFocusOnTab: true delegate: SettingsMenuItem { diff --git a/Linphone/view/Page/Form/Settings/SettingsPage.qml b/Linphone/view/Page/Form/Settings/SettingsPage.qml index 0a9509ce..886d50ba 100644 --- a/Linphone/view/Page/Form/Settings/SettingsPage.qml +++ b/Linphone/view/Page/Form/Settings/SettingsPage.qml @@ -13,6 +13,8 @@ AbstractSettingsMenu { families: [ //: "Appels" {title: qsTr("settings_calls_title"), layout: "CallSettingsLayout"}, + //: "Transfert d'appel" + {title: qsTr("settings_call_forward"), layout: "CallForwardSettingsLayout"}, //: "Conversations" {title: qsTr("settings_conversations_title"), layout: "ChatSettingsLayout", visible: !SettingsCpp.disableChatFeature}, //: "Contacts" diff --git a/Linphone/view/Page/Layout/Main/MainLayout.qml b/Linphone/view/Page/Layout/Main/MainLayout.qml index 6742c459..ab5a3e86 100644 --- a/Linphone/view/Page/Layout/Main/MainLayout.qml +++ b/Linphone/view/Page/Layout/Main/MainLayout.qml @@ -608,6 +608,12 @@ Item { Component.onCompleted: { magicSearchBar.numericPadPopup = callPage.numericPadPopup } + onGoToCallForwardSettings: { + var page = settingsPageComponent.createObject(parent, { + defaultIndex: 1 + }); + openContextualMenuComponent(page) + } } ContactPage { id: contactPage diff --git a/Linphone/view/Page/Layout/Settings/CallForwardSettingsLayout.qml b/Linphone/view/Page/Layout/Settings/CallForwardSettingsLayout.qml new file mode 100644 index 00000000..03c812bc --- /dev/null +++ b/Linphone/view/Page/Layout/Settings/CallForwardSettingsLayout.qml @@ -0,0 +1,139 @@ + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls.Basic as Control +import Linphone +import SettingsCpp 1.0 +import UtilsCpp + +AbstractSettingsLayout { + id: mainItem + + property bool enableCallForward: SettingsCpp.callForwardToAddress.length > 0 + property string localCallForwardToAddress: SettingsCpp.callForwardToAddress + + width: parent?.width + + contentModel: [ + { + title: "", + subTitle: "", + contentComponent: parametersComponent + } + ] + + Connections { + target: SettingsCpp + function onCallForwardToAddressChanged() { + requestTimeOut.stop() + UtilsCpp.getMainWindow().closeLoadingPopup() + UtilsCpp.showInformationPopup("", + SettingsCpp.callForwardToAddress + ? qsTr("settings_call_forward_activation_success") + (SettingsCpp.callForwardToAddress == "voicemail" ? qsTr("settings_call_forward_to_voicemail") : SettingsCpp.callForwardToAddress) + : qsTr("settings_call_forward_deactivation_success") + , true) + } + } + + Timer { + id: requestTimeOut + interval: 10000 + running: false + repeat: false + onTriggered: { + UtilsCpp.getMainWindow().closeLoadingPopup() + UtilsCpp.showInformationPopup("", qsTr("settings_call_forward_address_timeout"), false) + } + } + + onSave: { + if (mainItem.enableCallForward && mainItem.localCallForwardToAddress.length == 0) { + UtilsCpp.getMainWindow().showInformationPopup("", qsTr("settings_call_forward_address_cannot_be_empty"), false) + return + } + requestTimeOut.start() + if (!mainItem.enableCallForward && SettingsCpp.callForwardToAddress.length > 0) { + UtilsCpp.getMainWindow().showLoadingPopup(qsTr("settings_call_forward_address_progress_disabling") + " ...") + SettingsCpp.callForwardToAddress = "" + } else if (SettingsCpp.callForwardToAddress != mainItem.localCallForwardToAddress) { + UtilsCpp.getMainWindow().showLoadingPopup(qsTr("settings_call_forward_address_progress_enabling")+(mainItem.localCallForwardToAddress === 'voicemail' ? qsTr("settings_call_forward_to_voicemail") : mainItem.localCallForwardToAddress) + " ...") + SettingsCpp.callForwardToAddress = mainItem.localCallForwardToAddress + } + } + + // Generic forward parameters + ///////////////////////////// + + Component { + id: parametersComponent + ColumnLayout { + spacing: Math.round(20 * DefaultStyle.dp) + SwitchSetting { + //: "Forward calls" + titleText: qsTr("settings_call_forward_activate_title") + //: "Enable call forwarding to voicemail or sip address" + subTitleText: qsTr("settings_call_forward_activate_subtitle") + propertyName: "enableCallForward" + propertyOwner: mainItem + } + Text { + visible: mainItem.enableCallForward + //: Forward to destination + text: qsTr("settings_call_forward_destination_choose") + font { + pixelSize: Typography.p2l.pixelSize + weight: Typography.p2l.weight + } + } + ComboBox { + id: forwardDestination + visible: mainItem.enableCallForward + Layout.fillWidth: true + Layout.preferredHeight: Math.round(49 * DefaultStyle.dp) + model: [ + {text: qsTr("settings_call_forward_to_voicemail")}, + {text: qsTr("settings_call_forward_to_sipaddress")} + ] + property bool isInitialized: false + Component.onCompleted: { + if (mainItem.enableCallForward) { + forwardDestination.currentIndex = + (mainItem.localCallForwardToAddress === "voicemail" || mainItem.localCallForwardToAddress.length === 0) ? 0 : 1; + } else { + forwardDestination.currentIndex = 0; + } + forwardDestination.isInitialized = true; + } + onCurrentIndexChanged: { + if (!forwardDestination.isInitialized) + return; + if (currentIndex == 0) + mainItem.localCallForwardToAddress = "voicemail"; + else { + mainItem.localCallForwardToAddress = ""; + sipInputField.empty(); + } + } + onVisibleChanged: { + if (visible) { + currentIndex = 0 + mainItem.localCallForwardToAddress = "voicemail"; + } + } + + } + DecoratedTextField { + id: sipInputField + visible: mainItem.enableCallForward && forwardDestination.currentIndex == 1 + Layout.fillWidth: true + propertyName: "localCallForwardToAddress" + propertyOwner: mainItem + //: SIP Address + title: qsTr("settings_call_forward_sipaddress_title") + placeHolder: qsTr("settings_call_forward_sipaddress_placeholder") + useTitleAsPlaceHolder: false + toValidate: true + } + } + } +} diff --git a/Linphone/view/Page/Main/Call/CallPage.qml b/Linphone/view/Page/Main/Call/CallPage.qml index 4e056729..0abde8bb 100644 --- a/Linphone/view/Page/Main/Call/CallPage.qml +++ b/Linphone/view/Page/Main/Call/CallPage.qml @@ -18,6 +18,7 @@ AbstractMainPage { property var selectedRowHistoryGui signal listViewUpdated + signal goToCallForwardSettings onVisibleChanged: if (!visible) { goToCallHistory() @@ -217,6 +218,42 @@ AbstractMainPage { value: false } } + Rectangle { + visible: SettingsCpp.callForwardToAddress.length > 0 + Layout.fillWidth: true + Layout.preferredHeight: Math.round(40 * DefaultStyle.dp) + Layout.topMargin: Math.round(18 * DefaultStyle.dp) + Layout.rightMargin: Math.round(39 * DefaultStyle.dp) + color: "transparent" + radius: 25 * DefaultStyle.dp + border.color: DefaultStyle.warning_500_main + border.width: 2 * DefaultStyle.dp + + RowLayout { + anchors.centerIn: parent + spacing: 10 * DefaultStyle.dp + EffectImage { + fillMode: Image.PreserveAspectFit + imageSource: AppIcons.callForward + colorizationColor: DefaultStyle.warning_500_main + Layout.preferredHeight: Math.round(24 * DefaultStyle.dp) + Layout.preferredWidth: Math.round(24 * DefaultStyle.dp) + } + Text { + text: qsTr("call_forward_to_address_info") + (SettingsCpp.callForwardToAddress == 'voicemail' ? qsTr("call_forward_to_address_info_voicemail") : SettingsCpp.callForwardToAddress) + color: DefaultStyle.warning_500_main + font: Typography.p1 + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + goToCallForwardSettings() + } + } + } Item { Layout.fillWidth: true Layout.fillHeight: true @@ -276,6 +313,7 @@ AbstractMainPage { policy: Control.ScrollBar.AsNeeded } } + } } } diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index 1027b447..796dabc4 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -126,4 +126,5 @@ QtObject { property string resourcePackage: "image://internal/package.svg" property string appWindow: "image://internal/app-window.svg" property string bellMwi: "image://internal/bell-simple.svg" + property string callForward: "image://internal/call-forward.svg" } diff --git a/Linphone/view/Style/DefaultStyle.qml b/Linphone/view/Style/DefaultStyle.qml index d21c269b..0acbc4b1 100644 --- a/Linphone/view/Style/DefaultStyle.qml +++ b/Linphone/view/Style/DefaultStyle.qml @@ -61,5 +61,6 @@ QtObject { property color groupCallButtonColor: "#EEF7F8" property color placeholders: '#CACACA' // No name in design - + + property color warning_500_main: "#FFDC2E" }