diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4b1c9757..f2166cc0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added
- Features:
- * messages features : Reply, forward (to contact, to a SIP address or to a timeline)
+ * messages features : Reply, forward (to contact, to a SIP address or to a timeline), Vocal record (on send)
## 4.3.2
diff --git a/linphone-app/CMakeLists.txt b/linphone-app/CMakeLists.txt
index de7f917f6..34e94ddb3 100644
--- a/linphone-app/CMakeLists.txt
+++ b/linphone-app/CMakeLists.txt
@@ -181,6 +181,8 @@ set(SOURCES
src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp
src/components/presence/OwnPresenceModel.cpp
src/components/presence/Presence.cpp
+ src/components/recorder/RecorderManager.cpp
+ src/components/recorder/RecorderModel.cpp
src/components/search/SearchHandler.cpp
src/components/search/SearchResultModel.cpp
src/components/search/SearchSipAddressesModel.cpp
@@ -288,6 +290,8 @@ set(HEADERS
src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp
src/components/presence/OwnPresenceModel.hpp
src/components/presence/Presence.hpp
+ src/components/recorder/RecorderManager.hpp
+ src/components/recorder/RecorderModel.hpp
src/components/search/SearchHandler.hpp
src/components/search/SearchResultModel.hpp
src/components/search/SearchSipAddressesModel.hpp
diff --git a/linphone-app/assets/images/chat_audio_pause_custom.svg b/linphone-app/assets/images/chat_audio_pause_custom.svg
new file mode 100644
index 000000000..e6b1d6996
--- /dev/null
+++ b/linphone-app/assets/images/chat_audio_pause_custom.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/linphone-app/assets/images/chat_audio_play_custom.svg b/linphone-app/assets/images/chat_audio_play_custom.svg
new file mode 100644
index 000000000..34dfe5c4c
--- /dev/null
+++ b/linphone-app/assets/images/chat_audio_play_custom.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/linphone-app/assets/images/chat_audio_soundwave_custom.svg b/linphone-app/assets/images/chat_audio_soundwave_custom.svg
new file mode 100644
index 000000000..773208494
--- /dev/null
+++ b/linphone-app/assets/images/chat_audio_soundwave_custom.svg
@@ -0,0 +1,48 @@
+
+
diff --git a/linphone-app/assets/images/chat_audio_stop_custom.svg b/linphone-app/assets/images/chat_audio_stop_custom.svg
new file mode 100644
index 000000000..b6a7a56ee
--- /dev/null
+++ b/linphone-app/assets/images/chat_audio_stop_custom.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/linphone-app/resources.qrc b/linphone-app/resources.qrc
index 3b9736223..3fa40297e 100644
--- a/linphone-app/resources.qrc
+++ b/linphone-app/resources.qrc
@@ -30,6 +30,10 @@
assets/images/cancel_custom.svg
assets/images/chat_custom.svg
assets/images/chat_amount.svg
+ assets/images/chat_audio_pause_custom.svg
+ assets/images/chat_audio_play_custom.svg
+ assets/images/chat_audio_soundwave_custom.svg
+ assets/images/chat_audio_stop_custom.svg
assets/images/chat_count.svg
assets/images/chat_delivered.svg
assets/images/chat_error.svg
@@ -193,6 +197,7 @@
ui/modules/Common/Helpers/InvertedMouseArea.qml
ui/modules/Common/Image/Icon.qml
ui/modules/Common/Image/RoundedImage.qml
+ ui/modules/Common/Indicators/MediaProgressBar.qml
ui/modules/Common/Indicators/VuMeter.qml
ui/modules/Common/Menus/ApplicationMenuEntry.qml
ui/modules/Common/Menus/ApplicationMenu.qml
@@ -238,6 +243,7 @@
ui/modules/Common/Styles/Form/Tab/TabButtonStyle.qml
ui/modules/Common/Styles/Form/Tab/TabContainerStyle.qml
ui/modules/Common/Styles/Form/TransparentTextInputStyle.qml
+ ui/modules/Common/Styles/Indicators/MediaProgressBarStyle.qml
ui/modules/Common/Styles/Indicators/VuMeterStyle.qml
ui/modules/Common/Styles/Menus/ApplicationMenuStyle.qml
ui/modules/Common/Styles/Menus/DropDownStaticMenuStyle.qml
@@ -270,6 +276,7 @@
ui/modules/Linphone/Chat/Chat.qml
ui/modules/Linphone/Chat/ChatDeliveries.qml
ui/modules/Linphone/Chat/ChatMenu.qml
+ ui/modules/Linphone/Chat/ChatAudioPreview.qml
ui/modules/Linphone/Chat/ChatMessagePreview.qml
ui/modules/Linphone/Chat/ChatForwardMessage.qml
ui/modules/Linphone/Chat/ChatReplyMessage.qml
@@ -314,6 +321,7 @@
ui/modules/Linphone/Styles/Calls/CallStatisticsStyle.qml
ui/modules/Linphone/Styles/Calls/ConferenceControlsStyle.qml
ui/modules/Linphone/Styles/Chat/ChatStyle.qml
+ ui/modules/Linphone/Styles/Chat/ChatAudioPreviewStyle.qml
ui/modules/Linphone/Styles/Chat/ChatForwardMessageStyle.qml
ui/modules/Linphone/Styles/Chat/ChatReplyMessageStyle.qml
ui/modules/Linphone/Styles/Codecs/CodecsViewerStyle.qml
diff --git a/linphone-app/src/app/App.cpp b/linphone-app/src/app/App.cpp
index 76fd16ac8..eea329ac8 100644
--- a/linphone-app/src/app/App.cpp
+++ b/linphone-app/src/app/App.cpp
@@ -533,6 +533,14 @@ static inline void registerSharedSingletonType (const char *name) {
qmlRegisterSingletonType(Constants::MainQmlUri, 1, 0, name, makeSharedSingleton);
}
+template
+static QObject *makeSharedSingleton (QQmlEngine *, QJSEngine *) {
+ QObject *object = (CoreManager::getInstance()->*function)();
+ QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
+ return object;
+}
+
+
template
static QObject *makeSharedSingleton (QQmlEngine *, QJSEngine *) {
QObject *object = (CoreManager::getInstance()->*function)();
@@ -545,6 +553,11 @@ static inline void registerSharedSingletonType (const char *name) {
qmlRegisterSingletonType(Constants::MainQmlUri, 1, 0, name, makeSharedSingleton);
}
+template
+static inline void registerSharedSingletonType (const char *name) {
+ qmlRegisterSingletonType(Constants::MainQmlUri, 1, 0, name, makeSharedSingleton);
+}
+
template
static inline void registerUncreatableType (const char *name) {
qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, name, QLatin1String("Uncreatable"));
@@ -647,6 +660,7 @@ void App::registerTypes () {
registerUncreatableType("ContentModel");
registerUncreatableType("HistoryModel");
registerUncreatableType("LdapModel");
+ registerUncreatableType("RecorderModel");
registerUncreatableType("SearchResultModel");
registerUncreatableType("SipAddressObserver");
registerUncreatableType("VcardModel");
@@ -680,6 +694,7 @@ void App::registerSharedTypes () {
registerSharedSingletonType("ContactsImporterListModel");
registerSharedSingletonType("LdapListModel");
registerSharedSingletonType("TimelineListModel");
+ registerSharedSingletonType("RecorderManager");
//qmlRegisterSingletonType(Constants::MainQmlUri, 1, 0, "ColorList", mColorListModel);
diff --git a/linphone-app/src/components/Components.hpp b/linphone-app/src/components/Components.hpp
index 617412500..009de1b9c 100644
--- a/linphone-app/src/components/Components.hpp
+++ b/linphone-app/src/components/Components.hpp
@@ -63,6 +63,8 @@
#include "participant-imdn/ParticipantImdnStateListModel.hpp"
#include "participant-imdn/ParticipantImdnStateProxyModel.hpp"
#include "presence/OwnPresenceModel.hpp"
+#include "recorder/RecorderModel.hpp"
+#include "recorder/RecorderManager.hpp"
#include "settings/AccountSettingsModel.hpp"
#include "settings/SettingsModel.hpp"
#include "search/SearchResultModel.hpp"
diff --git a/linphone-app/src/components/chat-room/ChatRoomModel.cpp b/linphone-app/src/components/chat-room/ChatRoomModel.cpp
index 47f922b4f..eb79045ae 100644
--- a/linphone-app/src/components/chat-room/ChatRoomModel.cpp
+++ b/linphone-app/src/components/chat-room/ChatRoomModel.cpp
@@ -52,6 +52,8 @@
#include "components/participant/ParticipantModel.hpp"
#include "components/participant/ParticipantListModel.hpp"
#include "components/presence/Presence.hpp"
+#include "components/recorder/RecorderManager.hpp"
+#include "components/recorder/RecorderModel.hpp"
#include "components/timeline/TimelineModel.hpp"
#include "components/timeline/TimelineListModel.hpp"
#include "components/core/event-count-notifier/AbstractEventCountNotifier.hpp"
@@ -600,6 +602,7 @@ void ChatRoomModel::setReply(ChatMessageModel * model){
void ChatRoomModel::clearReply(){
mReply = nullptr;
}
+
//------------------------------------------------------------------------------------------------
void ChatRoomModel::deleteChatRoom(){
@@ -639,13 +642,18 @@ void ChatRoomModel::updateParticipants(const QVariantList& participants){
void ChatRoomModel::sendMessage (const QString &message) {
shared_ptr _message;
- if(mReply){
+ if(mReply)
_message = mChatRoom->createReplyMessage(mReply);
- _message->addUtf8TextContent(message.toUtf8().toStdString());
- }else{
- _message= mChatRoom->createMessageFromUtf8(message.toUtf8().toStdString());
+ else
+ _message= mChatRoom->createEmptyMessage();
+ auto recorder = CoreManager::getInstance()->getRecorderManager();
+ if(recorder->haveVocalRecorder()) {
+ auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
+ if(content)
+ _message->addContent(content);
}
-
+ if(!message.isEmpty())
+ _message->addUtf8TextContent(message.toUtf8().toStdString());
_message->send();
emit messageSent(_message);
}
@@ -677,6 +685,13 @@ void ChatRoomModel::sendFileMessage (const QString &path) {
shared_ptr message = mChatRoom->createFileTransferMessage(content);
message->getContents().front()->setFilePath(Utils::appStringToCoreString(path));
+
+ auto recorder = CoreManager::getInstance()->getRecorderManager();
+ if(recorder->haveVocalRecorder()) {
+ auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
+ if(content)
+ message->addContent(content);
+ }
message->send();
emit messageSent(message);
@@ -686,6 +701,12 @@ void ChatRoomModel::forwardMessage(ChatMessageModel * model){
if(model){
shared_ptr _message;
_message = mChatRoom->createForwardMessage(model->getChatMessage());
+ auto recorder = CoreManager::getInstance()->getRecorderManager();
+ if(recorder->haveVocalRecorder()) {
+ auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
+ if(content)
+ _message->addContent(content);
+ }
_message->send();
emit messageSent(_message);
}
diff --git a/linphone-app/src/components/core/CoreManager.cpp b/linphone-app/src/components/core/CoreManager.cpp
index 84219f480..31836e278 100644
--- a/linphone-app/src/components/core/CoreManager.cpp
+++ b/linphone-app/src/components/core/CoreManager.cpp
@@ -36,6 +36,7 @@
#include "components/contacts/ContactsImporterListModel.hpp"
#include "components/history/HistoryModel.hpp"
#include "components/ldap/LdapListModel.hpp"
+#include "components/recorder/RecorderManager.hpp"
#include "components/settings/AccountSettingsModel.hpp"
#include "components/settings/SettingsModel.hpp"
#include "components/sip-addresses/SipAddressesModel.hpp"
@@ -123,6 +124,14 @@ HistoryModel* CoreManager::getHistoryModel(){
}
return mHistoryModel;
}
+
+RecorderManager* CoreManager::getRecorderManager(){
+ if(!mRecorderManager){
+ mRecorderManager = new RecorderManager(this);
+ emit recorderManagerCreated(mRecorderManager);
+ }
+ return mRecorderManager;
+}
// -----------------------------------------------------------------------------
void CoreManager::init (QObject *parent, const QString &configPath) {
@@ -183,6 +192,7 @@ void CoreManager::cleanLogs () const {
mCore->resetLogCollection();
}
+// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
diff --git a/linphone-app/src/components/core/CoreManager.hpp b/linphone-app/src/components/core/CoreManager.hpp
index df73546ae..264ac3c94 100644
--- a/linphone-app/src/components/core/CoreManager.hpp
+++ b/linphone-app/src/components/core/CoreManager.hpp
@@ -42,6 +42,7 @@ class CoreHandlers;
class EventCountNotifier;
class HistoryModel;
class LdapListModel;
+class RecorderManager;
class SettingsModel;
class SipAddressesModel;
class VcardModel;
@@ -76,6 +77,7 @@ public:
//bool chatRoomModelExists (std::shared_ptr chatRoom);
HistoryModel* getHistoryModel();
+ RecorderManager* getRecorderManager();
// ---------------------------------------------------------------------------
// Video render lock.
@@ -179,6 +181,7 @@ signals:
void chatRoomModelCreated (const std::shared_ptr &chatRoomModel);
void historyModelCreated (HistoryModel *historyModel);
+ void recorderManagerCreated(RecorderManager *recorderModel);
void logsUploaded (const QString &url);
@@ -227,6 +230,7 @@ private:
//QList, std::weak_ptr>> mChatRoomModels;
HistoryModel * mHistoryModel = nullptr;
LdapListModel *mLdapListModel = nullptr;
+ RecorderManager* mRecorderManager = nullptr;
QTimer *mCbsTimer = nullptr;
diff --git a/linphone-app/src/components/recorder/RecorderManager.cpp b/linphone-app/src/components/recorder/RecorderManager.cpp
new file mode 100644
index 000000000..dc0bf9670
--- /dev/null
+++ b/linphone-app/src/components/recorder/RecorderManager.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2021 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
+#include
+
+#include "app/App.hpp"
+#include "components/core/CoreManager.hpp"
+
+#include "RecorderManager.hpp"
+#include "RecorderModel.hpp"
+
+// =============================================================================
+
+RecorderManager::RecorderManager (QObject * parent) : QObject(parent) {
+ App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
+}
+
+RecorderManager::~RecorderManager(){
+}
+
+
+bool RecorderManager::haveVocalRecorder() const{
+ return mVocalRecorder != nullptr;
+}
+
+RecorderModel* RecorderManager::getVocalRecorder(){
+ if( !mVocalRecorder) {
+ auto core = CoreManager::getInstance()->getCore();
+ std::shared_ptr params = core->createRecorderParams();
+ params->setFileFormat(linphone::RecorderFileFormat::Mkv);
+ params->setVideoCodec("");
+ auto recorder = core->createRecorder(params);
+ if(recorder)
+ mVocalRecorder = RecorderModel::create(recorder, this);
+ emit haveVocalRecorderChanged();
+ }
+ return mVocalRecorder.get();
+}
+
+RecorderModel* RecorderManager::resetVocalRecorder(){
+ if(mVocalRecorder)
+ clearVocalRecorder();
+ return getVocalRecorder();
+}
+
+void RecorderManager::clearVocalRecorder(){
+ if( mVocalRecorder){
+ mVocalRecorder = nullptr;
+ emit haveVocalRecorderChanged();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
diff --git a/linphone-app/src/components/recorder/RecorderManager.hpp b/linphone-app/src/components/recorder/RecorderManager.hpp
new file mode 100644
index 000000000..45e5adb19
--- /dev/null
+++ b/linphone-app/src/components/recorder/RecorderManager.hpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2021 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 RECORDER_MANAGER_MODEL_H
+#define RECORDER_MANAGER_MODEL_H
+
+
+#include
+#include
+
+// =============================================================================
+class RecorderModel;
+
+class RecorderManager : public QObject {
+ Q_OBJECT
+public:
+ RecorderManager (QObject * parent = nullptr);
+ virtual ~RecorderManager();
+
+ Q_PROPERTY(bool haveVocalRecorder READ haveVocalRecorder NOTIFY haveVocalRecorderChanged)
+
+ bool haveVocalRecorder() const;
+ Q_INVOKABLE RecorderModel* getVocalRecorder();
+ Q_INVOKABLE RecorderModel* resetVocalRecorder();
+ Q_INVOKABLE void clearVocalRecorder();
+
+signals:
+ void haveVocalRecorderChanged();
+
+private:
+ std::shared_ptr mVocalRecorder;
+};
+#endif
diff --git a/linphone-app/src/components/recorder/RecorderModel.cpp b/linphone-app/src/components/recorder/RecorderModel.cpp
new file mode 100644
index 000000000..30194e6f2
--- /dev/null
+++ b/linphone-app/src/components/recorder/RecorderModel.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2021 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
+#include
+
+#include "app/App.hpp"
+#include "app/paths/Paths.hpp"
+#include "components/core/CoreManager.hpp"
+#include "components/settings/SettingsModel.hpp"
+#include "utils/Utils.hpp"
+
+#include "RecorderModel.hpp"
+
+// =============================================================================
+
+RecorderModel::RecorderModel ( std::shared_ptr recorder, QObject * parent) : QObject(parent) {
+ App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
+ mRecorder= recorder;
+}
+
+RecorderModel::~RecorderModel(){
+}
+
+std::shared_ptr RecorderModel::create(std::shared_ptr recorder, QObject * parent){
+ return std::make_shared(recorder, parent);
+}
+
+
+std::shared_ptr RecorderModel::getRecorder(){
+ return mRecorder;
+}
+
+int RecorderModel::getDuration()const{
+ return mRecorder->getDuration();
+}
+
+LinphoneEnums::RecorderState RecorderModel::getState() const{
+ return LinphoneEnums::fromLinphone(mRecorder->getState());
+}
+
+QString RecorderModel::getFile()const{
+ return Utils::coreStringToAppString(mRecorder->getFile());
+}
+
+void RecorderModel::start(){
+ bool soFarSoGood;
+ QString filename = QStringLiteral("vocal_%1.mkv")
+ .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss-zzz"));
+ const QString safeFilePath = Utils::getSafeFilePath(
+ QStringLiteral("%1%2")
+ .arg(Utils::coreStringToAppString(Paths::getCapturesDirPath()))
+ .arg(filename),
+ &soFarSoGood
+ );
+
+ if (!soFarSoGood) {
+ qWarning() << QStringLiteral("Unable to create safe file path for: %1.").arg(filename);
+ }else if(mRecorder->open(Utils::appStringToCoreString(safeFilePath)) < 0)
+ qWarning() << QStringLiteral("Unable to open safe file path for: %1.").arg(filename);
+ else if( mRecorder->start() < 0)
+ qWarning() << QStringLiteral("Unable to start recording to : %1.").arg(filename);
+ emit stateChanged();
+ emit fileChanged();
+}
+
+void RecorderModel::pause(){
+ qWarning() << mRecorder->pause();
+ emit stateChanged();
+}
+
+void RecorderModel::stop(){
+ if(mRecorder->pause() == 0)
+ mRecorder->close();
+ emit stateChanged();
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
diff --git a/linphone-app/src/components/recorder/RecorderModel.hpp b/linphone-app/src/components/recorder/RecorderModel.hpp
new file mode 100644
index 000000000..6ea1269fc
--- /dev/null
+++ b/linphone-app/src/components/recorder/RecorderModel.hpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021 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 RECORDER_MODEL_H
+#define RECORDER_MODEL_H
+
+#include "utils/LinphoneEnums.hpp"
+
+// =============================================================================
+
+
+class RecorderModel : public QObject {
+ Q_OBJECT
+
+public:
+ static std::shared_ptr create(std::shared_ptr recorder,QObject * parent = nullptr);// Call it instead constructor
+ RecorderModel (std::shared_ptr recorder,QObject * parent = nullptr);
+ virtual ~RecorderModel();
+
+ Q_PROPERTY(LinphoneEnums::RecorderState state READ getState NOTIFY stateChanged)
+ Q_PROPERTY(QString file READ getFile NOTIFY fileChanged)
+
+ std::shared_ptr getRecorder();
+
+ Q_INVOKABLE int getDuration()const;
+ LinphoneEnums::RecorderState getState() const;
+ Q_INVOKABLE QString getFile()const;
+
+ Q_INVOKABLE void start();
+ Q_INVOKABLE void pause();
+ Q_INVOKABLE void stop();
+
+signals:
+ void stateChanged();
+ void fileChanged();
+
+private:
+ std::shared_ptr mRecorder;
+};
+Q_DECLARE_METATYPE(std::shared_ptr)
+Q_DECLARE_METATYPE(RecorderModel*)
+#endif
diff --git a/linphone-app/src/components/sound-player/SoundPlayer.hpp b/linphone-app/src/components/sound-player/SoundPlayer.hpp
index b7d4aa265..0dc18309c 100644
--- a/linphone-app/src/components/sound-player/SoundPlayer.hpp
+++ b/linphone-app/src/components/sound-player/SoundPlayer.hpp
@@ -37,11 +37,11 @@ namespace linphone {
class SoundPlayer : public QObject {
class Handlers;
- Q_OBJECT;
+ Q_OBJECT
- Q_PROPERTY(QString source READ getSource WRITE setSource NOTIFY sourceChanged);
- Q_PROPERTY(PlaybackState playbackState READ getPlaybackState WRITE setPlaybackState NOTIFY playbackStateChanged);
- Q_PROPERTY(int duration READ getDuration NOTIFY sourceChanged);
+ Q_PROPERTY(QString source READ getSource WRITE setSource NOTIFY sourceChanged)
+ Q_PROPERTY(PlaybackState playbackState READ getPlaybackState WRITE setPlaybackState NOTIFY playbackStateChanged)
+ Q_PROPERTY(int duration READ getDuration NOTIFY sourceChanged)
public:
enum PlaybackState {
diff --git a/linphone-app/src/utils/LinphoneEnums.cpp b/linphone-app/src/utils/LinphoneEnums.cpp
index b5ee6f0b5..ae8d367b3 100644
--- a/linphone-app/src/utils/LinphoneEnums.cpp
+++ b/linphone-app/src/utils/LinphoneEnums.cpp
@@ -29,9 +29,11 @@ void LinphoneEnums::registerMetaTypes(){
qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
}
-
linphone::MediaEncryption LinphoneEnums::toLinphone(const LinphoneEnums::MediaEncryption& data){
return static_cast(data);
}
@@ -73,3 +75,10 @@ linphone::Tunnel::Mode LinphoneEnums::toLinphone(const LinphoneEnums::TunnelMode
LinphoneEnums::TunnelMode LinphoneEnums::fromLinphone(const linphone::Tunnel::Mode& data){
return static_cast(data);
}
+
+linphone::RecorderState LinphoneEnums::toLinphone(const LinphoneEnums::RecorderState& data){
+ return static_cast(data);
+}
+LinphoneEnums::RecorderState LinphoneEnums::fromLinphone(const linphone::RecorderState& data){
+ return static_cast(data);
+}
\ No newline at end of file
diff --git a/linphone-app/src/utils/LinphoneEnums.hpp b/linphone-app/src/utils/LinphoneEnums.hpp
index 95ee581c3..8da35c46d 100644
--- a/linphone-app/src/utils/LinphoneEnums.hpp
+++ b/linphone-app/src/utils/LinphoneEnums.hpp
@@ -121,6 +121,16 @@ Q_ENUM_NS(TunnelMode)
linphone::Tunnel::Mode toLinphone(const LinphoneEnums::TunnelMode& mode);
LinphoneEnums::TunnelMode fromLinphone(const linphone::Tunnel::Mode& mode);
+
+enum RecorderState{
+ RecorderStateClosed = int(linphone::RecorderState::Closed),
+ RecorderStatePaused = int(linphone::RecorderState::Paused),
+ RecorderStateRunning = int(linphone::RecorderState::Running)
+};
+Q_ENUM_NS(RecorderState)
+
+linphone::RecorderState toLinphone(const LinphoneEnums::RecorderState& state);
+LinphoneEnums::RecorderState fromLinphone(const linphone::RecorderState& state);
}
Q_DECLARE_METATYPE(LinphoneEnums::MediaEncryption)
@@ -129,5 +139,6 @@ Q_DECLARE_METATYPE(LinphoneEnums::EventLogType)
Q_DECLARE_METATYPE(LinphoneEnums::ChatMessageState)
Q_DECLARE_METATYPE(LinphoneEnums::CallStatus)
Q_DECLARE_METATYPE(LinphoneEnums::TunnelMode)
+Q_DECLARE_METATYPE(LinphoneEnums::RecorderState)
#endif
diff --git a/linphone-app/ui/modules/Common/Form/ActionButton.qml b/linphone-app/ui/modules/Common/Form/ActionButton.qml
index 8a1b0bef3..f2c09444e 100644
--- a/linphone-app/ui/modules/Common/Form/ActionButton.qml
+++ b/linphone-app/ui/modules/Common/Form/ActionButton.qml
@@ -1,5 +1,6 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.12
import Common 1.0
@@ -33,6 +34,8 @@ Item {
property bool useStates: true
//property bool autoIcon : false // hovered/pressed : use an automatic layer instead of specific icon image
property int iconSize : colorSet.iconSize
+ property int iconHeight: colorSet.iconHeight ? colorSet.iconHeight : 0
+ property int iconWidth: colorSet.iconWidth ? colorSet.iconWidth : 0
readonly property alias hovered: button.hovered
property alias text: button.text
// Tooltip aliases
@@ -44,24 +47,34 @@ Item {
property alias backgroundRadius : backgroundColor.radius
+ property alias horizontalAlignment: icon.horizontalAlignment
+ property alias verticalAlignment: icon.verticalAlignment
+ property alias fillMode: icon.fillMode
-// AutoColor for hide part alpha /4
- property color foregroundHiddenPartNormalColor : colorSet.foregroundNormalColor ? Qt.rgba(colorSet.foregroundNormalColor.r, colorSet.foregroundNormalColor.g, colorSet.foregroundNormalColor.b, colorSet.foregroundNormalColor.a/4) : 'transparent'
- property color foregroundHiddenPartDisabledColor : colorSet.foregroundDisabledColor ? Qt.rgba(colorSet.foregroundDisabledColor.r, colorSet.foregroundDisabledColor.g, colorSet.foregroundDisabledColor.b, colorSet.foregroundDisabledColor.a/4): 'transparent'
- property color foregroundHiddenPartHoveredColor : colorSet.foregroundHoveredColor ? Qt.rgba(colorSet.foregroundHoveredColor.r, colorSet.foregroundHoveredColor.g, colorSet.foregroundHoveredColor.b, colorSet.foregroundHoveredColor.a/4): 'transparent'
- property color foregroundHiddenPartUpdatingColor : colorSet.foregroundUpdatingColor ? Qt.rgba(colorSet.foregroundUpdatingColor.r, colorSet.foregroundUpdatingColor.g, colorSet.foregroundUpdatingColor.b, colorSet.foregroundUpdatingColor.a/4): 'transparent'
- property color foregroundHiddenPartPressedColor : colorSet.foregroundPressedColor ? Qt.rgba(colorSet.foregroundPressedColor.r, colorSet.foregroundPressedColor.g, colorSet.foregroundPressedColor.b, colorSet.foregroundPressedColor.a/4): 'transparent'
+// Hidden part : transparent if not specified
+ property color backgroundHiddenPartNormalColor : colorSet.backgroundHiddenPartNormalColor ? colorSet.backgroundHiddenPartNormalColor : (colorSet.backgroundNormalColor ? colorSet.backgroundNormalColor : 'transparent')
+ property color backgroundHiddenPartDisabledColor : colorSet.backgroundHiddenPartDisabledColor ? colorSet.backgroundHiddenPartDisabledColor : (colorSet.backgroundDisabledColor ? colorSet.backgroundDisabledColor : 'transparent')
+ property color backgroundHiddenPartHoveredColor : colorSet.backgroundHiddenPartHoveredColor ? colorSet.backgroundHiddenPartHoveredColor : (colorSet.backgroundHoveredColor ? colorSet.backgroundHoveredColor : 'transparent')
+ property color backgroundHiddenPartUpdatingColor : colorSet.backgroundHiddenPartUpdatingColor ? colorSet.backgroundHiddenPartUpdatingColor : (colorSet.backgroundUpdatingColor ? colorSet.backgroundUpdatingColor : 'transparent')
+ property color backgroundHiddenPartPressedColor : colorSet.backgroundHiddenPartPressedColor ? colorSet.backgroundHiddenPartPressedColor : (colorSet.backgroundPressedColor ? colorSet.backgroundPressedColor : 'transparent')
+
+// AutoColor : alpha /4 for foreground
+ property color foregroundHiddenPartNormalColor : colorSet.foregroundHiddenPartNormalColor ? colorSet.foregroundHiddenPartNormalColor : (colorSet.foregroundNormalColor ? Qt.rgba(colorSet.foregroundNormalColor.r, colorSet.foregroundNormalColor.g, colorSet.foregroundNormalColor.b, colorSet.foregroundNormalColor.a/4) : 'transparent')
+ property color foregroundHiddenPartDisabledColor : colorSet.foregroundHiddenPartDisabledColor ? colorSet.foregroundHiddenPartDisabledColor : (colorSet.foregroundDisabledColor ? Qt.rgba(colorSet.foregroundDisabledColor.r, colorSet.foregroundDisabledColor.g, colorSet.foregroundDisabledColor.b, colorSet.foregroundDisabledColor.a/4): 'transparent')
+ property color foregroundHiddenPartHoveredColor : colorSet.foregroundHiddenPartHoveredColor ? colorSet.foregroundHiddenPartHoveredColor : (colorSet.foregroundHoveredColor ? Qt.rgba(colorSet.foregroundHoveredColor.r, colorSet.foregroundHoveredColor.g, colorSet.foregroundHoveredColor.b, colorSet.foregroundHoveredColor.a/4): 'transparent')
+ property color foregroundHiddenPartUpdatingColor : colorSet.foregroundHiddenPartUpdatingColor ? colorSet.foregroundHiddenPartUpdatingColor : (colorSet.foregroundUpdatingColor ? Qt.rgba(colorSet.foregroundUpdatingColor.r, colorSet.foregroundUpdatingColor.g, colorSet.foregroundUpdatingColor.b, colorSet.foregroundUpdatingColor.a/4): 'transparent')
+ property color foregroundHiddenPartPressedColor : colorSet.foregroundHiddenPartPressedColor ? colorSet.foregroundHiddenPartPressedColor : (colorSet.foregroundPressedColor ? Qt.rgba(colorSet.foregroundPressedColor.r, colorSet.foregroundPressedColor.g, colorSet.foregroundPressedColor.b, colorSet.foregroundPressedColor.a/4): 'transparent')
+//---------------------------------------------
property int percentageDisplayed : 100
-
// If `useStates` = true, the used icons are:
// `icon`_pressed, `icon`_hovered and `icon`_normal.
property string icon : colorSet.icon
// ---------------------------------------------------------------------------
- signal clicked
+ signal clicked(real x, real y)
// ---------------------------------------------------------------------------
@@ -120,6 +133,21 @@ Item {
}else
return "black"
}
+ function getBackgroundHiddenPartColor(){
+ if(isCustom){
+ if(wrappedButton.icon == '')
+ return wrappedButton.backgroundHiddenPartNormalColor
+ if (wrappedButton.updating)
+ return wrappedButton.backgroundHiddenPartUpdatingColor
+ if (!useStates)
+ return wrappedButton.backgroundHiddenPartNormalColor
+ if (!wrappedButton.enabled)
+ return wrappedButton.backgroundHiddenPartDisabledColor
+ return button.down ? wrappedButton.backgroundHiddenPartPressedColor
+ : (button.hovered ? wrappedButton.backgroundHiddenPartHoveredColor: wrappedButton.backgroundHiddenPartNormalColor)
+ }else
+ return 'transparent'
+ }
function getForegroundHiddenPartColor(){
if(isCustom){
if(wrappedButton.icon == '')
@@ -137,20 +165,31 @@ Item {
}
// ---------------------------------------------------------------------------
- height: iconSize || parent.iconSize || parent.height
- width: iconSize || parent.iconSize || parent.width
+ height: iconHeight || iconSize || parent.iconSize || parent.height
+ width: iconWidth || iconSize || parent.iconSize || parent.width
Button {
id: button
anchors.fill: parent
- background: Rectangle {
- id: backgroundColor
- color: getBackgroundColor()
- }
+ background: Row{
+ anchors.fill: parent
+ Rectangle {
+ height: parent.height
+ width:parent.width * wrappedButton.percentageDisplayed / 100
+ id: backgroundColor
+ color: getBackgroundColor()
+ }
+ Rectangle {
+ height: parent.height
+ width: parent.width * ( 1 - wrappedButton.percentageDisplayed / 100 )
+ id: backgroundHiddenPartColor
+ color: getBackgroundHiddenPartColor()
+ }
+ }
hoverEnabled: !wrappedButton.updating//|| wrappedButton.autoIcon
- onClicked: !wrappedButton.updating && wrappedButton.enabled && wrappedButton.clicked()
+ onClicked: !wrappedButton.updating && wrappedButton.enabled && wrappedButton.clicked(pressX, pressY)
Rectangle{
id: foregroundColor
anchors.fill:parent
@@ -179,6 +218,7 @@ Item {
id: icon
anchors.centerIn: parent
+ anchors.fill: iconHeight>0 || iconWidth ? parent : undefined
icon: {
if(!Images[_getIcon()])
console.log("No images for: "+_getIcon())
@@ -187,6 +227,8 @@ Item {
iconSize: wrappedButton.iconSize || (
parent.width > parent.height ? parent.height : parent.width
)
+ iconHeight: wrappedButton.iconHeight
+ iconWidth: wrappedButton.iconWidth
visible: !isCustom
}
diff --git a/linphone-app/ui/modules/Common/Form/DroppableTextArea.qml b/linphone-app/ui/modules/Common/Form/DroppableTextArea.qml
index fb0d13a81..5837a211f 100644
--- a/linphone-app/ui/modules/Common/Form/DroppableTextArea.qml
+++ b/linphone-app/ui/modules/Common/Form/DroppableTextArea.qml
@@ -4,6 +4,7 @@ import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.12
import Common 1.0
+import Linphone 1.0
import Common.Styles 1.0
import Utils 1.0
@@ -30,6 +31,7 @@ Item {
signal dropped (var files)
signal validText (string text)
+ signal audioRecordRequest()
// ---------------------------------------------------------------------------
@@ -86,7 +88,7 @@ Item {
}
// Record audio
ActionButton {
- visible:false && droppableTextArea.enabled// TODO
+ visible:droppableTextArea.enabled
id: recordAudioButton
//anchors.verticalCenter: parent.verticalCenter
@@ -97,9 +99,7 @@ Item {
backgroundRadius: 8
colorSet: DroppableTextAreaStyle.chatMicro
- useStates:false
-
- onClicked: {console.log('Record audio request')}
+ onClicked: droppableTextArea.audioRecordRequest()
}
@@ -137,7 +137,7 @@ Item {
}
}
function handleValidation () {
- if (text.length !== 0) {
+ if (RecorderManager.haveVocalRecorder || text.length !== 0) {
validText(text)
}
}
diff --git a/linphone-app/ui/modules/Common/Image/Icon.qml b/linphone-app/ui/modules/Common/Image/Icon.qml
index 4890625bd..71d947bfb 100644
--- a/linphone-app/ui/modules/Common/Image/Icon.qml
+++ b/linphone-app/ui/modules/Common/Image/Icon.qml
@@ -12,40 +12,40 @@ import Utils 1.0
Item {
id: mainItem
property var iconSize // Required.
+ property int iconHeight: 0 // Or this
+ property int iconWidth: 0 // <-- too
+
property string icon
property color overwriteColor
+ property alias horizontalAlignment: image.horizontalAlignment
+ property alias verticalAlignment: image.verticalAlignment
+ property alias fillMode: image.fillMode
+
+
// Use this slot because of testing overwriteColor in layer doesn't seem to work
onOverwriteColorChanged: if(overwriteColor)
image.colorOverwriteEnabled = true
else
image.colorOverwriteEnabled = false
- height: iconSize
- width: iconSize
+ height: iconHeight > 0 ? iconHeight : iconSize
+ width: iconWidth > 0 ? iconWidth : iconSize
Image {
id:image
property bool colorOverwriteEnabled : false
mipmap: SettingsModel.mipmapEnabled
- cache: Images.areReadOnlyImages
- function getIconSize () {
- Utils.assert(
- (icon == null || icon == '' || iconSize != null && iconSize >= 0),
- '`iconSize` must be defined and must be positive. (icon=`' +
- icon + '`, iconSize=' + iconSize + ')'
- )
-
- return iconSize
- }
+ cache: Images.areReadOnlyImages
- anchors.centerIn: parent
+ //anchors.centerIn: parent
+ anchors.fill: parent
- width: iconSize
- height: iconSize
+ //width: iconWidth > 0 ? iconWidth : mainItem.width
+ //height: iconHeight > 0 ? iconHeight : mainItem.height
fillMode: Image.PreserveAspectFit
source: Utils.resolveImageUri(icon)
- sourceSize.width: getIconSize()
- sourceSize.height: getIconSize()
+ sourceSize.width: iconWidth > 0 ? iconWidth : iconSize
+ sourceSize.height: iconHeight > 0 ? iconHeight : iconSize
layer {
enabled: image.colorOverwriteEnabled
effect: ColorOverlay {
diff --git a/linphone-app/ui/modules/Common/Indicators/MediaProgressBar.qml b/linphone-app/ui/modules/Common/Indicators/MediaProgressBar.qml
new file mode 100644
index 000000000..a7553aebe
--- /dev/null
+++ b/linphone-app/ui/modules/Common/Indicators/MediaProgressBar.qml
@@ -0,0 +1,107 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import QtGraphicalEffects 1.12
+
+import Common 1.0
+import Linphone 1.0
+import Utils 1.0
+import Units 1.0
+import Common.Styles 1.0
+
+// =============================================================================
+
+ProgressBar {
+ id: progressBar
+
+ property bool stopAtEnd: true
+ property bool resetAtEnd: false
+ property int progressDuration // Max duration
+ property int progressPosition // Position of pregress bar in [0 ; progressDuration]
+ property alias colorSet: progression.colorSet
+
+ function start(){
+ progressBar.value = 0
+ animationTest.start()
+ }
+ function resume(){
+ if(progressBar.value >= 100)
+ progressBar.value = 0
+ animationTest.start()
+ }
+ function stop(){
+ animationTest.stop()
+ }
+ signal endReached()
+ signal refreshPositionRequested()
+ signal seekRequested(int ms)
+ Timer{
+ id: animationTest
+ repeat: true
+ onTriggered: progressBar.refreshPositionRequested()
+ interval: 5
+ }
+ to: 101
+ value: 0
+ onValueChanged:{
+ if(value > 100){
+ if( progressBar.stopAtEnd)
+ stop()
+ if(progressBar.resetAtEnd) {
+ progressBar.value = 0
+ progressPosition = 0
+ }else{
+ progressBar.value = 100// Stay at 100
+ progressPosition = progressDuration
+ }
+
+ progressBar.endReached()
+ }else
+ progression.percentageDisplayed = value
+ }
+
+ anchors.topMargin: 5
+ anchors.bottomMargin: 5
+
+ background: Rectangle {
+ color: MediaProgressBarStyle.backgroundColor
+ radius: 5
+ }
+
+
+ contentItem:
+ Rectangle{
+ anchors.fill: parent
+ radius: 5
+ RowLayout{
+ anchors.fill: parent
+
+ ActionButton{
+ id: progression
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.topMargin: 5
+ Layout.bottomMargin: 5
+ Layout.leftMargin: 10
+ backgroundRadius: 5
+ fillMode: Image.TileHorizontally
+ verticalAlignment: Image.AlignLeft
+ isCustom: true
+ colorSet: MediaProgressBarStyle.progressionWave
+ percentageDisplayed: 0
+ onClicked: progressBar.seekRequested(x * progressBar.progressDuration/width)
+ }
+ Text{
+ Layout.fillHeight: true
+ Layout.preferredWidth: 100
+ Layout.rightMargin: 10
+ horizontalAlignment: Qt.AlignRight
+ verticalAlignment: Qt.AlignVCenter
+ text: progressBar.progressPosition >= 0 ? Utils.formatElapsedTime( progressBar.progressPosition / 1000 ) : '-'
+ property font customFont : SettingsModel.textMessageFont
+ font.family: customFont.family
+ font.pointSize: Units.dp * (customFont.pointSize + 2)
+ }
+ }
+ }
+}
diff --git a/linphone-app/ui/modules/Common/Styles/Indicators/MediaProgressBarStyle.qml b/linphone-app/ui/modules/Common/Styles/Indicators/MediaProgressBarStyle.qml
new file mode 100644
index 000000000..6ebbb6af1
--- /dev/null
+++ b/linphone-app/ui/modules/Common/Styles/Indicators/MediaProgressBarStyle.qml
@@ -0,0 +1,36 @@
+pragma Singleton
+import QtQml 2.2
+
+import ColorsList 1.0
+
+// =============================================================================
+
+QtObject {
+ property string sectionName: 'MediaProgressBar'
+
+ property color backgroundColor: ColorsList.add(sectionName+'_bg', 'k').color
+ property string gaugeIcon: 'chat_audio_soundwave_custom'
+
+ property QtObject progressionWave: QtObject{
+ property int iconSize: 30
+ property int iconHeight: 40
+ property int iconWidth: 250
+ property string name : 'progression_soundwave'
+ property string icon : 'chat_audio_soundwave_custom'
+ property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'a_n_b_bg').color
+ property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'a_h_b_bg').color
+ property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'a_p_b_bg').color
+ property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'a_n_b_fg').color
+ property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'a_h_b_fg').color
+ property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'a_p_b_fg').color
+
+ property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
+ property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
+ property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
+
+ property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
+ property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
+ property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
+ }
+
+}
diff --git a/linphone-app/ui/modules/Common/Styles/qmldir b/linphone-app/ui/modules/Common/Styles/qmldir
index c9480c1c9..db6a3eb2e 100644
--- a/linphone-app/ui/modules/Common/Styles/qmldir
+++ b/linphone-app/ui/modules/Common/Styles/qmldir
@@ -41,6 +41,7 @@ singleton FormVGroupStyle 1.0 Form/Placements/FormVGroupStyle.qml
singleton TabButtonStyle 1.0 Form/Tab/TabButtonStyle.qml
singleton TabContainerStyle 1.0 Form/Tab/TabContainerStyle.qml
+singleton MediaProgressBarStyle 1.0 Indicators/MediaProgressBarStyle.qml
singleton VuMeterStyle 1.0 Indicators/VuMeterStyle.qml
singleton ApplicationMenuStyle 1.0 Menus/ApplicationMenuStyle.qml
diff --git a/linphone-app/ui/modules/Common/qmldir b/linphone-app/ui/modules/Common/qmldir
index ccca1aefd..da4f8190b 100644
--- a/linphone-app/ui/modules/Common/qmldir
+++ b/linphone-app/ui/modules/Common/qmldir
@@ -65,6 +65,7 @@ InvertedMouseArea 1.0 Helpers/InvertedMouseArea.qml
Icon 1.0 Image/Icon.qml
RoundedImage 1.0 Image/RoundedImage.qml
+MediaProgressBar 1.0 Indicators/MediaProgressBar.qml
VuMeter 1.0 Indicators/VuMeter.qml
ApplicationMenu 1.0 Menus/ApplicationMenu.qml
diff --git a/linphone-app/ui/modules/Linphone/Chat/Chat.qml b/linphone-app/ui/modules/Linphone/Chat/Chat.qml
index 11a3022e2..afaa5c112 100644
--- a/linphone-app/ui/modules/Linphone/Chat/Chat.qml
+++ b/linphone-app/ui/modules/Linphone/Chat/Chat.qml
@@ -426,6 +426,7 @@ Rectangle {
chatMessagePreview.hide()
}
}
+ onAudioRecordRequest: RecorderManager.resetVocalRecorder()
Component.onCompleted: {text = proxyModel.cachedText; cursorPosition=text.length}
Rectangle{
anchors.fill:parent
diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatAudioPreview.qml b/linphone-app/ui/modules/Linphone/Chat/ChatAudioPreview.qml
new file mode 100644
index 000000000..5e1b4cb6a
--- /dev/null
+++ b/linphone-app/ui/modules/Linphone/Chat/ChatAudioPreview.qml
@@ -0,0 +1,155 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+import Common 1.0
+import Linphone 1.0
+import Linphone.Styles 1.0
+import Utils 1.0
+import UtilsCpp 1.0
+import LinphoneEnums 1.0
+
+import Units 1.0
+
+import 'Chat.js' as Logic
+
+// =============================================================================
+
+Rectangle{
+ id: audioPreviewBlock
+ property bool haveRecorder: RecorderManager.haveVocalRecorder
+ property RecorderModel vocalRecorder : (haveRecorder ? RecorderManager.getVocalRecorder() : null)
+ property bool isRecording : (vocalRecorder ? vocalRecorder.state != LinphoneEnums.RecorderStateClosed : false)
+ property bool isPlaying : vocalPlayer.item && vocalPlayer.item.playbackState === SoundPlayer.PlayingState
+
+ onVocalRecorderChanged: if(haveRecorder)
+ audioPreviewBlock.state = 'showed'
+ onIsRecordingChanged: if(isRecording) {
+ mediaProgressBar.start()
+ }else
+ mediaProgressBar.stop()
+ onIsPlayingChanged: isPlaying ? mediaProgressBar.resume() : mediaProgressBar.stop()
+
+ Layout.preferredHeight: 70
+
+ color: ChatAudioPreviewStyle.backgroundColor
+ radius: 0
+ state: "hidden"
+ visible: haveRecorder
+ onVisibleChanged: if(!visible) hide()
+ clip: false
+ function hide(){
+ state = 'hidden'
+ }
+ Loader {
+ id: vocalPlayer
+
+ active: false
+ sourceComponent: SoundPlayer {
+ source: (haveRecorder && vocalRecorder? vocalRecorder.file : '')
+ onStopped:{
+ mediaProgressBar.value = 100
+ }
+ Component.onCompleted: {
+ play()// This will open the file and allow seeking
+ pause()
+ }
+ }
+ }
+ RowLayout{
+ id: lineLayout
+ anchors.fill: parent
+ spacing: 10
+ ActionButton{
+ Layout.preferredHeight: iconSize
+ Layout.preferredWidth: iconSize
+ Layout.leftMargin: 10
+ Layout.alignment: Qt.AlignVCenter
+ isCustom: true
+ colorSet: ChatAudioPreviewStyle.deleteAction
+ onClicked: audioPreviewBlock.hide()
+ }
+ Item{
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter
+ Layout.topMargin: 5
+ Layout.bottomMargin: 5
+ MediaProgressBar{
+ id: mediaProgressBar
+ anchors.fill: parent
+ progressDuration: 0
+ progressPosition: 0
+ stopAtEnd: !audioPreviewBlock.isRecording
+ resetAtEnd: false
+ colorSet: isRecording ? ChatAudioPreviewStyle.recordingProgressionWave : ChatAudioPreviewStyle.progressionWave
+ onEndReached:{
+ if(vocalPlayer.item)
+ vocalPlayer.item.stop()
+ }
+ onRefreshPositionRequested: if( vocalPlayer.item){
+ progressPosition = vocalPlayer.item.getPosition()
+ value = 100 * ( progressPosition / vocalPlayer.item.duration)
+ }else{// Recording
+ progressDuration = vocalRecorder.getDuration()
+ progressPosition = progressDuration
+ value = value + 0.01
+ }
+ onSeekRequested: if( vocalPlayer.item){
+ vocalPlayer.item.seek(ms)
+ progressPosition = vocalPlayer.item.getPosition()
+ value = 100 * (progressPosition / vocalPlayer.item.duration)
+ }
+ }
+ }
+ ActionButton{
+ Layout.preferredHeight: iconSize
+ Layout.preferredWidth: iconSize
+ Layout.rightMargin: 15
+ Layout.leftMargin: 5
+ Layout.alignment: Qt.AlignVCenter
+ isCustom: true
+ colorSet: audioPreviewBlock.isRecording ? ChatAudioPreviewStyle.stopAction
+ : (audioPreviewBlock.isPlaying ? ChatAudioPreviewStyle.pauseAction
+ : ChatAudioPreviewStyle.playAction)
+ onClicked:{
+ if(audioPreviewBlock.isRecording){// Stop the record and save the file
+ audioPreviewBlock.vocalRecorder.stop()
+ mediaProgressBar.value = 100
+ vocalPlayer.active = true
+ }else if(audioPreviewBlock.isPlaying){// Pause the play
+ vocalPlayer.item.pause()
+ }else{// Play the audio
+ vocalPlayer.item.play()
+ }
+ }
+ }
+ }
+ states: [
+ State {
+ name: "hidden"
+ PropertyChanges { target: audioPreviewBlock; opacity: 0 }
+ },
+ State {
+ name: "showed"
+ PropertyChanges { target: audioPreviewBlock; opacity: 1 }
+ }
+ ]
+ transitions: [
+ Transition {
+ from: "*"; to: "showed"
+ SequentialAnimation{
+ ScriptAction{ script: audioPreviewBlock.vocalRecorder.start() }
+ NumberAnimation{ properties: "opacity"; easing.type: Easing.OutBounce; duration: 250 }
+ }
+ },
+ Transition {
+ from: "*"; to: "hidden"
+ SequentialAnimation{
+ ScriptAction{ script: RecorderManager.clearVocalRecorder()}
+ ScriptAction{ script: vocalPlayer.active = false }
+ NumberAnimation{ properties: "opacity"; duration: 250 }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatForwardMessage.qml b/linphone-app/ui/modules/Linphone/Chat/ChatForwardMessage.qml
index ed095baf0..b0eb77c90 100644
--- a/linphone-app/ui/modules/Linphone/Chat/ChatForwardMessage.qml
+++ b/linphone-app/ui/modules/Linphone/Chat/ChatForwardMessage.qml
@@ -49,7 +49,7 @@ Item {
id: headerText
height: icon.height
verticalAlignment: Qt.AlignVCenter
- property string forwardInfo: mainChatMessageModel.getForwardInfoDisplayName
+ property string forwardInfo: mainChatMessageModel ? mainChatMessageModel.getForwardInfoDisplayName : ''
//: 'Forwarded' : Header on a message that contains a forward.
text: 'Forwarded' + (forwardInfo ? ' : ' +forwardInfo : '')
font.family: mainItem.customFont.family
diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatMessagePreview.qml b/linphone-app/ui/modules/Linphone/Chat/ChatMessagePreview.qml
index b87f7985d..39f0862da 100644
--- a/linphone-app/ui/modules/Linphone/Chat/ChatMessagePreview.qml
+++ b/linphone-app/ui/modules/Linphone/Chat/ChatMessagePreview.qml
@@ -15,18 +15,22 @@ import 'Chat.js' as Logic
// =============================================================================
ColumnLayout{
property alias replyChatMessageModel : replyPreview.replyChatMessageModel
- property int maxHeight: parent.height
+ property int maxHeight: parent.height - ( audioPreview.visible ? audioPreview.height : 0)
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
- height: replyPreview.height
+ spacing: 0
function hide(){
replyPreview.hide()
+ audioPreview.hide()
}
-
ChatReplyPreview{
id: replyPreview
Layout.fillWidth: true
}
+ ChatAudioPreview{
+ id: audioPreview
+ Layout.fillWidth: true
+ }
}
\ No newline at end of file
diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatReplyMessage.qml b/linphone-app/ui/modules/Linphone/Chat/ChatReplyMessage.qml
index 78e364301..08eecd2d7 100644
--- a/linphone-app/ui/modules/Linphone/Chat/ChatReplyMessage.qml
+++ b/linphone-app/ui/modules/Linphone/Chat/ChatReplyMessage.qml
@@ -33,7 +33,7 @@ Item {
width: maxWidth < 0 || maxWidth > fitWidth ? fitWidth : maxWidth
height: fitHeight
- onMainChatMessageModelChanged: if( mainChatMessageModel.replyChatMessageModel) chatMessageModel = mainChatMessageModel.replyChatMessageModel
+ onMainChatMessageModelChanged: if( mainChatMessageModel && mainChatMessageModel.replyChatMessageModel) chatMessageModel = mainChatMessageModel.replyChatMessageModel
ColumnLayout{
@@ -118,7 +118,7 @@ Item {
font.pointSize: Units.dp * (customFont.pointSize + ChatReplyMessageStyle.replyArea.pointSizeOffset)
font.weight: Font.Light
color: ChatReplyMessageStyle.replyArea.foregroundColor
- text: (visible ? Utils.encodeTextToQmlRichFormat(chatMessageModel.content, {
+ text: (visible && chatMessageModel? Utils.encodeTextToQmlRichFormat(chatMessageModel.content, {
imagesHeight: ChatStyle.entry.message.images.height,
imagesWidth: ChatStyle.entry.message.images.width
})
diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatReplyPreview.qml b/linphone-app/ui/modules/Linphone/Chat/ChatReplyPreview.qml
index 6081ce1bb..72cd3f936 100644
--- a/linphone-app/ui/modules/Linphone/Chat/ChatReplyPreview.qml
+++ b/linphone-app/ui/modules/Linphone/Chat/ChatReplyPreview.qml
@@ -19,7 +19,7 @@ Rectangle{
property ChatMessageModel replyChatMessageModel
onReplyChatMessageModelChanged: if(replyChatMessageModel) replyPreviewBlock.state = "showed"
- Layout.preferredHeight: Math.min(replayPreviewText.implicitHeight + replyPreviewHeaderArea.implicitHeight + 10, parent.maxHeight)
+ Layout.preferredHeight: Math.min(replyPreviewText.implicitHeight + replyPreviewHeaderArea.implicitHeight + 10, parent.maxHeight)
property int leftMargin: textArea.textLeftMargin
property int rightMargin: textArea.textRightMargin
@@ -69,11 +69,11 @@ Rectangle{
}
}
Flickable {
- id: replayPreviewTextArea
- ScrollBar.vertical: ForceScrollBar {visible: replayPreviewTextArea.height < replayPreviewText.implicitHeight}
+ id: replyPreviewTextArea
+ ScrollBar.vertical: ForceScrollBar {visible: replyPreviewTextArea.height < replyPreviewText.implicitHeight}
boundsBehavior: Flickable.StopAtBounds
clip: true
- contentHeight: replayPreviewText.implicitHeight
+ contentHeight: replyPreviewText.implicitHeight
contentWidth: width - ScrollBar.vertical.width
flickableDirection: Flickable.VerticalFlick
@@ -81,7 +81,7 @@ Rectangle{
Layout.fillWidth: true
TextEdit {
- id: replayPreviewText
+ id: replyPreviewText
property font customFont : SettingsModel.textMessageFont
anchors.left: parent.left
diff --git a/linphone-app/ui/modules/Linphone/Styles/Chat/ChatAudioPreviewStyle.qml b/linphone-app/ui/modules/Linphone/Styles/Chat/ChatAudioPreviewStyle.qml
new file mode 100644
index 000000000..f27727b2b
--- /dev/null
+++ b/linphone-app/ui/modules/Linphone/Styles/Chat/ChatAudioPreviewStyle.qml
@@ -0,0 +1,120 @@
+pragma Singleton
+import QtQml 2.2
+
+import Units 1.0
+import ColorsList 1.0
+
+// =============================================================================
+
+QtObject {
+ property string sectionName : 'ChatAudioPreview'
+ property color color: ColorsList.add(sectionName, 'q').color
+ property QtObject header: QtObject{
+ property color color: ColorsList.add(sectionName+'_header', 'h').color
+ property int pointSizeOffset: -3
+ property QtObject replyIcon: QtObject{
+ property string icon : 'menu_reply_custom'
+ property int iconSize: 22
+ }
+ }
+ property color backgroundColor: ColorsList.add(sectionName+'_bg', 'aa').color
+
+ property QtObject audioArea: QtObject{
+ property color outgoingMarkColor: ColorsList.add(sectionName+'_reply_outgoing_mark', 'm').color
+ property color incomingMarkColor: ColorsList.add(sectionName+'_reply_incoming_mark', 'r').color
+ property color backgroundColor: ColorsList.add(sectionName+'_reply_bg', 'q').color
+ property color foregroundColor: ColorsList.add(sectionName+'_reply_fg', 'h').color
+ property int usernamePointSizeOffset: -2
+ property int pointSizeOffset: -2
+ }
+ property QtObject deleteAction: QtObject {
+ property int iconSize: 40
+ property string name : 'delete'
+ property string icon : 'delete_custom'
+ property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
+ property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
+ property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
+ property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
+ property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
+ property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
+ }
+ property QtObject stopAction: QtObject {
+ property int iconSize: 30
+ property string name : 'stop'
+ property string icon : 'chat_audio_stop_custom'
+ property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
+ property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
+ property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
+ property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
+ property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
+ property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
+ }
+ property QtObject pauseAction: QtObject {
+ property int iconSize: 30
+ property string name : 'pause'
+ property string icon : 'chat_audio_pause_custom'
+ property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
+ property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
+ property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
+ property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
+ property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
+ property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
+ }
+ property QtObject playAction: QtObject {
+ property int iconSize: 30
+ property string name : 'play'
+ property string icon : 'chat_audio_play_custom'
+ property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
+ property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
+ property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
+ property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
+ property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
+ property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
+ }
+
+ property QtObject progressionWave: QtObject{
+ property int iconSize: 30
+ property int iconHeight: 40
+ property int iconWidth: 250
+ property string name : 'progression_soundwave'
+ property string icon : 'chat_audio_soundwave_custom'
+ property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'a_n_b_bg').color
+ property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'a_h_b_bg').color
+ property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'a_p_b_bg').color
+ property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'a_n_b_fg').color
+ property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'a_h_b_fg').color
+ property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'a_p_b_fg').color
+
+ property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
+ property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
+ property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
+
+ property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
+ property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
+ property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
+ }
+
+ property QtObject recordingProgressionWave: QtObject{
+ property int iconSize: 30
+ property int iconHeight: 40
+ property int iconWidth: 250
+ property string name : 'recording_progression_soundwave'
+ property string icon : 'chat_audio_soundwave_custom'
+ property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'r_n_b_bg').color
+ property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'r_h_b_bg').color
+ property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'r_p_b_bg').color
+ property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'r_n_b_fg').color
+ property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'r_h_b_fg').color
+ property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'r_p_b_fg').color
+
+ property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
+ property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
+ property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
+
+ property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
+ property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
+ property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
+ }
+
+ property int padding: 8
+}
diff --git a/linphone-app/ui/modules/Linphone/Styles/qmldir b/linphone-app/ui/modules/Linphone/Styles/qmldir
index 98d31211f..1b421a0e3 100644
--- a/linphone-app/ui/modules/Linphone/Styles/qmldir
+++ b/linphone-app/ui/modules/Linphone/Styles/qmldir
@@ -10,6 +10,7 @@ singleton CardBlockStyle 1.0 Blocks/CardBlockStyle.qml
singleton RequestBlockStyle 1.0 Blocks/RequestBlockStyle.qml
singleton ChatStyle 1.0 Chat/ChatStyle.qml
+singleton ChatAudioPreviewStyle 1.0 Chat/ChatAudioPreviewStyle.qml
singleton ChatForwardMessageStyle 1.0 Chat/ChatForwardMessageStyle.qml
singleton ChatReplyMessageStyle 1.0 Chat/ChatReplyMessageStyle.qml
diff --git a/linphone-app/ui/modules/Linphone/qmldir b/linphone-app/ui/modules/Linphone/qmldir
index 5fd04e4e4..8d0b973b4 100644
--- a/linphone-app/ui/modules/Linphone/qmldir
+++ b/linphone-app/ui/modules/Linphone/qmldir
@@ -15,6 +15,7 @@ Calls 1.0 Calls/Calls.qml
CallStatistics 1.0 Calls/CallStatistics.qml
Chat 1.0 Chat/Chat.qml
+ChatAudioPreview 1.0 Chat/ChatAudioPreview.qml
ChatMessagePreview 1.0 Chat/ChatMessagePreview.qml
ChatForwardMessage 1.0 Chat/ChatForwardMessage.qml
ChatReplyMessage 1.0 Chat/ChatReplyMessage.qml
diff --git a/linphone-sdk b/linphone-sdk
index 3ea9277c8..b1171bf5f 160000
--- a/linphone-sdk
+++ b/linphone-sdk
@@ -1 +1 @@
-Subproject commit 3ea9277c8035deb632e47519971122550ca8f64d
+Subproject commit b1171bf5faf08237ddc6e074b71dd6319c0470af